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,
Expression,
} from 'dbgate-sqltree';
import type { NamedObjectInfo, DatabaseInfo } from 'dbgate-types';
import type { NamedObjectInfo, DatabaseInfo, TableInfo } from 'dbgate-types';
export interface ChangeSetItem {
pureName: string;
@ -21,7 +21,16 @@ export interface ChangeSetItem {
fields?: { [column: string]: string };
}
export interface ChangeSetDataUpdateCommand {
type: 'renameField' | 'deleteField' | 'setField';
field: string;
value?: any;
}
export interface ChangeSet {
structure?: TableInfo;
dataUpdateCommands?: ChangeSetDataUpdateCommand[];
setColumnMode?: 'fixed' | 'variable';
inserts: ChangeSetItem[];
updates: ChangeSetItem[];
deletes: ChangeSetItem[];
@ -456,5 +465,12 @@ export function changeSetInsertDocuments(changeSet: ChangeSet, documents: any[],
export function changeSetContainsChanges(changeSet: ChangeSet) {
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;
}
changeSetKeyFields: string[] = null;
editableStructure: TableInfo = null;
sortable = false;
groupable = false;
filterable = false;

View File

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

View File

@ -1,6 +1,6 @@
<script lang="ts">
import _, { indexOf, range } from 'lodash';
import { GridDisplay } from 'dbgate-datalib';
import { ChangeSet, DisplayColumn, GridDisplay } from 'dbgate-datalib';
import { filterName } from 'dbgate-tools';
import CloseSearchButton from '../buttons/CloseSearchButton.svelte';
@ -14,6 +14,9 @@
import keycodes from '../utility/keycodes';
import ColumnManagerRow from './ColumnManagerRow.svelte';
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 display: GridDisplay;
@ -21,6 +24,9 @@
export let isDynamicStructure = false;
export let conid;
export let database;
export let allowChangeChangeSetStructure = false;
export let changeSetState: { value: ChangeSet } = null;
export let dispatchChangeSet = null;
let filter;
let domFocusField;
@ -103,8 +109,44 @@
selectedColumns = value;
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>
{#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>
<SearchInput placeholder="Search columns" bind:value={filter} />
<CloseSearchButton bind:filter />
@ -122,6 +164,9 @@
}}>Add</InlineButton
>
{/if}
{#if allowChangeChangeSetStructure}
<InlineButton on:click={handleAddColumn}>Add</InlineButton>
{/if}
<InlineButton on:click={() => display.hideAllColumns()}>Hide</InlineButton>
<InlineButton on:click={() => display.showAllColumns()}>Show</InlineButton>
</SearchBoxWrapper>
@ -139,12 +184,18 @@
/>
{#each items as column (column.uniqueName)}
{@const columnIndex = items.indexOf(column)}
<ColumnManagerRow
{display}
{column}
{isJsonView}
{conid}
{database}
{tableInfo}
{setTableInfo}
columnInfo={tableInfo?.columns?.[columnIndex]}
{columnIndex}
{allowChangeChangeSetStructure}
isSelected={selectedColumns.includes(column.uniqueName) || currentColumnUniqueName == column.uniqueName}
on:click={() => {
if (domFocusField) domFocusField.focus();
@ -198,4 +249,14 @@
left: -1000px;
top: -1000px;
}
.selectwrap :global(select) {
flex: 1;
padding: 3px 0px;
border: none;
}
.selectwrap {
border-bottom: 1px solid var(--theme-border);
}
</style>

View File

@ -4,6 +4,8 @@
import FontIcon from '../icons/FontIcon.svelte';
import ColumnLabel from '../elements/ColumnLabel.svelte';
import { createEventDispatcher } from 'svelte';
import { showModal } from '../modals/modalTools';
import ColumnEditorModal from '../tableeditor/ColumnEditorModal.svelte';
export let column;
export let display;
@ -12,6 +14,26 @@
export let conid;
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();
</script>
@ -29,32 +51,63 @@
on:mousemove
on:mouseup
>
<span class="expandColumnIcon" style={`margin-right: ${5 + (column.uniquePath.length - 1) * 10}px`}>
<FontIcon
icon={column.isExpandable ? plusExpandIcon(display.isExpandedColumn(column.uniqueName)) : 'icon invisible-box'}
on:click={() => display.toggleExpandedColumn(column.uniqueName)}
/>
</span>
{#if isJsonView}
<FontIcon icon="img column" />
{:else}
<input
type="checkbox"
checked={column.isChecked}
on:click={e => {
e.stopPropagation();
}}
on:mousedown={e => {
e.stopPropagation();
}}
on:change={() => {
const newValue = !column.isChecked;
display.setColumnVisibility(column.uniquePath, newValue);
dispatch('setvisibility', newValue);
}}
/>
<div>
<span class="expandColumnIcon" style={`margin-right: ${5 + (column.uniquePath.length - 1) * 10}px`}>
<FontIcon
icon={column.isExpandable ? plusExpandIcon(display.isExpandedColumn(column.uniqueName)) : 'icon invisible-box'}
on:click={() => display.toggleExpandedColumn(column.uniqueName)}
/>
</span>
{#if isJsonView}
<FontIcon icon="img column" />
{:else}
<input
type="checkbox"
checked={column.isChecked}
on:click={e => {
e.stopPropagation();
}}
on:mousedown={e => {
e.stopPropagation();
}}
on:change={() => {
const newValue = !column.isChecked;
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}
<ColumnLabel {...column} showDataType {conid} {database} />
</div>
<style>
@ -63,6 +116,8 @@
margin-right: 5px;
cursor: pointer;
white-space: nowrap;
display: flex;
justify-content: space-between;
}
.row:hover {
background: var(--theme-bg-hover);
@ -71,4 +126,13 @@
.row.isSelected {
background: var(--theme-bg-selected);
}
.icon {
position: relative;
/* top: 5px;
padding: 5px; */
}
.icon:hover {
background-color: var(--theme-bg-3);
}
</style>

View File

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

View File

@ -13,10 +13,10 @@
import { editorAddColumn, editorDeleteColumn, editorModifyColumn, fillEditorColumnInfo } from 'dbgate-tools';
export let columnInfo;
export let setTableInfo;
export let tableInfo;
export let setTableInfo = null;
export let tableInfo = null;
export let onAddNext;
export let driver;
export let driver = null;
</script>
<FormProvider initialValues={fillEditorColumnInfo(columnInfo || {}, tableInfo)}>
@ -31,7 +31,10 @@
<FormCheckboxField name="notNull" label="NOT NULL" />
<FormCheckboxField name="isPrimaryKey" label="Is Primary Key" />
<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" />
{#if driver?.dialect?.columnProperties?.isUnsigned}
<FormCheckboxField name="isUnsigned" label="Unsigned" />

View File

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