@@ -34,6 +35,10 @@
display: flex;
}
+ .narrow {
+ padding: 3px 1px;
+ }
+
.outer.disabled {
color: var(--theme-font-3);
}
diff --git a/packages/web/src/icons/FontIcon.svelte b/packages/web/src/icons/FontIcon.svelte
index dcb024cf..2625fc69 100644
--- a/packages/web/src/icons/FontIcon.svelte
+++ b/packages/web/src/icons/FontIcon.svelte
@@ -66,6 +66,8 @@
'icon check-all': 'mdi mdi-check-all',
'icon checkbox-blank': 'mdi mdi-checkbox-blank-outline',
'icon checkbox-marked': 'mdi mdi-checkbox-marked-outline',
+ 'icon dots-horizontal': 'mdi mdi-dots-horizontal',
+ 'icon dots-vertical': 'mdi mdi-dots-vertical',
'icon run': 'mdi mdi-play',
'icon chevron-down': 'mdi mdi-chevron-down',
diff --git a/packages/web/src/modals/DefineDictionaryDescriptionModal.svelte b/packages/web/src/modals/DefineDictionaryDescriptionModal.svelte
new file mode 100644
index 00000000..12e3e9ef
--- /dev/null
+++ b/packages/web/src/modals/DefineDictionaryDescriptionModal.svelte
@@ -0,0 +1,103 @@
+
+
+
+
+ Define description
+
+
+
+
+
+
+
+
+
+
+ {
+ closeCurrentModal();
+ saveDictionaryDescription(
+ $tableInfo,
+ conid,
+ database,
+ $values.columns,
+ $values.delimiter,
+ $values.useForAllDatabases
+ );
+ onConfirm();
+ }}
+ />
+
+
+
+
+
+
diff --git a/packages/web/src/modals/DictionaryLookupModal.svelte b/packages/web/src/modals/DictionaryLookupModal.svelte
new file mode 100644
index 00000000..28b3b3dc
--- /dev/null
+++ b/packages/web/src/modals/DictionaryLookupModal.svelte
@@ -0,0 +1,46 @@
+
+
+
+
+ Lookup from {pureName}
+
+ {pureName}
+
+
+ {
+ closeCurrentModal();
+ onConfirm();
+ }}
+ />
+
+
+
+
+
diff --git a/packages/web/src/utility/dictionaryDescriptionTools.ts b/packages/web/src/utility/dictionaryDescriptionTools.ts
new file mode 100644
index 00000000..84b0e684
--- /dev/null
+++ b/packages/web/src/utility/dictionaryDescriptionTools.ts
@@ -0,0 +1,75 @@
+import { TableInfo } from 'dbgate-types';
+import _ from 'lodash';
+import { getLocalStorage, setLocalStorage, removeLocalStorage } from './storageCache';
+
+interface DictionaryDescription {
+ expression: string;
+ columns: string[];
+ delimiter: string;
+}
+
+function checkDescription(desc: DictionaryDescription, table: TableInfo) {
+ return desc.columns.length > 0 && desc.columns.every(x => table.columns.find(y => y.columnName == x));
+}
+
+export function getDictionaryDescription(table: TableInfo, conid: string, database: string): DictionaryDescription {
+ const keySpecific = `dictionary_spec_${table.schemaName}||${table.pureName}||${conid}||${database}`;
+ const keyCommon = `dictionary_spec_${table.schemaName}||${table.pureName}`;
+
+ const cachedSpecific = getLocalStorage(keySpecific);
+ const cachedCommon = getLocalStorage(keyCommon);
+
+ if (cachedSpecific && checkDescription(cachedSpecific, table)) return cachedSpecific;
+ if (cachedCommon && checkDescription(cachedCommon, table)) return cachedCommon;
+
+ const descColumn = table.columns.find(x => x?.dataType?.toLowerCase()?.includes('char'));
+ if (descColumn) {
+ return {
+ columns: [descColumn.columnName],
+ delimiter: null,
+ expression: descColumn.columnName,
+ };
+ }
+
+ return null;
+}
+
+export function parseDelimitedColumnList(columns) {
+ return _.compact((columns || '').split(',').map(x => x.trim()));
+}
+
+export function changeDelimitedColumnList(columns, columnName, isChecked) {
+ const parsed = parseDelimitedColumnList(columns);
+ const includes = parsed.includes(columnName);
+ if (includes == isChecked) return columns;
+ if (isChecked) parsed.push(columnName);
+ else _.remove(parsed, x => x == columnName);
+ return parsed.join(',');
+}
+
+export function saveDictionaryDescription(
+ table: TableInfo,
+ conid: string,
+ database: string,
+ expression: string,
+ delimiter: string,
+ useForAllDatabases: boolean
+) {
+ const keySpecific = `dictionary_spec_${table.schemaName}||${table.pureName}||${conid}||${database}`;
+ const keyCommon = `dictionary_spec_${table.schemaName}||${table.pureName}`;
+
+ removeLocalStorage(keySpecific);
+ if (useForAllDatabases) removeLocalStorage(keyCommon);
+
+ const description = {
+ columns: parseDelimitedColumnList(expression),
+ expression,
+ delimiter,
+ };
+
+ if (useForAllDatabases) {
+ setLocalStorage(keyCommon, description);
+ } else {
+ setLocalStorage(keySpecific, description);
+ }
+}
diff --git a/packages/web/src/utility/openArchiveFolder.ts b/packages/web/src/utility/openArchiveFolder.ts
index 7bc7cbe8..e2ae05fb 100644
--- a/packages/web/src/utility/openArchiveFolder.ts
+++ b/packages/web/src/utility/openArchiveFolder.ts
@@ -10,5 +10,6 @@ export function openArchiveFolder() {
properties: ['openDirectory'],
});
const linkedFolder = filePaths && filePaths[0];
+ if (!linkedFolder) return;
axiosInstance.post('archive/create-link', { linkedFolder });
}