mirror of
https://github.com/dbgate/dbgate
synced 2024-11-07 20:26:23 +00:00
define description modal
This commit is contained in:
parent
ec5b887e78
commit
1f821fc654
@ -72,7 +72,7 @@
|
||||
<FontIcon icon="img sort-desc" />
|
||||
</span>
|
||||
{/if}
|
||||
<DropDownButton menu={getMenu} />
|
||||
<DropDownButton menu={getMenu} narrow />
|
||||
<div class="horizontal-split-handle resizeHandleControl" use:splitterDrag={'clientX'} on:resizeSplitter />
|
||||
</div>
|
||||
|
||||
|
@ -12,6 +12,9 @@
|
||||
import keycodes from '../utility/keycodes';
|
||||
|
||||
import DropDownButton from '../elements/DropDownButton.svelte';
|
||||
import InlineButton from '../elements/InlineButton.svelte';
|
||||
import FontIcon from '../icons/FontIcon.svelte';
|
||||
import DictionaryLookupModal from '../modals/DictionaryLookupModal.svelte';
|
||||
|
||||
export let isReadOnly = false;
|
||||
export let filterType;
|
||||
@ -20,6 +23,9 @@
|
||||
export let showResizeSplitter = false;
|
||||
export let onFocusGrid;
|
||||
export let onGetReference;
|
||||
export let foreignKey = null;
|
||||
export let conid = null;
|
||||
export let database = null;
|
||||
|
||||
let value;
|
||||
let isError;
|
||||
@ -190,6 +196,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
function handleShowDictionary() {
|
||||
showModal(DictionaryLookupModal, {
|
||||
conid,
|
||||
database,
|
||||
pureName: foreignKey.refTableName,
|
||||
schemaName: foreignKey.refSchemaName,
|
||||
});
|
||||
}
|
||||
|
||||
$: value = filter;
|
||||
|
||||
$: {
|
||||
@ -226,9 +241,14 @@
|
||||
on:paste={handlePaste}
|
||||
class:isError
|
||||
class:isOk
|
||||
placeholder='Filter'
|
||||
placeholder="Filter"
|
||||
/>
|
||||
<DropDownButton icon="icon filter" menu={createMenu} />
|
||||
{#if foreignKey && conid && database}
|
||||
<InlineButton on:click={handleShowDictionary} narrow square>
|
||||
<FontIcon icon="icon dots-horizontal" />
|
||||
</InlineButton>
|
||||
{/if}
|
||||
<DropDownButton icon="icon filter" menu={createMenu} narrow />
|
||||
{#if showResizeSplitter}
|
||||
<div class="horizontal-split-handle resizeHandleControl" use:splitterDrag={'clientX'} on:resizeSplitter />
|
||||
{/if}
|
||||
|
@ -1234,6 +1234,9 @@
|
||||
>
|
||||
<DataFilterControl
|
||||
onGetReference={value => (domFilterControlsRef.get()[col.uniqueName] = value)}
|
||||
foreignKey={col.foreignKey}
|
||||
{conid}
|
||||
{database}
|
||||
filterType={col.filterType || getFilterType(col.dataType)}
|
||||
filter={display.getFilter(col.uniqueName)}
|
||||
setFilter={value => display.setFilter(col.uniqueName, value)}
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
export let icon = 'icon chevron-down';
|
||||
export let menu;
|
||||
export let narrow = false;
|
||||
let domButton;
|
||||
|
||||
function handleClick() {
|
||||
@ -17,6 +18,6 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<InlineButton square on:click={handleClick} bind:this={domButton}>
|
||||
<InlineButton square {narrow} on:click={handleClick} bind:this={domButton}>
|
||||
<FontIcon {icon} />
|
||||
</InlineButton>
|
||||
|
@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
export let disabled = false;
|
||||
export let square = false;
|
||||
export let narrow = false;
|
||||
|
||||
let domButton;
|
||||
|
||||
@ -9,7 +10,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="outer buttonLike" class:disabled class:square on:click bind:this={domButton}>
|
||||
<div class="outer buttonLike" class:disabled class:square class:narrow on:click bind:this={domButton}>
|
||||
<div class="inner">
|
||||
<slot />
|
||||
</div>
|
||||
@ -34,6 +35,10 @@
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.narrow {
|
||||
padding: 3px 1px;
|
||||
}
|
||||
|
||||
.outer.disabled {
|
||||
color: var(--theme-font-3);
|
||||
}
|
||||
|
@ -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',
|
||||
|
103
packages/web/src/modals/DefineDictionaryDescriptionModal.svelte
Normal file
103
packages/web/src/modals/DefineDictionaryDescriptionModal.svelte
Normal file
@ -0,0 +1,103 @@
|
||||
<script lang="ts">
|
||||
import FormProvider from '../forms/FormProvider.svelte';
|
||||
import FormSubmit from '../forms/FormSubmit.svelte';
|
||||
import FormStyledButton from '../elements/FormStyledButton.svelte';
|
||||
import ModalBase from './ModalBase.svelte';
|
||||
import { closeCurrentModal } from './modalTools';
|
||||
import { useTableInfo } from '../utility/metadataLoaders';
|
||||
import TableControl from '../elements/TableControl.svelte';
|
||||
import TextField from '../forms/TextField.svelte';
|
||||
import FormTextField from '../forms/FormTextField.svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
import FormProviderCore from '../forms/FormProviderCore.svelte';
|
||||
import {
|
||||
changeDelimitedColumnList,
|
||||
getDictionaryDescription,
|
||||
parseDelimitedColumnList,
|
||||
saveDictionaryDescription,
|
||||
} from '../utility/dictionaryDescriptionTools';
|
||||
import { includes } from 'lodash';
|
||||
import FormCheckboxField from '../forms/FormCheckboxField.svelte';
|
||||
|
||||
export let conid;
|
||||
export let database;
|
||||
export let pureName;
|
||||
export let schemaName;
|
||||
export let onConfirm;
|
||||
|
||||
$: tableInfo = useTableInfo({ conid, database, schemaName, pureName });
|
||||
|
||||
$: descriptionInfo = getDictionaryDescription($tableInfo, conid, database);
|
||||
|
||||
const values = writable({});
|
||||
|
||||
function initValues(descriptionInfo) {
|
||||
$values = {
|
||||
columns: descriptionInfo.expression,
|
||||
delimiter: descriptionInfo.delimiter,
|
||||
};
|
||||
}
|
||||
|
||||
$: if (descriptionInfo) initValues(descriptionInfo);
|
||||
</script>
|
||||
|
||||
<FormProviderCore {values}>
|
||||
<ModalBase {...$$restProps}>
|
||||
<svelte:fragment slot="header">Define description</svelte:fragment>
|
||||
|
||||
<div class="wrapper">
|
||||
<TableControl
|
||||
rows={$tableInfo?.columns || []}
|
||||
columns={[
|
||||
{ fieldName: 'checked', header: '', slot: 1 },
|
||||
{ fieldName: 'columnName', header: 'Column' },
|
||||
{ fieldName: 'dataType', header: 'Data type' },
|
||||
]}
|
||||
>
|
||||
<input
|
||||
let:row
|
||||
type="checkbox"
|
||||
slot="1"
|
||||
checked={parseDelimitedColumnList($values.columns).includes(row.columnName)}
|
||||
on:change={e => {
|
||||
$values = {
|
||||
...$values,
|
||||
columns: changeDelimitedColumnList($values.columns, row.columnName, e.target.checked),
|
||||
};
|
||||
}}
|
||||
/>
|
||||
</TableControl>
|
||||
</div>
|
||||
|
||||
<FormTextField name="columns" label="Show columns" />
|
||||
|
||||
<FormTextField name="delimiter" label="Delimiter" />
|
||||
|
||||
<FormCheckboxField name="useForAllDatabases" label="Use for all databases" />
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<FormSubmit
|
||||
value="OK"
|
||||
on:click={() => {
|
||||
closeCurrentModal();
|
||||
saveDictionaryDescription(
|
||||
$tableInfo,
|
||||
conid,
|
||||
database,
|
||||
$values.columns,
|
||||
$values.delimiter,
|
||||
$values.useForAllDatabases
|
||||
);
|
||||
onConfirm();
|
||||
}}
|
||||
/>
|
||||
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
|
||||
</svelte:fragment>
|
||||
</ModalBase>
|
||||
</FormProviderCore>
|
||||
|
||||
<style>
|
||||
.wrapper {
|
||||
margin: var(--dim-large-form-margin);
|
||||
}
|
||||
</style>
|
46
packages/web/src/modals/DictionaryLookupModal.svelte
Normal file
46
packages/web/src/modals/DictionaryLookupModal.svelte
Normal file
@ -0,0 +1,46 @@
|
||||
<script lang="ts">
|
||||
import FormProvider from '../forms/FormProvider.svelte';
|
||||
import FormSubmit from '../forms/FormSubmit.svelte';
|
||||
import FormStyledButton from '../elements/FormStyledButton.svelte';
|
||||
import ModalBase from './ModalBase.svelte';
|
||||
import { closeCurrentModal, showModal } from './modalTools';
|
||||
import DefineDictionaryDescriptionModal from './DefineDictionaryDescriptionModal.svelte';
|
||||
|
||||
export let onConfirm;
|
||||
export let conid;
|
||||
export let database;
|
||||
export let pureName;
|
||||
export let schemaName;
|
||||
|
||||
function defineDescription() {
|
||||
showModal(DefineDictionaryDescriptionModal, {
|
||||
conid,
|
||||
database,
|
||||
schemaName,
|
||||
pureName,
|
||||
onConfirm: () => reload(),
|
||||
});
|
||||
}
|
||||
|
||||
function reload() {}
|
||||
</script>
|
||||
|
||||
<FormProvider>
|
||||
<ModalBase {...$$restProps}>
|
||||
<svelte:fragment slot="header">Lookup from {pureName}</svelte:fragment>
|
||||
|
||||
{pureName}
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<FormSubmit
|
||||
value="OK"
|
||||
on:click={() => {
|
||||
closeCurrentModal();
|
||||
onConfirm();
|
||||
}}
|
||||
/>
|
||||
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
|
||||
<FormStyledButton type="button" value="Customize" on:click={defineDescription} />
|
||||
</svelte:fragment>
|
||||
</ModalBase>
|
||||
</FormProvider>
|
75
packages/web/src/utility/dictionaryDescriptionTools.ts
Normal file
75
packages/web/src/utility/dictionaryDescriptionTools.ts
Normal file
@ -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);
|
||||
}
|
||||
}
|
@ -10,5 +10,6 @@ export function openArchiveFolder() {
|
||||
properties: ['openDirectory'],
|
||||
});
|
||||
const linkedFolder = filePaths && filePaths[0];
|
||||
if (!linkedFolder) return;
|
||||
axiosInstance.post('archive/create-link', { linkedFolder });
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user