schema editing in dataset

This commit is contained in:
Jan Prochazka 2023-02-16 11:47:17 +01:00
parent 675ef6e593
commit cd1267b464
8 changed files with 184 additions and 33 deletions

View File

@ -9,7 +9,7 @@ import {
AllowIdentityInsert, AllowIdentityInsert,
Expression, Expression,
} from 'dbgate-sqltree'; } from 'dbgate-sqltree';
import type { NamedObjectInfo, DatabaseInfo } from 'dbgate-types'; import type { NamedObjectInfo, DatabaseInfo, TableInfo } from 'dbgate-types';
export interface ChangeSetItem { export interface ChangeSetItem {
pureName: string; pureName: string;
@ -21,7 +21,16 @@ export interface ChangeSetItem {
fields?: { [column: string]: string }; fields?: { [column: string]: string };
} }
export interface ChangeSetDataUpdateCommand {
type: 'renameField' | 'deleteField' | 'setField';
field: string;
value?: any;
}
export interface ChangeSet { export interface ChangeSet {
structure?: TableInfo;
dataUpdateCommands?: ChangeSetDataUpdateCommand[];
setColumnMode?: 'fixed' | 'variable';
inserts: ChangeSetItem[]; inserts: ChangeSetItem[];
updates: ChangeSetItem[]; updates: ChangeSetItem[];
deletes: ChangeSetItem[]; deletes: ChangeSetItem[];
@ -456,5 +465,12 @@ export function changeSetInsertDocuments(changeSet: ChangeSet, documents: any[],
export function changeSetContainsChanges(changeSet: ChangeSet) { export function changeSetContainsChanges(changeSet: ChangeSet) {
if (!changeSet) return false; if (!changeSet) return false;
return changeSet.deletes.length > 0 || changeSet.updates.length > 0 || changeSet.inserts.length > 0; return (
changeSet.deletes.length > 0 ||
changeSet.updates.length > 0 ||
changeSet.inserts.length > 0 ||
!!changeSet.structure ||
!!changeSet.setColumnMode ||
changeSet.dataUpdateCommands?.length > 0
);
} }

View File

@ -84,6 +84,7 @@ export abstract class GridDisplay {
return this.baseTable || this.baseView; return this.baseTable || this.baseView;
} }
changeSetKeyFields: string[] = null; changeSetKeyFields: string[] = null;
editableStructure: TableInfo = null;
sortable = false; sortable = false;
groupable = false; groupable = false;
filterable = false; filterable = false;

View File

@ -24,6 +24,7 @@ export class JslGridDisplay extends GridDisplay {
this.isDynamicStructure = isDynamicStructure; this.isDynamicStructure = isDynamicStructure;
this.filterTypeOverride = 'eval'; this.filterTypeOverride = 'eval';
this.editable = editable; this.editable = editable;
this.editableStructure = editable ? structure : null;
if (structure?.columns) { if (structure?.columns) {
this.columns = _.uniqBy( this.columns = _.uniqBy(

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import _, { indexOf, range } from 'lodash'; import _, { indexOf, range } from 'lodash';
import { GridDisplay } from 'dbgate-datalib'; import { ChangeSet, DisplayColumn, GridDisplay } from 'dbgate-datalib';
import { filterName } from 'dbgate-tools'; import { filterName } from 'dbgate-tools';
import CloseSearchButton from '../buttons/CloseSearchButton.svelte'; import CloseSearchButton from '../buttons/CloseSearchButton.svelte';
@ -14,6 +14,9 @@
import keycodes from '../utility/keycodes'; import keycodes from '../utility/keycodes';
import ColumnManagerRow from './ColumnManagerRow.svelte'; import ColumnManagerRow from './ColumnManagerRow.svelte';
import { copyTextToClipboard } from '../utility/clipboard'; import { copyTextToClipboard } from '../utility/clipboard';
import SelectField from '../forms/SelectField.svelte';
import ColumnEditorModal from '../tableeditor/ColumnEditorModal.svelte';
import { tick } from 'svelte';
export let managerSize; export let managerSize;
export let display: GridDisplay; export let display: GridDisplay;
@ -21,6 +24,9 @@
export let isDynamicStructure = false; export let isDynamicStructure = false;
export let conid; export let conid;
export let database; export let database;
export let allowChangeChangeSetStructure = false;
export let changeSetState: { value: ChangeSet } = null;
export let dispatchChangeSet = null;
let filter; let filter;
let domFocusField; let domFocusField;
@ -103,8 +109,44 @@
selectedColumns = value; selectedColumns = value;
if (value.length > 0) currentColumnUniqueName = value[0]; if (value.length > 0) currentColumnUniqueName = value[0];
} }
$: tableInfo = display?.editableStructure;
$: setTableInfo = updFunc => {
const structure = updFunc(display?.editableStructure);
dispatchChangeSet({
type: 'set',
value: {
...changeSetState?.value,
structure,
},
});
};
function handleAddColumn() {
showModal(ColumnEditorModal, {
setTableInfo,
tableInfo,
onAddNext: async () => {
await tick();
handleAddColumn();
},
});
}
</script> </script>
{#if allowChangeChangeSetStructure}
<div class="selectwrap">
<SelectField
isNative
class="colmode"
value="fixed"
options={[
{ label: 'Fixed columns (like SQL)', value: 'fixed' },
{ label: 'Variable columns (like MongoDB)', value: 'variable' },
]}
/>
</div>
{/if}
<SearchBoxWrapper> <SearchBoxWrapper>
<SearchInput placeholder="Search columns" bind:value={filter} /> <SearchInput placeholder="Search columns" bind:value={filter} />
<CloseSearchButton bind:filter /> <CloseSearchButton bind:filter />
@ -122,6 +164,9 @@
}}>Add</InlineButton }}>Add</InlineButton
> >
{/if} {/if}
{#if allowChangeChangeSetStructure}
<InlineButton on:click={handleAddColumn}>Add</InlineButton>
{/if}
<InlineButton on:click={() => display.hideAllColumns()}>Hide</InlineButton> <InlineButton on:click={() => display.hideAllColumns()}>Hide</InlineButton>
<InlineButton on:click={() => display.showAllColumns()}>Show</InlineButton> <InlineButton on:click={() => display.showAllColumns()}>Show</InlineButton>
</SearchBoxWrapper> </SearchBoxWrapper>
@ -139,12 +184,18 @@
/> />
{#each items as column (column.uniqueName)} {#each items as column (column.uniqueName)}
{@const columnIndex = items.indexOf(column)}
<ColumnManagerRow <ColumnManagerRow
{display} {display}
{column} {column}
{isJsonView} {isJsonView}
{conid} {conid}
{database} {database}
{tableInfo}
{setTableInfo}
columnInfo={tableInfo?.columns?.[columnIndex]}
{columnIndex}
{allowChangeChangeSetStructure}
isSelected={selectedColumns.includes(column.uniqueName) || currentColumnUniqueName == column.uniqueName} isSelected={selectedColumns.includes(column.uniqueName) || currentColumnUniqueName == column.uniqueName}
on:click={() => { on:click={() => {
if (domFocusField) domFocusField.focus(); if (domFocusField) domFocusField.focus();
@ -198,4 +249,14 @@
left: -1000px; left: -1000px;
top: -1000px; top: -1000px;
} }
.selectwrap :global(select) {
flex: 1;
padding: 3px 0px;
border: none;
}
.selectwrap {
border-bottom: 1px solid var(--theme-border);
}
</style> </style>

View File

@ -4,6 +4,8 @@
import FontIcon from '../icons/FontIcon.svelte'; import FontIcon from '../icons/FontIcon.svelte';
import ColumnLabel from '../elements/ColumnLabel.svelte'; import ColumnLabel from '../elements/ColumnLabel.svelte';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import { showModal } from '../modals/modalTools';
import ColumnEditorModal from '../tableeditor/ColumnEditorModal.svelte';
export let column; export let column;
export let display; export let display;
@ -12,6 +14,26 @@
export let conid; export let conid;
export let database; export let database;
export let tableInfo;
export let setTableInfo;
export let columnInfo = null;
export let columnIndex = -1;
export let allowChangeChangeSetStructure = false;
function handleEditColumn() {
showModal(ColumnEditorModal, { columnInfo, tableInfo, setTableInfo });
}
function exchange(array, i1, i2) {
const i1r = (i1 + array.length) % array.length;
const i2r = (i2 + array.length) % array.length;
const res = [...array];
[res[i1r], res[i2r]] = [res[i2r], res[i1r]];
return res;
}
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
</script> </script>
@ -29,32 +51,63 @@
on:mousemove on:mousemove
on:mouseup on:mouseup
> >
<span class="expandColumnIcon" style={`margin-right: ${5 + (column.uniquePath.length - 1) * 10}px`}> <div>
<FontIcon <span class="expandColumnIcon" style={`margin-right: ${5 + (column.uniquePath.length - 1) * 10}px`}>
icon={column.isExpandable ? plusExpandIcon(display.isExpandedColumn(column.uniqueName)) : 'icon invisible-box'} <FontIcon
on:click={() => display.toggleExpandedColumn(column.uniqueName)} icon={column.isExpandable ? plusExpandIcon(display.isExpandedColumn(column.uniqueName)) : 'icon invisible-box'}
/> on:click={() => display.toggleExpandedColumn(column.uniqueName)}
</span> />
{#if isJsonView} </span>
<FontIcon icon="img column" /> {#if isJsonView}
{:else} <FontIcon icon="img column" />
<input {:else}
type="checkbox" <input
checked={column.isChecked} type="checkbox"
on:click={e => { checked={column.isChecked}
e.stopPropagation(); on:click={e => {
}} e.stopPropagation();
on:mousedown={e => { }}
e.stopPropagation(); on:mousedown={e => {
}} e.stopPropagation();
on:change={() => { }}
const newValue = !column.isChecked; on:change={() => {
display.setColumnVisibility(column.uniquePath, newValue); const newValue = !column.isChecked;
dispatch('setvisibility', newValue); display.setColumnVisibility(column.uniquePath, newValue);
}} dispatch('setvisibility', newValue);
/> }}
/>
{/if}
<ColumnLabel {...column} showDataType {conid} {database} />
</div>
{#if allowChangeChangeSetStructure}
<div class="nowrap">
<span class="icon" on:click={handleEditColumn}>
<FontIcon icon="icon edit" />
</span>
<span
class="icon"
on:click={() =>
setTableInfo(info => ({ ...info, columns: info.columns.filter(x => x.pairingId != columnInfo?.pairingId) }))}
>
<FontIcon icon="icon delete" />
</span>
<span
class="icon"
on:click={() =>
setTableInfo(info => ({ ...info, columns: exchange(info.columns, columnIndex, columnIndex - 1) }))}
>
<FontIcon icon="icon arrow-up" />
</span>
<span
class="icon"
on:click={() =>
setTableInfo(info => ({ ...info, columns: exchange(info.columns, columnIndex, columnIndex + 1) }))}
>
<FontIcon icon="icon arrow-down" />
</span>
</div>
{/if} {/if}
<ColumnLabel {...column} showDataType {conid} {database} />
</div> </div>
<style> <style>
@ -63,6 +116,8 @@
margin-right: 5px; margin-right: 5px;
cursor: pointer; cursor: pointer;
white-space: nowrap; white-space: nowrap;
display: flex;
justify-content: space-between;
} }
.row:hover { .row:hover {
background: var(--theme-bg-hover); background: var(--theme-bg-hover);
@ -71,4 +126,13 @@
.row.isSelected { .row.isSelected {
background: var(--theme-bg-selected); background: var(--theme-bg-selected);
} }
.icon {
position: relative;
/* top: 5px;
padding: 5px; */
}
.icon:hover {
background-color: var(--theme-bg-3);
}
</style> </style>

View File

@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { createGridCache, createGridConfig, JslGridDisplay } from 'dbgate-datalib'; import { createGridCache, createGridConfig, JslGridDisplay } from 'dbgate-datalib';
import { generateTablePairingId } from 'dbgate-tools';
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
import JslFormView from '../formview/JslFormView.svelte'; import JslFormView from '../formview/JslFormView.svelte';
import { apiOff, apiOn, useApiCall } from '../utility/api'; import { apiOff, apiOn, useApiCall } from '../utility/api';
@ -16,6 +17,8 @@
export let changeSetStore = null; export let changeSetStore = null;
export let dispatchChangeSet = null; export let dispatchChangeSet = null;
export let allowChangeChangeSetStructure = false;
let loadedRows; let loadedRows;
let infoCounter = 0; let infoCounter = 0;
@ -44,7 +47,7 @@
$: display = new JslGridDisplay( $: display = new JslGridDisplay(
jslid, jslid,
$info, (allowChangeChangeSetStructure && changeSetState?.value?.structure) || generateTablePairingId($info),
$config, $config,
config.update, config.update,
$cache, $cache,
@ -71,5 +74,6 @@
{changeSetState} {changeSetState}
{changeSetStore} {changeSetStore}
{dispatchChangeSet} {dispatchChangeSet}
{allowChangeChangeSetStructure}
/> />
{/key} {/key}

View File

@ -13,10 +13,10 @@
import { editorAddColumn, editorDeleteColumn, editorModifyColumn, fillEditorColumnInfo } from 'dbgate-tools'; import { editorAddColumn, editorDeleteColumn, editorModifyColumn, fillEditorColumnInfo } from 'dbgate-tools';
export let columnInfo; export let columnInfo;
export let setTableInfo; export let setTableInfo = null;
export let tableInfo; export let tableInfo = null;
export let onAddNext; export let onAddNext;
export let driver; export let driver = null;
</script> </script>
<FormProvider initialValues={fillEditorColumnInfo(columnInfo || {}, tableInfo)}> <FormProvider initialValues={fillEditorColumnInfo(columnInfo || {}, tableInfo)}>
@ -31,7 +31,10 @@
<FormCheckboxField name="notNull" label="NOT NULL" /> <FormCheckboxField name="notNull" label="NOT NULL" />
<FormCheckboxField name="isPrimaryKey" label="Is Primary Key" /> <FormCheckboxField name="isPrimaryKey" label="Is Primary Key" />
<FormCheckboxField name="autoIncrement" label="Is Autoincrement" /> <FormCheckboxField name="autoIncrement" label="Is Autoincrement" />
<FormTextField name="defaultValue" label="Default value. Please use valid SQL expression, eg. 'Hello World' for string value, '' for empty string" /> <FormTextField
name="defaultValue"
label="Default value. Please use valid SQL expression, eg. 'Hello World' for string value, '' for empty string"
/>
<FormTextField name="computedExpression" label="Computed expression" /> <FormTextField name="computedExpression" label="Computed expression" />
{#if driver?.dialect?.columnProperties?.isUnsigned} {#if driver?.dialect?.columnProperties?.isUnsigned}
<FormCheckboxField name="isUnsigned" label="Unsigned" /> <FormCheckboxField name="isUnsigned" label="Unsigned" />

View File

@ -87,6 +87,7 @@
<JslDataGrid <JslDataGrid
jslid={jslid || `archive://${archiveFolder}/${archiveFile}`} jslid={jslid || `archive://${archiveFolder}/${archiveFile}`}
supportsReload supportsReload
allowChangeChangeSetStructure
changeSetState={$changeSetStore} changeSetState={$changeSetStore}
focusOnVisible focusOnVisible
{changeSetStore} {changeSetStore}