search settings

This commit is contained in:
SPRINX0\prochazka
2024-12-16 11:47:53 +01:00
parent dc302f89c7
commit a20b4b3339
8 changed files with 145 additions and 34 deletions

View File

@@ -2,9 +2,9 @@ import _compact from 'lodash/compact';
import _isString from 'lodash/isString'; import _isString from 'lodash/isString';
import _startCase from 'lodash/startCase'; import _startCase from 'lodash/startCase';
export interface FilterNameDefinition { // export interface FilterNameDefinition {
childName: string; // childName: string;
} // }
function camelMatch(filter: string, text: string): boolean { function camelMatch(filter: string, text: string): boolean {
if (!text) return false; 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; if (!filter) return true;
// const camelVariants = [name.replace(/[^A-Z]/g, '')] // const camelVariants = [name.replace(/[^A-Z]/g, '')]
@@ -28,22 +28,32 @@ export function filterName(filter: string, ...names: (string | FilterNameDefinit
const namesCompacted = _compact(names); 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) { for (const token of tokens) {
// const tokenUpper = token.toUpperCase(); const found = namesCompacted.find(name => camelMatch(token, name));
if (token.startsWith('#')) { if (!found) return false;
// 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;
}
} }
return true; 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;
}

View File

@@ -31,6 +31,7 @@
export let showPinnedInsteadOfUnpin = false; export let showPinnedInsteadOfUnpin = false;
export let indentLevel = 0; export let indentLevel = 0;
export let disableBoldScroll = false; export let disableBoldScroll = false;
export let filter = null;
$: isChecked = $: isChecked =
checkedObjectsStore && $checkedObjectsStore.find(x => module?.extractKey(data) == module?.extractKey(x)); checkedObjectsStore && $checkedObjectsStore.find(x => module?.extractKey(data) == module?.extractKey(x));

View File

@@ -31,7 +31,7 @@
$: filtered = !groupFunc $: filtered = !groupFunc
? list.filter(data => { ? 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; if (matcher && !matcher(filter)) return false;
return true; return true;
}) })
@@ -39,7 +39,7 @@
$: childrenMatched = !groupFunc $: childrenMatched = !groupFunc
? list.filter(data => { ? 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; if (matcher && !matcher(filter)) return false;
return true; return true;
}) })
@@ -62,7 +62,7 @@
$: listGrouped = groupFunc $: listGrouped = groupFunc
? _.compact( ? _.compact(
(list || []).map(data => { (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 isMatched = matcher && !matcher(filter) ? false : true;
const group = groupFunc(data); const group = groupFunc(data);
return { group, data, isMatched }; return { group, data, isMatched };

View File

@@ -57,6 +57,7 @@
{module} {module}
{disableContextMenu} {disableContextMenu}
{passProps} {passProps}
{filter}
/> />
{#if (isExpanded || isExpandedBySearch) && subItemsComponent} {#if (isExpanded || isExpandedBySearch) && subItemsComponent}

View File

@@ -3,14 +3,30 @@
export const extractKey = ({ schemaName, pureName }) => (schemaName ? `${schemaName}.${pureName}` : pureName); export const extractKey = ({ schemaName, pureName }) => (schemaName ? `${schemaName}.${pureName}` : pureName);
export const createMatcher = export const createMatcher =
({ schemaName, pureName, columns }) => (
filter => { schemaName, pureName, objectComment, tableEngine, columns, objectTypeField, createSql },
filterName( cfg = DEFAULT_SEARCH_SETTINGS
filter, ) =>
pureName, filter => {
schemaName, const filterArgs = [];
...(columns?.map(({ columnName }) => ({ childName: columnName })) || []) 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 createTitle = ({ schemaName, pureName }) => (schemaName ? `${schemaName}.${pureName}` : pureName);
export const databaseObjectIcons = { export const databaseObjectIcons = {
@@ -877,9 +893,11 @@
import AppObjectCore from './AppObjectCore.svelte'; import AppObjectCore from './AppObjectCore.svelte';
import { import {
currentDatabase, currentDatabase,
DEFAULT_SEARCH_SETTINGS,
extensions, extensions,
getActiveTab, getActiveTab,
getCurrentSettings, getCurrentSettings,
getDatabaseObjectAppObjectSearchSettings,
getExtensions, getExtensions,
getLastUsedDefaultActions, getLastUsedDefaultActions,
lastUsedDefaultActions, lastUsedDefaultActions,

View File

@@ -55,6 +55,8 @@
let submenuItem; let submenuItem;
let submenuOffset; let submenuOffset;
let switchIndex = 0;
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
let closeHandlers = []; let closeHandlers = [];
@@ -80,6 +82,14 @@
submenuOffset = hoverOffset; submenuOffset = hoverOffset;
return; return;
} }
if (item.switchStore) {
item.switchStore.update(x => ({
...x,
[item.switchValue]: !x[item.switchValue],
}));
switchIndex++;
return;
}
dispatchClose(); dispatchClose();
if (onCloseParent) onCloseParent(); if (onCloseParent) onCloseParent();
if (item.onClick) item.onClick(); if (item.onClick) item.onClick();
@@ -131,7 +141,18 @@
}} }}
> >
<a on:click={e => handleClick(e, item)} class:disabled={item.disabled} class:bold={item.isBold}> <a on:click={e => handleClick(e, item)} class:disabled={item.disabled} class:bold={item.isBold}>
{item.text || item.label} <span>
{#if item.switchStoreGetter}
{#key switchIndex}
{#if item.switchStoreGetter()[item.switchValue]}
<FontIcon icon="icon checkbox-marked" padRight />
{:else}
<FontIcon icon="icon checkbox-blank" padRight />
{/if}
{/key}
{/if}
{item.text || item.label}
</span>
{#if item.keyText} {#if item.keyText}
<span class="keyText">{formatKeyText(item.keyText)}</span> <span class="keyText">{formatKeyText(item.keyText)}</span>
{/if} {/if}
@@ -179,6 +200,7 @@
white-space: nowrap; white-space: nowrap;
overflow-y: auto; overflow-y: auto;
max-height: calc(100% - 20px); max-height: calc(100% - 20px);
user-select: none;
} }
.keyText { .keyText {

View File

@@ -161,6 +161,25 @@ export const lastUsedDefaultActions = writableWithStorage({}, 'lastUsedDefaultAc
export const selectedDatabaseObjectAppObject = writable(null); export const selectedDatabaseObjectAppObject = writable(null);
export const focusedConnectionOrDatabase = writable<{ conid: string; database?: string; connection: any }>(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]) => export const currentThemeDefinition = derived([currentTheme, extensions], ([$currentTheme, $extensions]) =>
$extensions.themes.find(x => x.themeClassName == $currentTheme) $extensions.themes.find(x => x.themeClassName == $currentTheme)
); );
@@ -359,3 +378,11 @@ lastUsedDefaultActions.subscribe(value => {
lastUsedDefaultActionsValue = value; lastUsedDefaultActionsValue = value;
}); });
export const getLastUsedDefaultActions = () => lastUsedDefaultActionsValue; export const getLastUsedDefaultActions = () => lastUsedDefaultActionsValue;
let databaseObjectAppObjectSearchSettingsValue: typeof DEFAULT_SEARCH_SETTINGS = {
...DEFAULT_SEARCH_SETTINGS,
};
databaseObjectAppObjectSearchSettings.subscribe(value => {
databaseObjectAppObjectSearchSettingsValue = value;
});
export const getDatabaseObjectAppObjectSearchSettings = () => databaseObjectAppObjectSearchSettingsValue;

View File

@@ -38,8 +38,10 @@
import { extractDbNameFromComposite, findEngineDriver } from 'dbgate-tools'; import { extractDbNameFromComposite, findEngineDriver } from 'dbgate-tools';
import { import {
currentDatabase, currentDatabase,
databaseObjectAppObjectSearchSettings,
extensions, extensions,
focusedConnectionOrDatabase, focusedConnectionOrDatabase,
getDatabaseObjectAppObjectSearchSettings,
getSelectedDatabaseObjectAppObject, getSelectedDatabaseObjectAppObject,
selectedDatabaseObjectAppObject, selectedDatabaseObjectAppObject,
} from '../stores'; } from '../stores';
@@ -124,8 +126,32 @@
return res; 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 => { $: flatFilteredList = objectList.filter(data => {
const matcher = databaseObjectAppObject.createMatcher(data); const matcher = databaseObjectAppObject.createMatcher(data, $databaseObjectAppObjectSearchSettings);
if (matcher && !matcher(filter)) return false; if (matcher && !matcher(filter)) return false;
return true; return true;
}); });
@@ -184,7 +210,7 @@
{:else} {:else}
<SearchBoxWrapper> <SearchBoxWrapper>
<SearchInput <SearchInput
placeholder="Search in tables, objects, # prefix in columns" placeholder="Search in tables, views, procedures"
bind:value={filter} bind:value={filter}
bind:this={domFilter} bind:this={domFilter}
onFocusFilteredList={() => { onFocusFilteredList={() => {
@@ -192,7 +218,12 @@
}} }}
/> />
<CloseSearchButton bind:filter /> <CloseSearchButton bind:filter />
<DropDownButton icon="icon plus-thick" menu={createAddMenu} /> {#if filter}
<DropDownButton icon="icon filter" menu={createSearchMenu} />
{/if}
{#if !filter}
<DropDownButton icon="icon plus-thick" menu={createAddMenu} />
{/if}
<InlineButton on:click={handleRefreshDatabase} title="Refresh database connection and object list" square> <InlineButton on:click={handleRefreshDatabase} title="Refresh database connection and object list" square>
<FontIcon icon="icon refresh" /> <FontIcon icon="icon refresh" />
</InlineButton> </InlineButton>
@@ -256,6 +287,7 @@
showPinnedInsteadOfUnpin: true, showPinnedInsteadOfUnpin: true,
connection: $connection, connection: $connection,
hideSchemaName: !!$appliedCurrentSchema, hideSchemaName: !!$appliedCurrentSchema,
searchSettings: $databaseObjectAppObjectSearchSettings,
}} }}
getIsExpanded={data => getIsExpanded={data =>
expandedObjects.includes(`${data.objectTypeField}||${data.schemaName}||${data.pureName}`)} expandedObjects.includes(`${data.objectTypeField}||${data.schemaName}||${data.pureName}`)}