mirror of
https://github.com/dbgate/dbgate
synced 2024-11-07 20:26:23 +00:00
schema editing in dataset
This commit is contained in:
parent
675ef6e593
commit
cd1267b464
@ -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
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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(
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
|
@ -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" />
|
||||
|
@ -87,6 +87,7 @@
|
||||
<JslDataGrid
|
||||
jslid={jslid || `archive://${archiveFolder}/${archiveFile}`}
|
||||
supportsReload
|
||||
allowChangeChangeSetStructure
|
||||
changeSetState={$changeSetStore}
|
||||
focusOnVisible
|
||||
{changeSetStore}
|
||||
|
Loading…
Reference in New Issue
Block a user