@adecore/database 0.21.0 → 0.21.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assign-ref.d.ts +2 -0
- package/dist/assign-ref.js +9 -0
- package/dist/client/client.js +14 -15
- package/dist/client/types.d.ts +1 -0
- package/dist/client/types.js +2 -0
- package/dist/connections/ConnectionForm.js +7 -2
- package/dist/connections/ConnectionManager.js +5 -5
- package/dist/connections/EngineIcon.js +0 -1
- package/dist/console/QueryConsole.js +12 -4
- package/dist/console/editing.js +2 -4
- package/dist/console/useConsoleHistory.js +3 -2
- package/dist/ddl/alter-mysql.js +2 -8
- package/dist/ddl/alter-sqlite.js +1 -1
- package/dist/ddl/column.js +5 -8
- package/dist/ddl/create.d.ts +1 -0
- package/dist/ddl/create.js +1 -1
- package/dist/ddl/diff.js +25 -23
- package/dist/designer/ColumnsEditor.js +8 -6
- package/dist/designer/ForeignKeysEditor.js +5 -1
- package/dist/designer/OptionsEditor.js +12 -3
- package/dist/designer/SqlPreview.d.ts +1 -5
- package/dist/designer/SqlPreview.js +1 -1
- package/dist/designer/TableDesigner.js +7 -6
- package/dist/explorer/DatabaseExplorer.js +12 -13
- package/dist/explorer/TableDialog.js +1 -1
- package/dist/explorer/tree.js +6 -16
- package/dist/grid/DataGrid.js +11 -14
- package/dist/grid/EnumPicker.js +4 -3
- package/dist/grid/GridHeaderCell.d.ts +1 -1
- package/dist/grid/GridHeaderCell.js +2 -2
- package/dist/grid/RecordView.js +2 -1
- package/dist/grid/ValueDock.js +2 -6
- package/dist/grid/aggregates.js +2 -3
- package/dist/grid/column-layout.js +3 -2
- package/dist/grid/copy-formats.js +2 -3
- package/dist/grid/display.d.ts +0 -1
- package/dist/grid/display.js +1 -3
- package/dist/grid/layout.js +6 -11
- package/dist/host/helper-path.js +8 -0
- package/dist/host/host.js +11 -16
- package/dist/host/validate.js +44 -43
- package/dist/sql.d.ts +2 -0
- package/dist/sql.js +5 -2
- package/dist/structure/StructureView.js +5 -6
- package/dist/table/TableView.js +19 -12
- package/dist/table/command-field.js +8 -12
- package/dist/table/grid-rows.js +2 -1
- package/dist/table/layout-store.js +5 -2
- package/dist/table/pending.js +11 -3
- package/dist/table/useTableTransfer.js +6 -5
- package/dist/testing/fake.js +7 -9
- package/dist/value/ValueEditor.js +1 -1
- package/dist/value/draft.js +1 -6
- package/dist/value/json.js +4 -4
- package/dist/workbench/DatabaseWorkbench.js +12 -5
- package/dist/workbench/tabs.js +3 -4
- package/package.json +6 -6
- package/src/assign-ref.ts +10 -0
- package/src/client/client.ts +17 -18
- package/src/client/types.ts +3 -0
- package/src/connections/ConnectionForm.tsx +62 -47
- package/src/connections/ConnectionManager.tsx +6 -5
- package/src/connections/EngineIcon.tsx +0 -2
- package/src/console/QueryConsole.tsx +52 -31
- package/src/console/editing.ts +2 -4
- package/src/console/useConsoleHistory.ts +5 -2
- package/src/ddl/alter-mysql.ts +3 -11
- package/src/ddl/alter-sqlite.ts +1 -1
- package/src/ddl/column.ts +5 -7
- package/src/ddl/create.ts +1 -1
- package/src/ddl/diff.ts +30 -25
- package/src/designer/ColumnsEditor.tsx +145 -125
- package/src/designer/ForeignKeysEditor.tsx +44 -27
- package/src/designer/OptionsEditor.tsx +95 -75
- package/src/designer/SqlPreview.tsx +2 -3
- package/src/designer/TableDesigner.tsx +7 -6
- package/src/explorer/DatabaseExplorer.tsx +26 -31
- package/src/explorer/TableDialog.tsx +1 -1
- package/src/explorer/tree.ts +8 -19
- package/src/grid/DataGrid.tsx +11 -14
- package/src/grid/EnumPicker.tsx +4 -3
- package/src/grid/GridHeaderCell.tsx +2 -3
- package/src/grid/RecordView.tsx +2 -1
- package/src/grid/ValueDock.tsx +2 -5
- package/src/grid/aggregates.ts +2 -4
- package/src/grid/column-layout.ts +3 -2
- package/src/grid/copy-formats.ts +2 -3
- package/src/grid/display.ts +1 -5
- package/src/grid/layout.ts +10 -11
- package/src/host/helper-path.ts +8 -8
- package/src/host/host.ts +11 -16
- package/src/host/validate.ts +45 -43
- package/src/protocol/messages.ts +0 -1
- package/src/sql.ts +7 -2
- package/src/structure/StructureView.tsx +7 -8
- package/src/table/CommandField.tsx +0 -1
- package/src/table/TableView.tsx +48 -34
- package/src/table/command-field.ts +9 -12
- package/src/table/grid-rows.ts +2 -7
- package/src/table/layout-store.ts +6 -3
- package/src/table/pending.ts +12 -7
- package/src/table/useTableTransfer.ts +7 -5
- package/src/testing/fake.ts +8 -12
- package/src/value/ValueEditor.tsx +1 -1
- package/src/value/draft.ts +1 -6
- package/src/value/json.ts +4 -4
- package/src/workbench/DatabaseWorkbench.tsx +14 -6
- package/src/workbench/DesignerTab.tsx +1 -1
- package/src/workbench/tabs.ts +5 -8
package/src/ddl/create.ts
CHANGED
|
@@ -43,7 +43,7 @@ export const mysqlOptionsSql = (options: TableOptions, against?: TableOptions):
|
|
|
43
43
|
|
|
44
44
|
const sqliteOptionsSql = (options: TableOptions): string[] => [...(options.strict ? ['STRICT'] : []), ...(options.withoutRowid ? ['WITHOUT ROWID'] : [])];
|
|
45
45
|
|
|
46
|
-
const inlineIndexSql = (dialect: Dialect, index: IndexDraft): string =>
|
|
46
|
+
export const inlineIndexSql = (dialect: Dialect, index: IndexDraft): string =>
|
|
47
47
|
[index.unique ? 'UNIQUE INDEX' : 'INDEX', ...(index.name === '' ? [] : [quote(dialect, index.name)]), `(${columnList(dialect, index.columns)})`].join(' ');
|
|
48
48
|
|
|
49
49
|
/* `CREATE [UNIQUE] INDEX name ON table (columns)`. */
|
package/src/ddl/diff.ts
CHANGED
|
@@ -73,13 +73,10 @@ const OPTION_FIELDS: readonly (keyof TableOptions)[] = ['engine', 'charset', 'co
|
|
|
73
73
|
const matches = (original: { readonly name: string; readonly key: string }, draft: { readonly originalName: string | null; readonly key: string }): boolean =>
|
|
74
74
|
draft.originalName !== null && (original.name === '' ? draft.key === original.key : draft.originalName === original.name);
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
export const diffOf = (dialect: Dialect, original: TableDraft, draft: TableDraft): TableDiff => {
|
|
78
|
-
const survivors = draft.columns.filter((column) => column.originalName !== null);
|
|
79
|
-
const renames = new Map(survivors.map((column) => [column.originalName!, column.name]));
|
|
80
|
-
const project = (names: readonly string[]): string[] => names.map((name) => renames.get(name) ?? name);
|
|
81
|
-
const originalOf = (name: string): ColumnDraft | undefined => original.columns.find((column) => column.name === name);
|
|
76
|
+
type Project = (names: readonly string[]) => string[];
|
|
82
77
|
|
|
78
|
+
const columnChanges = (dialect: Dialect, original: TableDraft, draft: TableDraft) => {
|
|
79
|
+
const survivors = draft.columns.filter((column) => column.originalName !== null);
|
|
83
80
|
const positions = survivors.map((column) => original.columns.findIndex((candidate) => candidate.name === column.originalName));
|
|
84
81
|
const staying = longestRun(positions);
|
|
85
82
|
let survivor = 0;
|
|
@@ -92,7 +89,7 @@ export const diffOf = (dialect: Dialect, original: TableDraft, draft: TableDraft
|
|
|
92
89
|
return;
|
|
93
90
|
}
|
|
94
91
|
const rank = survivor++;
|
|
95
|
-
const before =
|
|
92
|
+
const before = original.columns.find((candidate) => candidate.name === column.originalName);
|
|
96
93
|
if (before === undefined) {
|
|
97
94
|
return;
|
|
98
95
|
}
|
|
@@ -107,7 +104,10 @@ export const diffOf = (dialect: Dialect, original: TableDraft, draft: TableDraft
|
|
|
107
104
|
});
|
|
108
105
|
|
|
109
106
|
const droppedColumns = original.columns.filter((column) => !survivors.some((kept) => kept.originalName === column.name));
|
|
107
|
+
return { droppedColumns, keptColumns, addedColumns };
|
|
108
|
+
};
|
|
110
109
|
|
|
110
|
+
const indexChanges = (original: TableDraft, draft: TableDraft, project: Project) => {
|
|
111
111
|
const droppedIndexes: IndexDraft[] = [];
|
|
112
112
|
const addedIndexes = draft.indexes.filter((index) => index.originalName === null);
|
|
113
113
|
const renamedIndexes: RenamedIndex[] = [];
|
|
@@ -122,39 +122,44 @@ export const diffOf = (dialect: Dialect, original: TableDraft, draft: TableDraft
|
|
|
122
122
|
renamedIndexes.push({ from: before, to: after });
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
|
+
return { droppedIndexes, addedIndexes, renamedIndexes };
|
|
126
|
+
};
|
|
125
127
|
|
|
128
|
+
const sameForeignKey = (before: ForeignKeyDraft, after: ForeignKeyDraft, project: Project): boolean =>
|
|
129
|
+
before.name === after.name &&
|
|
130
|
+
sameList(project(before.columns), after.columns) &&
|
|
131
|
+
before.referencedSchema === after.referencedSchema &&
|
|
132
|
+
before.referencedTable === after.referencedTable &&
|
|
133
|
+
sameList(before.referencedColumns, after.referencedColumns) &&
|
|
134
|
+
before.onUpdate === after.onUpdate &&
|
|
135
|
+
before.onDelete === after.onDelete;
|
|
136
|
+
|
|
137
|
+
const foreignKeyChanges = (original: TableDraft, draft: TableDraft, project: Project) => {
|
|
126
138
|
const droppedForeignKeys: ForeignKeyDraft[] = [];
|
|
127
139
|
const addedForeignKeys = draft.foreignKeys.filter((key) => key.originalName === null);
|
|
128
140
|
for (const before of original.foreignKeys) {
|
|
129
141
|
const after = draft.foreignKeys.find((key) => matches(before, key));
|
|
130
|
-
|
|
131
|
-
after !== undefined &&
|
|
132
|
-
before.name === after.name &&
|
|
133
|
-
sameList(project(before.columns), after.columns) &&
|
|
134
|
-
before.referencedSchema === after.referencedSchema &&
|
|
135
|
-
before.referencedTable === after.referencedTable &&
|
|
136
|
-
sameList(before.referencedColumns, after.referencedColumns) &&
|
|
137
|
-
before.onUpdate === after.onUpdate &&
|
|
138
|
-
before.onDelete === after.onDelete;
|
|
139
|
-
if (!same) {
|
|
142
|
+
if (after === undefined || !sameForeignKey(before, after, project)) {
|
|
140
143
|
droppedForeignKeys.push(before);
|
|
141
144
|
if (after !== undefined) {
|
|
142
145
|
addedForeignKeys.push(after);
|
|
143
146
|
}
|
|
144
147
|
}
|
|
145
148
|
}
|
|
149
|
+
return { droppedForeignKeys, addedForeignKeys };
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/* What a draft changes in the table it came from, told apart by the names items kept. */
|
|
153
|
+
export const diffOf = (dialect: Dialect, original: TableDraft, draft: TableDraft): TableDiff => {
|
|
154
|
+
const renames = new Map(draft.columns.filter((column) => column.originalName !== null).map((column) => [column.originalName!, column.name]));
|
|
155
|
+
const project: Project = (names) => names.map((name) => renames.get(name) ?? name);
|
|
146
156
|
|
|
147
157
|
return {
|
|
148
158
|
renamed: original.name !== draft.name,
|
|
149
|
-
|
|
150
|
-
keptColumns,
|
|
151
|
-
addedColumns,
|
|
159
|
+
...columnChanges(dialect, original, draft),
|
|
152
160
|
primaryKeyChanged: !sameList(project(original.primaryKey), draft.primaryKey),
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
renamedIndexes,
|
|
156
|
-
droppedForeignKeys,
|
|
157
|
-
addedForeignKeys,
|
|
161
|
+
...indexChanges(original, draft, project),
|
|
162
|
+
...foreignKeyChanges(original, draft, project),
|
|
158
163
|
optionsChanged: OPTION_FIELDS.some((field) => original.options[field] !== draft.options[field])
|
|
159
164
|
};
|
|
160
165
|
};
|
|
@@ -4,7 +4,7 @@ import { ArrowDown, ArrowUp, KeyRound, Plus, Trash2 } from 'lucide-react';
|
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
import { Button, ButtonGroup, Checkbox, EmptyState, Icon, IconButton, Input, Pill } from '@adecore/ui';
|
|
6
6
|
import { formatNumber } from '@adecore/ui/format';
|
|
7
|
-
import { typeSuggestionsOf } from '../ddl/index.ts';
|
|
7
|
+
import { typeSuggestionsOf, type ColumnDraft } from '../ddl/index.ts';
|
|
8
8
|
import type { EditorProps } from './editor-props.ts';
|
|
9
9
|
import { addColumn, moveColumn, patchColumn, removeColumn, togglePrimaryKey } from './edit.ts';
|
|
10
10
|
|
|
@@ -65,130 +65,18 @@ export function ColumnsEditor({ draft, dialect, disabled, onChange, className, r
|
|
|
65
65
|
</tr>
|
|
66
66
|
</thead>
|
|
67
67
|
<tbody>
|
|
68
|
-
{draft.columns.map((column, at) =>
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
disabled={disabled || at === 0}
|
|
81
|
-
onClick={() => onChange(moveColumn(draft, column.key, -1))}
|
|
82
|
-
/>
|
|
83
|
-
<IconButton
|
|
84
|
-
icon={ArrowDown}
|
|
85
|
-
size="xs"
|
|
86
|
-
label={t('designer.columns.moveDown')}
|
|
87
|
-
disabled={disabled || at === draft.columns.length - 1}
|
|
88
|
-
onClick={() => onChange(moveColumn(draft, column.key, 1))}
|
|
89
|
-
/>
|
|
90
|
-
</ButtonGroup>
|
|
91
|
-
</td>
|
|
92
|
-
<td className={clsx(TD, 'min-w-40')}>
|
|
93
|
-
<Input
|
|
94
|
-
size="sm"
|
|
95
|
-
className="w-full"
|
|
96
|
-
aria-label={t('designer.columns.name')}
|
|
97
|
-
spellCheck={false}
|
|
98
|
-
value={column.name}
|
|
99
|
-
disabled={locked}
|
|
100
|
-
onChange={(event) => patch({ name: event.target.value })}
|
|
101
|
-
/>
|
|
102
|
-
</td>
|
|
103
|
-
<td className={clsx(TD, 'min-w-36')}>
|
|
104
|
-
<Input
|
|
105
|
-
size="sm"
|
|
106
|
-
mono
|
|
107
|
-
className="w-full"
|
|
108
|
-
aria-label={t('designer.columns.type')}
|
|
109
|
-
list={listId}
|
|
110
|
-
spellCheck={false}
|
|
111
|
-
value={column.type}
|
|
112
|
-
disabled={locked}
|
|
113
|
-
onChange={(event) => patch({ type: event.target.value })}
|
|
114
|
-
/>
|
|
115
|
-
</td>
|
|
116
|
-
<td className={TD}>
|
|
117
|
-
<Checkbox
|
|
118
|
-
className="mx-auto"
|
|
119
|
-
label={t('designer.columns.notNullFor', { name: column.name })}
|
|
120
|
-
checked={!column.nullable}
|
|
121
|
-
disabled={locked}
|
|
122
|
-
onCheckedChange={(checked) => patch({ nullable: !checked })}
|
|
123
|
-
/>
|
|
124
|
-
</td>
|
|
125
|
-
<td className={clsx(TD, 'min-w-36')}>
|
|
126
|
-
{column.generated ? (
|
|
127
|
-
<Pill mono>{column.generatedClause ?? t('designer.columns.generated')}</Pill>
|
|
128
|
-
) : (
|
|
129
|
-
<Input
|
|
130
|
-
size="sm"
|
|
131
|
-
mono
|
|
132
|
-
className="w-full"
|
|
133
|
-
aria-label={t('designer.columns.default')}
|
|
134
|
-
placeholder={t('designer.columns.noDefault')}
|
|
135
|
-
spellCheck={false}
|
|
136
|
-
value={column.defaultValue ?? ''}
|
|
137
|
-
disabled={disabled}
|
|
138
|
-
onChange={(event) => patch({ defaultValue: event.target.value === '' ? null : event.target.value })}
|
|
139
|
-
/>
|
|
140
|
-
)}
|
|
141
|
-
</td>
|
|
142
|
-
{mysql && (
|
|
143
|
-
<td className={TD}>
|
|
144
|
-
<Checkbox
|
|
145
|
-
className="mx-auto"
|
|
146
|
-
label={t('designer.columns.autoIncrementFor', { name: column.name })}
|
|
147
|
-
checked={column.autoIncrement}
|
|
148
|
-
disabled={locked}
|
|
149
|
-
onCheckedChange={(checked) => patch({ autoIncrement: checked })}
|
|
150
|
-
/>
|
|
151
|
-
</td>
|
|
152
|
-
)}
|
|
153
|
-
<td className={clsx(TD, 'whitespace-nowrap')}>
|
|
154
|
-
<span className="flex items-center gap-1">
|
|
155
|
-
<IconButton
|
|
156
|
-
icon={KeyRound}
|
|
157
|
-
size="xs"
|
|
158
|
-
label={keyAt < 0 ? t('designer.columns.addToKey') : t('designer.columns.removeFromKey')}
|
|
159
|
-
aria-pressed={keyAt >= 0}
|
|
160
|
-
disabled={locked}
|
|
161
|
-
onClick={() => onChange(togglePrimaryKey(draft, column.key))}
|
|
162
|
-
/>
|
|
163
|
-
{keyAt >= 0 && draft.primaryKey.length > 1 && (
|
|
164
|
-
<span className="text-xs text-text-muted">{formatNumber(keyAt + 1)}</span>
|
|
165
|
-
)}
|
|
166
|
-
</span>
|
|
167
|
-
</td>
|
|
168
|
-
{mysql && (
|
|
169
|
-
<td className={clsx(TD, 'min-w-40')}>
|
|
170
|
-
<Input
|
|
171
|
-
size="sm"
|
|
172
|
-
className="w-full"
|
|
173
|
-
aria-label={t('designer.columns.comment')}
|
|
174
|
-
value={column.comment}
|
|
175
|
-
disabled={locked}
|
|
176
|
-
onChange={(event) => patch({ comment: event.target.value })}
|
|
177
|
-
/>
|
|
178
|
-
</td>
|
|
179
|
-
)}
|
|
180
|
-
<td className={TD}>
|
|
181
|
-
<IconButton
|
|
182
|
-
icon={Trash2}
|
|
183
|
-
size="xs"
|
|
184
|
-
label={t('designer.columns.remove')}
|
|
185
|
-
disabled={disabled}
|
|
186
|
-
onClick={() => onChange(removeColumn(draft, column.key))}
|
|
187
|
-
/>
|
|
188
|
-
</td>
|
|
189
|
-
</tr>
|
|
190
|
-
);
|
|
191
|
-
})}
|
|
68
|
+
{draft.columns.map((column, at) => (
|
|
69
|
+
<ColumnRow
|
|
70
|
+
key={column.key}
|
|
71
|
+
draft={draft}
|
|
72
|
+
column={column}
|
|
73
|
+
at={at}
|
|
74
|
+
mysql={mysql}
|
|
75
|
+
listId={listId}
|
|
76
|
+
disabled={disabled}
|
|
77
|
+
onChange={onChange}
|
|
78
|
+
/>
|
|
79
|
+
))}
|
|
192
80
|
</tbody>
|
|
193
81
|
</table>
|
|
194
82
|
</div>
|
|
@@ -202,3 +90,135 @@ export function ColumnsEditor({ draft, dialect, disabled, onChange, className, r
|
|
|
202
90
|
</div>
|
|
203
91
|
);
|
|
204
92
|
}
|
|
93
|
+
|
|
94
|
+
interface ColumnRowProps extends Pick<EditorProps, 'draft' | 'disabled' | 'onChange'> {
|
|
95
|
+
column: ColumnDraft;
|
|
96
|
+
at: number;
|
|
97
|
+
mysql: boolean;
|
|
98
|
+
/* The id of the datalist with the suggested types. */
|
|
99
|
+
listId: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function ColumnRow({ draft, column, at, mysql, listId, disabled, onChange }: ColumnRowProps) {
|
|
103
|
+
const { t } = useTranslation('database');
|
|
104
|
+
const keyAt = draft.primaryKey.indexOf(column.name);
|
|
105
|
+
const locked = disabled || column.generated;
|
|
106
|
+
const patch = (change: Partial<ColumnDraft>): void => onChange(patchColumn(draft, column.key, change));
|
|
107
|
+
return (
|
|
108
|
+
<tr>
|
|
109
|
+
<td className={clsx(TD, 'whitespace-nowrap')}>
|
|
110
|
+
<ButtonGroup>
|
|
111
|
+
<IconButton
|
|
112
|
+
icon={ArrowUp}
|
|
113
|
+
size="xs"
|
|
114
|
+
label={t('designer.columns.moveUp')}
|
|
115
|
+
disabled={disabled || at === 0}
|
|
116
|
+
onClick={() => onChange(moveColumn(draft, column.key, -1))}
|
|
117
|
+
/>
|
|
118
|
+
<IconButton
|
|
119
|
+
icon={ArrowDown}
|
|
120
|
+
size="xs"
|
|
121
|
+
label={t('designer.columns.moveDown')}
|
|
122
|
+
disabled={disabled || at === draft.columns.length - 1}
|
|
123
|
+
onClick={() => onChange(moveColumn(draft, column.key, 1))}
|
|
124
|
+
/>
|
|
125
|
+
</ButtonGroup>
|
|
126
|
+
</td>
|
|
127
|
+
<td className={clsx(TD, 'min-w-40')}>
|
|
128
|
+
<Input
|
|
129
|
+
size="sm"
|
|
130
|
+
className="w-full"
|
|
131
|
+
aria-label={t('designer.columns.name')}
|
|
132
|
+
spellCheck={false}
|
|
133
|
+
value={column.name}
|
|
134
|
+
disabled={locked}
|
|
135
|
+
onChange={(event) => patch({ name: event.target.value })}
|
|
136
|
+
/>
|
|
137
|
+
</td>
|
|
138
|
+
<td className={clsx(TD, 'min-w-36')}>
|
|
139
|
+
<Input
|
|
140
|
+
size="sm"
|
|
141
|
+
mono
|
|
142
|
+
className="w-full"
|
|
143
|
+
aria-label={t('designer.columns.type')}
|
|
144
|
+
list={listId}
|
|
145
|
+
spellCheck={false}
|
|
146
|
+
value={column.type}
|
|
147
|
+
disabled={locked}
|
|
148
|
+
onChange={(event) => patch({ type: event.target.value })}
|
|
149
|
+
/>
|
|
150
|
+
</td>
|
|
151
|
+
<td className={TD}>
|
|
152
|
+
<Checkbox
|
|
153
|
+
className="mx-auto"
|
|
154
|
+
label={t('designer.columns.notNullFor', { name: column.name })}
|
|
155
|
+
checked={!column.nullable}
|
|
156
|
+
disabled={locked}
|
|
157
|
+
onCheckedChange={(checked) => patch({ nullable: !checked })}
|
|
158
|
+
/>
|
|
159
|
+
</td>
|
|
160
|
+
<td className={clsx(TD, 'min-w-36')}>
|
|
161
|
+
{column.generated ? (
|
|
162
|
+
<Pill mono>{column.generatedClause ?? t('designer.columns.generated')}</Pill>
|
|
163
|
+
) : (
|
|
164
|
+
<Input
|
|
165
|
+
size="sm"
|
|
166
|
+
mono
|
|
167
|
+
className="w-full"
|
|
168
|
+
aria-label={t('designer.columns.default')}
|
|
169
|
+
placeholder={t('designer.columns.noDefault')}
|
|
170
|
+
spellCheck={false}
|
|
171
|
+
value={column.defaultValue ?? ''}
|
|
172
|
+
disabled={disabled}
|
|
173
|
+
onChange={(event) => patch({ defaultValue: event.target.value === '' ? null : event.target.value })}
|
|
174
|
+
/>
|
|
175
|
+
)}
|
|
176
|
+
</td>
|
|
177
|
+
{mysql && (
|
|
178
|
+
<td className={TD}>
|
|
179
|
+
<Checkbox
|
|
180
|
+
className="mx-auto"
|
|
181
|
+
label={t('designer.columns.autoIncrementFor', { name: column.name })}
|
|
182
|
+
checked={column.autoIncrement}
|
|
183
|
+
disabled={locked}
|
|
184
|
+
onCheckedChange={(checked) => patch({ autoIncrement: checked })}
|
|
185
|
+
/>
|
|
186
|
+
</td>
|
|
187
|
+
)}
|
|
188
|
+
<td className={clsx(TD, 'whitespace-nowrap')}>
|
|
189
|
+
<span className="flex items-center gap-1">
|
|
190
|
+
<IconButton
|
|
191
|
+
icon={KeyRound}
|
|
192
|
+
size="xs"
|
|
193
|
+
label={keyAt < 0 ? t('designer.columns.addToKey') : t('designer.columns.removeFromKey')}
|
|
194
|
+
aria-pressed={keyAt >= 0}
|
|
195
|
+
disabled={locked}
|
|
196
|
+
onClick={() => onChange(togglePrimaryKey(draft, column.key))}
|
|
197
|
+
/>
|
|
198
|
+
{keyAt >= 0 && draft.primaryKey.length > 1 && <span className="text-xs text-text-muted">{formatNumber(keyAt + 1)}</span>}
|
|
199
|
+
</span>
|
|
200
|
+
</td>
|
|
201
|
+
{mysql && (
|
|
202
|
+
<td className={clsx(TD, 'min-w-40')}>
|
|
203
|
+
<Input
|
|
204
|
+
size="sm"
|
|
205
|
+
className="w-full"
|
|
206
|
+
aria-label={t('designer.columns.comment')}
|
|
207
|
+
value={column.comment}
|
|
208
|
+
disabled={locked}
|
|
209
|
+
onChange={(event) => patch({ comment: event.target.value })}
|
|
210
|
+
/>
|
|
211
|
+
</td>
|
|
212
|
+
)}
|
|
213
|
+
<td className={TD}>
|
|
214
|
+
<IconButton
|
|
215
|
+
icon={Trash2}
|
|
216
|
+
size="xs"
|
|
217
|
+
label={t('designer.columns.remove')}
|
|
218
|
+
disabled={disabled}
|
|
219
|
+
onClick={() => onChange(removeColumn(draft, column.key))}
|
|
220
|
+
/>
|
|
221
|
+
</td>
|
|
222
|
+
</tr>
|
|
223
|
+
);
|
|
224
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import clsx from 'clsx';
|
|
2
2
|
import { Plus, Trash2 } from 'lucide-react';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
|
-
import { Button, EmptyState, Field, Icon, IconButton, Input, Select, Spinner } from '@adecore/ui';
|
|
4
|
+
import { Button, EmptyState, Field, Icon, IconButton, Input, Select, Spinner, type SelectItem } from '@adecore/ui';
|
|
5
5
|
import { FOREIGN_KEY_ACTIONS } from '../ddl/index.ts';
|
|
6
6
|
import type { EditorProps } from './editor-props.ts';
|
|
7
7
|
import { NamePicker } from './NamePicker.tsx';
|
|
@@ -58,32 +58,20 @@ export function ForeignKeysEditor({ draft, schema, tables, referenceColumns, dis
|
|
|
58
58
|
onValueChange={(table) => onChange(setReferencedTable(draft, foreignKey.key, schema, table))}
|
|
59
59
|
/>
|
|
60
60
|
</Field>
|
|
61
|
-
<
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
<Select
|
|
76
|
-
size="sm"
|
|
77
|
-
className="w-full"
|
|
78
|
-
label={t('designer.foreignKeys.onDelete')}
|
|
79
|
-
value={foreignKey.onDelete ?? DEFAULT_ACTION}
|
|
80
|
-
disabled={disabled}
|
|
81
|
-
items={actions}
|
|
82
|
-
onValueChange={(action) =>
|
|
83
|
-
onChange(patchForeignKey(draft, foreignKey.key, { onDelete: action === DEFAULT_ACTION ? null : action }))
|
|
84
|
-
}
|
|
85
|
-
/>
|
|
86
|
-
</Field>
|
|
61
|
+
<ActionField
|
|
62
|
+
label={t('designer.foreignKeys.onUpdate')}
|
|
63
|
+
value={foreignKey.onUpdate}
|
|
64
|
+
items={actions}
|
|
65
|
+
disabled={disabled}
|
|
66
|
+
onValueChange={(action) => onChange(patchForeignKey(draft, foreignKey.key, { onUpdate: action }))}
|
|
67
|
+
/>
|
|
68
|
+
<ActionField
|
|
69
|
+
label={t('designer.foreignKeys.onDelete')}
|
|
70
|
+
value={foreignKey.onDelete}
|
|
71
|
+
items={actions}
|
|
72
|
+
disabled={disabled}
|
|
73
|
+
onValueChange={(action) => onChange(patchForeignKey(draft, foreignKey.key, { onDelete: action }))}
|
|
74
|
+
/>
|
|
87
75
|
<IconButton
|
|
88
76
|
icon={Trash2}
|
|
89
77
|
size="xs"
|
|
@@ -130,3 +118,32 @@ export function ForeignKeysEditor({ draft, schema, tables, referenceColumns, dis
|
|
|
130
118
|
</div>
|
|
131
119
|
);
|
|
132
120
|
}
|
|
121
|
+
|
|
122
|
+
/* What a key does on an update or a delete; `null` is the engine's default. */
|
|
123
|
+
function ActionField({
|
|
124
|
+
label,
|
|
125
|
+
value,
|
|
126
|
+
items,
|
|
127
|
+
disabled,
|
|
128
|
+
onValueChange
|
|
129
|
+
}: {
|
|
130
|
+
label: string;
|
|
131
|
+
value: string | null;
|
|
132
|
+
items: SelectItem<string>[];
|
|
133
|
+
disabled: boolean;
|
|
134
|
+
onValueChange(action: string | null): void;
|
|
135
|
+
}) {
|
|
136
|
+
return (
|
|
137
|
+
<Field label={label} group className="w-40">
|
|
138
|
+
<Select
|
|
139
|
+
size="sm"
|
|
140
|
+
className="w-full"
|
|
141
|
+
label={label}
|
|
142
|
+
value={value ?? DEFAULT_ACTION}
|
|
143
|
+
disabled={disabled}
|
|
144
|
+
items={items}
|
|
145
|
+
onValueChange={(action) => onValueChange(action === DEFAULT_ACTION ? null : action)}
|
|
146
|
+
/>
|
|
147
|
+
</Field>
|
|
148
|
+
);
|
|
149
|
+
}
|