diff --git a/packages/tools/src/filterName.ts b/packages/tools/src/filterName.ts index 81c1d0b8e..8cdc38d43 100644 --- a/packages/tools/src/filterName.ts +++ b/packages/tools/src/filterName.ts @@ -2,9 +2,9 @@ import _compact from 'lodash/compact'; import _isString from 'lodash/isString'; import _startCase from 'lodash/startCase'; -export interface FilterNameDefinition { - childName: string; -} +// export interface FilterNameDefinition { +// childName: string; +// } function camelMatch(filter: string, text: string): boolean { if (!text) return false; @@ -20,7 +20,7 @@ function camelMatch(filter: string, text: string): boolean { } } -export function filterName(filter: string, ...names: (string | FilterNameDefinition)[]) { +export function filterName(filter: string, ...names: string[]) { if (!filter) return true; // const camelVariants = [name.replace(/[^A-Z]/g, '')] @@ -28,22 +28,32 @@ export function filterName(filter: string, ...names: (string | FilterNameDefinit const namesCompacted = _compact(names); - // @ts-ignore - const namesOwn: string[] = namesCompacted.filter(x => _isString(x)); - // @ts-ignore - const namesChild: string[] = namesCompacted.filter(x => x.childName).map(x => x.childName); - for (const token of tokens) { - // const tokenUpper = token.toUpperCase(); - if (token.startsWith('#')) { - // const tokenUpperSub = tokenUpper.substring(1); - const found = namesChild.find(name => camelMatch(token.substring(1), name)); - if (!found) return false; - } else { - const found = namesOwn.find(name => camelMatch(token, name)); - if (!found) return false; - } + const found = namesCompacted.find(name => camelMatch(token, name)); + if (!found) return false; } return true; } + +export function tokenizeBySearchFilter(text: string, filter: string): { token: string; isMatch: boolean }[] { + const tokens = filter.split(' ').map(x => x.trim()); + + const result = []; + let lastMatch = 0; + for (const token of tokens) { + const index = text.indexOf(token, lastMatch); + if (index < 0) { + result.push({ token, isMatch: false }); + continue; + } + + result.push({ token: text.substring(lastMatch, index), isMatch: false }); + result.push({ token: text.substring(index, index + token.length), isMatch: true }); + lastMatch = index + token.length; + } + + result.push({ token: text.substring(lastMatch), isMatch: false }); + + return result; +} diff --git a/packages/web/src/appobj/AppObjectCore.svelte b/packages/web/src/appobj/AppObjectCore.svelte index e6c099d1f..6f7f072a0 100644 --- a/packages/web/src/appobj/AppObjectCore.svelte +++ b/packages/web/src/appobj/AppObjectCore.svelte @@ -31,6 +31,7 @@ export let showPinnedInsteadOfUnpin = false; export let indentLevel = 0; export let disableBoldScroll = false; + export let filter = null; $: isChecked = checkedObjectsStore && $checkedObjectsStore.find(x => module?.extractKey(data) == module?.extractKey(x)); diff --git a/packages/web/src/appobj/AppObjectList.svelte b/packages/web/src/appobj/AppObjectList.svelte index 87662a659..6a7387cf8 100644 --- a/packages/web/src/appobj/AppObjectList.svelte +++ b/packages/web/src/appobj/AppObjectList.svelte @@ -31,7 +31,7 @@ $: filtered = !groupFunc ? list.filter(data => { - const matcher = module.createMatcher && module.createMatcher(data); + const matcher = module.createMatcher && module.createMatcher(data, passProps?.searchSettings); if (matcher && !matcher(filter)) return false; return true; }) @@ -39,7 +39,7 @@ $: childrenMatched = !groupFunc ? list.filter(data => { - const matcher = module.createChildMatcher && module.createChildMatcher(data); + const matcher = module.createChildMatcher && module.createChildMatcher(data, passProps?.searchSettings); if (matcher && !matcher(filter)) return false; return true; }) @@ -62,7 +62,7 @@ $: listGrouped = groupFunc ? _.compact( (list || []).map(data => { - const matcher = module.createMatcher && module.createMatcher(data); + const matcher = module.createMatcher && module.createMatcher(data, passProps?.searchSettings); const isMatched = matcher && !matcher(filter) ? false : true; const group = groupFunc(data); return { group, data, isMatched }; diff --git a/packages/web/src/appobj/AppObjectListItem.svelte b/packages/web/src/appobj/AppObjectListItem.svelte index 395127e63..518564b27 100644 --- a/packages/web/src/appobj/AppObjectListItem.svelte +++ b/packages/web/src/appobj/AppObjectListItem.svelte @@ -57,6 +57,7 @@ {module} {disableContextMenu} {passProps} + {filter} /> {#if (isExpanded || isExpandedBySearch) && subItemsComponent} diff --git a/packages/web/src/appobj/DatabaseObjectAppObject.svelte b/packages/web/src/appobj/DatabaseObjectAppObject.svelte index 5e4ffeb5f..aae68a01e 100644 --- a/packages/web/src/appobj/DatabaseObjectAppObject.svelte +++ b/packages/web/src/appobj/DatabaseObjectAppObject.svelte @@ -3,14 +3,30 @@ export const extractKey = ({ schemaName, pureName }) => (schemaName ? `${schemaName}.${pureName}` : pureName); export const createMatcher = - ({ schemaName, pureName, columns }) => - filter => - filterName( - filter, - pureName, - schemaName, - ...(columns?.map(({ columnName }) => ({ childName: columnName })) || []) - ); + ( + { schemaName, pureName, objectComment, tableEngine, columns, objectTypeField, createSql }, + cfg = DEFAULT_SEARCH_SETTINGS + ) => + filter => { + const filterArgs = []; + if (cfg.schemaName) filterArgs.push(schemaName); + if (objectTypeField == 'tables') { + if (cfg.tableName) filterArgs.push(pureName); + if (cfg.tableComment) filterArgs.push(objectComment); + if (cfg.tableEngine) filterArgs.push(tableEngine); + + for (const column of columns || []) { + if (cfg.columnName) filterArgs.push(column.columnName); + if (cfg.columnComment) filterArgs.push(column.columnComment); + if (cfg.columnDataType) filterArgs.push(column.dataType); + } + } else { + if (cfg.sqlObjectName) filterArgs.push(pureName); + if (cfg.sqlObjectText) filterArgs.push(createSql); + } + + return filterName(filter, ...filterArgs); + }; export const createTitle = ({ schemaName, pureName }) => (schemaName ? `${schemaName}.${pureName}` : pureName); export const databaseObjectIcons = { @@ -877,9 +893,11 @@ import AppObjectCore from './AppObjectCore.svelte'; import { currentDatabase, + DEFAULT_SEARCH_SETTINGS, extensions, getActiveTab, getCurrentSettings, + getDatabaseObjectAppObjectSearchSettings, getExtensions, getLastUsedDefaultActions, lastUsedDefaultActions, diff --git a/packages/web/src/modals/DropDownMenu.svelte b/packages/web/src/modals/DropDownMenu.svelte index e1969c725..6af2f2e90 100644 --- a/packages/web/src/modals/DropDownMenu.svelte +++ b/packages/web/src/modals/DropDownMenu.svelte @@ -55,6 +55,8 @@ let submenuItem; let submenuOffset; + let switchIndex = 0; + const dispatch = createEventDispatcher(); let closeHandlers = []; @@ -80,6 +82,14 @@ submenuOffset = hoverOffset; return; } + if (item.switchStore) { + item.switchStore.update(x => ({ + ...x, + [item.switchValue]: !x[item.switchValue], + })); + switchIndex++; + return; + } dispatchClose(); if (onCloseParent) onCloseParent(); if (item.onClick) item.onClick(); @@ -131,7 +141,18 @@ }} > handleClick(e, item)} class:disabled={item.disabled} class:bold={item.isBold}> - {item.text || item.label} + + {#if item.switchStoreGetter} + {#key switchIndex} + {#if item.switchStoreGetter()[item.switchValue]} + + {:else} + + {/if} + {/key} + {/if} + {item.text || item.label} + {#if item.keyText} {formatKeyText(item.keyText)} {/if} @@ -179,6 +200,7 @@ white-space: nowrap; overflow-y: auto; max-height: calc(100% - 20px); + user-select: none; } .keyText { diff --git a/packages/web/src/stores.ts b/packages/web/src/stores.ts index a48d2018b..997bfcae4 100644 --- a/packages/web/src/stores.ts +++ b/packages/web/src/stores.ts @@ -161,6 +161,25 @@ export const lastUsedDefaultActions = writableWithStorage({}, 'lastUsedDefaultAc export const selectedDatabaseObjectAppObject = writable(null); export const focusedConnectionOrDatabase = writable<{ conid: string; database?: string; connection: any }>(null); +export const DEFAULT_SEARCH_SETTINGS = { + collectionName: true, + schemaName: false, + tableName: true, + viewName: true, + columnName: true, + columnDataType: true, + tableComment: true, + columnComment: true, + sqlObjectName: true, + sqlObjectText: true, + tableEngine: false, +}; + +export const databaseObjectAppObjectSearchSettings = writableWithStorage( + DEFAULT_SEARCH_SETTINGS, + 'databaseObjectAppObjectSearchSettings' +); + export const currentThemeDefinition = derived([currentTheme, extensions], ([$currentTheme, $extensions]) => $extensions.themes.find(x => x.themeClassName == $currentTheme) ); @@ -358,4 +377,12 @@ let lastUsedDefaultActionsValue = {}; lastUsedDefaultActions.subscribe(value => { lastUsedDefaultActionsValue = value; }); -export const getLastUsedDefaultActions = () => lastUsedDefaultActionsValue; \ No newline at end of file +export const getLastUsedDefaultActions = () => lastUsedDefaultActionsValue; + +let databaseObjectAppObjectSearchSettingsValue: typeof DEFAULT_SEARCH_SETTINGS = { + ...DEFAULT_SEARCH_SETTINGS, +}; +databaseObjectAppObjectSearchSettings.subscribe(value => { + databaseObjectAppObjectSearchSettingsValue = value; +}); +export const getDatabaseObjectAppObjectSearchSettings = () => databaseObjectAppObjectSearchSettingsValue; diff --git a/packages/web/src/widgets/SqlObjectList.svelte b/packages/web/src/widgets/SqlObjectList.svelte index 2be2c6ee1..aeb729871 100644 --- a/packages/web/src/widgets/SqlObjectList.svelte +++ b/packages/web/src/widgets/SqlObjectList.svelte @@ -38,8 +38,10 @@ import { extractDbNameFromComposite, findEngineDriver } from 'dbgate-tools'; import { currentDatabase, + databaseObjectAppObjectSearchSettings, extensions, focusedConnectionOrDatabase, + getDatabaseObjectAppObjectSearchSettings, getSelectedDatabaseObjectAppObject, selectedDatabaseObjectAppObject, } from '../stores'; @@ -124,8 +126,32 @@ return res; } + function createSearchMenu() { + const res = []; + if (driver?.databaseEngineTypes?.includes('document')) { + res.push({ label: 'Collection names' }); + } + if (driver?.databaseEngineTypes?.includes('sql')) { + res.push({ label: 'Schema name', switchValue: 'schemaName' }); + res.push({ label: 'Table name', switchValue: 'tableName' }); + res.push({ label: 'View name', switchValue: 'viewName' }); + res.push({ label: 'Column name', switchValue: 'columnName' }); + res.push({ label: 'Column data type', switchValue: 'columnType' }); + res.push({ label: 'Table comment', switchValue: 'tableComment' }); + res.push({ label: 'Column comment', switchValue: 'columnComment' }); + res.push({ label: 'Procedure/function/trigger name', switchValue: 'sqlObjectName' }); + res.push({ label: 'Procedure/function/trigger text', switchValue: 'sqlObjectText' }); + res.push({ label: 'Table engine', switchValue: 'tableEngine' }); + } + return res.map(item => ({ + ...item, + switchStore: databaseObjectAppObjectSearchSettings, + switchStoreGetter: getDatabaseObjectAppObjectSearchSettings, + })); + } + $: flatFilteredList = objectList.filter(data => { - const matcher = databaseObjectAppObject.createMatcher(data); + const matcher = databaseObjectAppObject.createMatcher(data, $databaseObjectAppObjectSearchSettings); if (matcher && !matcher(filter)) return false; return true; }); @@ -184,7 +210,7 @@ {:else} { @@ -192,7 +218,12 @@ }} /> - + {#if filter} + + {/if} + {#if !filter} + + {/if} @@ -256,6 +287,7 @@ showPinnedInsteadOfUnpin: true, connection: $connection, hideSchemaName: !!$appliedCurrentSchema, + searchSettings: $databaseObjectAppObjectSearchSettings, }} getIsExpanded={data => expandedObjects.includes(`${data.objectTypeField}||${data.schemaName}||${data.pureName}`)}