diff --git a/packages/tools/src/filterName.ts b/packages/tools/src/filterName.ts
index 81c1d0b8e..504ebf08d 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,124 @@ 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 filterNameCompoud(
+ filter: string,
+ namesMain: string[],
+ namesChild: string[]
+): 'main' | 'child' | 'both' | 'none' {
+ if (!filter) return 'both';
+
+ // const camelVariants = [name.replace(/[^A-Z]/g, '')]
+ const tokens = filter.split(' ').map(x => x.trim());
+
+ const namesCompactedMain = _compact(namesMain);
+ const namesCompactedChild = _compact(namesChild);
+
+ let isMainOnly = true;
+ let isChildOnly = true;
+
+ for (const token of tokens) {
+ const foundMain = namesCompactedMain.find(name => camelMatch(token, name));
+ const foundChild = namesCompactedChild.find(name => camelMatch(token, name));
+ if (!foundMain && !foundChild) return 'none';
+
+ if (!foundMain) isMainOnly = false;
+ if (!foundChild) isChildOnly = false;
+ }
+
+ if (isMainOnly && isChildOnly) return 'both';
+ if (isMainOnly) return 'main';
+ if (isChildOnly) return 'child';
+ return 'none';
+}
+
+export function tokenizeBySearchFilter(text: string, filter: string): { text: string; isMatch: boolean }[] {
+ const camelTokens = [];
+ const stdTokens = [];
+ for (const token of filter.split(' ').map(x => x.trim())) {
+ if (token.replace(/[A-Z]/g, '').length == 0) {
+ camelTokens.push(token);
+ } else {
+ stdTokens.push(token.toUpperCase());
+ }
+ }
+
+ let res = [
+ {
+ text,
+ isMatch: false,
+ },
+ ];
+
+ for (const token of camelTokens) {
+ const nextres = [];
+ for (const item of res) {
+ const indexes = [];
+ for (const char of token) {
+ const index = item.text.indexOf(char, indexes.length > 0 ? indexes[indexes.length - 1] + 1 : 0);
+ if (index < 0) {
+ indexes.push(-1);
+ } else {
+ indexes.push(index);
+ }
+ }
+ if (indexes.some(x => x < 0)) {
+ nextres.push(item);
+ } else {
+ let lastIndex = 0;
+ for (let i = 0; i < indexes.length; i++) {
+ if (indexes[i] > lastIndex) {
+ nextres.push({ text: item.text.substring(lastIndex, indexes[i]), isMatch: false });
+ }
+ nextres.push({ text: item.text.substring(indexes[i], indexes[i] + 1), isMatch: true });
+ lastIndex = indexes[i] + 1;
+ }
+ nextres.push({ text: item.text.substring(lastIndex), isMatch: false });
+ }
+ }
+ res = nextres;
+ }
+
+ for (const token of stdTokens) {
+ const nextres = [];
+ for (const item of res) {
+ const index = item.text?.toUpperCase().indexOf(token);
+ if (index < 0) {
+ nextres.push(item);
+ } else {
+ nextres.push({ text: item.text.substring(0, index), isMatch: false });
+ nextres.push({ text: item.text.substring(index, index + token.length), isMatch: true });
+ nextres.push({ text: item.text.substring(index + token.length), isMatch: false });
+ }
+ }
+ res = nextres;
+ }
+
+ return res.filter(x => x.text.length > 0);
+
+ // 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/AppFileAppObject.svelte b/packages/web/src/appobj/AppFileAppObject.svelte
index c51176f6b..f3ed7e257 100644
--- a/packages/web/src/appobj/AppFileAppObject.svelte
+++ b/packages/web/src/appobj/AppFileAppObject.svelte
@@ -29,8 +29,8 @@
export const extractKey = data => data.fileName;
export const createMatcher =
- ({ fileName }) =>
filter =>
+ ({ fileName }) =>
filterName(filter, fileName);
const APP_ICONS = {
'config.json': 'img json',
diff --git a/packages/web/src/appobj/AppFolderAppObject.svelte b/packages/web/src/appobj/AppFolderAppObject.svelte
index 857228e85..8cbc52a8c 100644
--- a/packages/web/src/appobj/AppFolderAppObject.svelte
+++ b/packages/web/src/appobj/AppFolderAppObject.svelte
@@ -1,6 +1,6 @@
{#if groupFunc}
@@ -107,7 +122,7 @@
/>
{/each}
{:else}
- {#each list as data}
+ {#each listLimited as data}
{/each}
+ {#if isListLimited}
+
+ {
+ expandLimited = true;
+ }}>Show next {filtered.length - listLimited.length}
+
+ {/if}
{/if}
diff --git a/packages/web/src/appobj/AppObjectListItem.svelte b/packages/web/src/appobj/AppObjectListItem.svelte
index 395127e63..0bafecdb4 100644
--- a/packages/web/src/appobj/AppObjectListItem.svelte
+++ b/packages/web/src/appobj/AppObjectListItem.svelte
@@ -57,15 +57,19 @@
{module}
{disableContextMenu}
{passProps}
+ {filter}
/>
{#if (isExpanded || isExpandedBySearch) && subItemsComponent}
diff --git a/packages/web/src/appobj/ArchiveFileAppObject.svelte b/packages/web/src/appobj/ArchiveFileAppObject.svelte
index 97874510d..84891e0b4 100644
--- a/packages/web/src/appobj/ArchiveFileAppObject.svelte
+++ b/packages/web/src/appobj/ArchiveFileAppObject.svelte
@@ -42,8 +42,8 @@
export const extractKey = data => data.fileName;
export const createMatcher =
- ({ fileName }) =>
filter =>
+ ({ fileName }) =>
filterName(filter, fileName);
const ARCHIVE_ICONS = {
'table.yaml': 'img table',
@@ -70,7 +70,7 @@
import { getExtensions } from '../stores';
import createQuickExportMenu from '../utility/createQuickExportMenu';
- import { exportQuickExportFile, } from '../utility/exportFileTools';
+ import { exportQuickExportFile } from '../utility/exportFileTools';
import openNewTab from '../utility/openNewTab';
import AppObjectCore from './AppObjectCore.svelte';
import InputTextModal from '../modals/InputTextModal.svelte';
diff --git a/packages/web/src/appobj/ArchiveFolderAppObject.svelte b/packages/web/src/appobj/ArchiveFolderAppObject.svelte
index 35cce3784..27ee13d42 100644
--- a/packages/web/src/appobj/ArchiveFolderAppObject.svelte
+++ b/packages/web/src/appobj/ArchiveFolderAppObject.svelte
@@ -1,6 +1,6 @@
+
+
+
+
diff --git a/packages/web/src/appobj/SavedFileAppObject.svelte b/packages/web/src/appobj/SavedFileAppObject.svelte
index 23011a5b2..99ccc2c15 100644
--- a/packages/web/src/appobj/SavedFileAppObject.svelte
+++ b/packages/web/src/appobj/SavedFileAppObject.svelte
@@ -104,8 +104,8 @@
export const extractKey = data => data.file;
export const createMatcher =
- ({ file }) =>
filter =>
+ ({ file }) =>
filterName(filter, file);
diff --git a/packages/web/src/appobj/SubDatabaseList.svelte b/packages/web/src/appobj/SubDatabaseList.svelte
index 6d8abaec0..27a2d6aa8 100644
--- a/packages/web/src/appobj/SubDatabaseList.svelte
+++ b/packages/web/src/appobj/SubDatabaseList.svelte
@@ -12,16 +12,18 @@
export let passProps;
export let isExpandedOnlyBySearch;
+ export let isExpandedBySearch;
$: databases = useDatabaseList({ conid: isExpandedOnlyBySearch ? null : data._id });
$: dbList = isExpandedOnlyBySearch ? getLocalStorage(`database_list_${data._id}`) || [] : $databases || [];
+
+ // .filter(x => filterName(filter, x.name, data.displayName, data.server))
filterName(filter, x.name, data.displayName, data.server)),
- x => x.sortOrder ?? x.name
- ).map(db => ({ ...db, connection: data }))}
+ list={_.sortBy(dbList, x => x.sortOrder ?? x.name).map(db => ({ ...db, connection: data }))}
module={databaseAppObject}
{passProps}
+ {filter}
+ {isExpandedBySearch}
/>
diff --git a/packages/web/src/appobj/SubProcedureLineList.svelte b/packages/web/src/appobj/SubProcedureLineList.svelte
new file mode 100644
index 000000000..2539c1929
--- /dev/null
+++ b/packages/web/src/appobj/SubProcedureLineList.svelte
@@ -0,0 +1,21 @@
+
+
+
+
+ ({
+ lineData,
+ }))}
+ module={procedureLineAppObject}
+ {filter}
+ {isExpandedBySearch}
+/>
diff --git a/packages/web/src/appobj/SubColumnParamList.svelte b/packages/web/src/appobj/SubTableColumnList.svelte
similarity index 81%
rename from packages/web/src/appobj/SubColumnParamList.svelte
rename to packages/web/src/appobj/SubTableColumnList.svelte
index f03f98822..32d06ef0b 100644
--- a/packages/web/src/appobj/SubColumnParamList.svelte
+++ b/packages/web/src/appobj/SubTableColumnList.svelte
@@ -5,6 +5,8 @@
import * as columnAppObject from './ColumnAppObject.svelte';
export let data;
+ export let filter;
+ export let isExpandedBySearch;
diff --git a/packages/web/src/datagrid/ColumnManager.svelte b/packages/web/src/datagrid/ColumnManager.svelte
index ee889e90e..76031e8ac 100644
--- a/packages/web/src/datagrid/ColumnManager.svelte
+++ b/packages/web/src/datagrid/ColumnManager.svelte
@@ -235,6 +235,7 @@
{columnIndex}
{allowChangeChangeSetStructure}
isSelected={selectedColumns.includes(column.uniqueName) || currentColumnUniqueName == column.uniqueName}
+ {filter}
on:click={() => {
if (domFocusField) domFocusField.focus();
selectedColumns = [column.uniqueName];
diff --git a/packages/web/src/datagrid/ColumnManagerRow.svelte b/packages/web/src/datagrid/ColumnManagerRow.svelte
index 864f6c60d..b5340d991 100644
--- a/packages/web/src/datagrid/ColumnManagerRow.svelte
+++ b/packages/web/src/datagrid/ColumnManagerRow.svelte
@@ -15,6 +15,7 @@
export let conid;
export let database;
export let isDynamicStructure;
+ export let filter = undefined;
export let tableInfo;
export let setTableInfo;
@@ -83,7 +84,7 @@
}}
/>
{/if}
-
+
{#if allowChangeChangeSetStructure && !isDynamicStructure}
diff --git a/packages/web/src/elements/ColumnLabel.svelte b/packages/web/src/elements/ColumnLabel.svelte
index 14d74c972..9e5443ba9 100644
--- a/packages/web/src/elements/ColumnLabel.svelte
+++ b/packages/web/src/elements/ColumnLabel.svelte
@@ -11,6 +11,7 @@
import { openDatabaseObjectDetail } from '../appobj/DatabaseObjectAppObject.svelte';
import FontIcon from '../icons/FontIcon.svelte';
+ import TokenizedFilteredText from '../widgets/TokenizedFilteredText.svelte';
import Link from './Link.svelte';
export let notNull = false;
@@ -25,6 +26,7 @@
export let conid = undefined;
export let database = undefined;
export let iconOverride = undefined;
+ export let filter = undefined;
$: icon = iconOverride || getColumnIcon($$props, forceIcon);
@@ -33,7 +35,7 @@
{#if icon}
{/if}
- {headerText || columnName}
+
{#if extInfo}
{extInfo}
{/if}
diff --git a/packages/web/src/icons/FontIcon.svelte b/packages/web/src/icons/FontIcon.svelte
index 1f8d6930f..06d4b956f 100644
--- a/packages/web/src/icons/FontIcon.svelte
+++ b/packages/web/src/icons/FontIcon.svelte
@@ -143,6 +143,7 @@
'icon parent-filter': 'mdi mdi-home-alert',
'icon parent-filter-outline': 'mdi mdi-home-alert-outline',
'icon download': 'mdi mdi-download',
+ 'icon text': 'mdi mdi-text',
'icon run': 'mdi mdi-play',
'icon chevron-down': 'mdi mdi-chevron-down',
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..dd60b6b53 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: false,
+ 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..7af64f669 100644
--- a/packages/web/src/widgets/SqlObjectList.svelte
+++ b/packages/web/src/widgets/SqlObjectList.svelte
@@ -27,7 +27,7 @@
import AppObjectList from '../appobj/AppObjectList.svelte';
import _ from 'lodash';
import * as databaseObjectAppObject from '../appobj/DatabaseObjectAppObject.svelte';
- import SubColumnParamList from '../appobj/SubColumnParamList.svelte';
+ import SubTableColumnList from '../appobj/SubTableColumnList.svelte';
import { chevronExpandIcon } from '../icons/expandIcons';
import ErrorInfo from '../elements/ErrorInfo.svelte';
import LoadingInfo from '../elements/LoadingInfo.svelte';
@@ -38,8 +38,10 @@
import { extractDbNameFromComposite, findEngineDriver } from 'dbgate-tools';
import {
currentDatabase,
+ databaseObjectAppObjectSearchSettings,
extensions,
focusedConnectionOrDatabase,
+ getDatabaseObjectAppObjectSearchSettings,
getSelectedDatabaseObjectAppObject,
selectedDatabaseObjectAppObject,
} from '../stores';
@@ -53,6 +55,7 @@
import { matchDatabaseObjectAppObject } from '../appobj/appObjectTools';
import FocusedConnectionInfoWidget from './FocusedConnectionInfoWidget.svelte';
import SubProcedureParamList from '../appobj/SubProcedureParamList.svelte';
+ import SubProcedureLineList from '../appobj/SubProcedureLineList.svelte';
export let conid;
export let database;
@@ -124,11 +127,32 @@
return res;
}
- $: flatFilteredList = objectList.filter(data => {
- const matcher = databaseObjectAppObject.createMatcher(data);
- if (matcher && !matcher(filter)) return false;
- return true;
- });
+ 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,
+ }));
+ }
+
+ $: matcher = databaseObjectAppObject.createMatcher(filter, $databaseObjectAppObjectSearchSettings);
+ $: flatFilteredList = objectList.filter(data => !matcher || matcher(data));
export function focus() {
domListHandler?.focusFirst();
@@ -184,7 +208,7 @@
{:else}
{
@@ -192,7 +216,12 @@
}}
/>
-
+ {#if filter}
+
+ {/if}
+ {#if !filter}
+
+ {/if}
@@ -240,10 +269,14 @@
.map(x => ({ ...x, conid, database }))}
module={databaseObjectAppObject}
groupFunc={data => getObjectTypeFieldLabel(data.objectTypeField, driver)}
- subItemsComponent={data =>
+ subItemsComponent={(data, { isExpandedBySearch }) =>
data.objectTypeField == 'procedures' || data.objectTypeField == 'functions'
- ? SubProcedureParamList
- : SubColumnParamList}
+ ? isExpandedBySearch
+ ? SubProcedureLineList
+ : SubProcedureParamList
+ : isExpandedBySearch && (data.objectTypeField == 'views' || data.objectTypeField == 'matviews')
+ ? SubProcedureLineList
+ : SubTableColumnList}
isExpandable={data =>
data.objectTypeField == 'tables' ||
data.objectTypeField == 'views' ||
@@ -256,6 +289,7 @@
showPinnedInsteadOfUnpin: true,
connection: $connection,
hideSchemaName: !!$appliedCurrentSchema,
+ searchSettings: $databaseObjectAppObjectSearchSettings,
}}
getIsExpanded={data =>
expandedObjects.includes(`${data.objectTypeField}||${data.schemaName}||${data.pureName}`)}
diff --git a/packages/web/src/widgets/TokenizedFilteredText.svelte b/packages/web/src/widgets/TokenizedFilteredText.svelte
new file mode 100644
index 000000000..3a7575a46
--- /dev/null
+++ b/packages/web/src/widgets/TokenizedFilteredText.svelte
@@ -0,0 +1,26 @@
+
+
+{#if tokenized}
+ {#each tokenized as token}
+ {#if token.isMatch}
+ {token.text}
+ {:else}
+ {token.text}
+ {/if}
+ {/each}
+{:else}
+ {text}
+{/if}
+
+