|
1 | 1 | <script lang="ts"> |
2 | 2 | import { type DBSchema } from '$lib/stores' |
3 | | - import { ChevronDownIcon, MoreVertical, Plus, Table2, Trash2Icon } from 'lucide-svelte' |
| 3 | + import { |
| 4 | + ChevronDownIcon, |
| 5 | + EditIcon, |
| 6 | + Loader2, |
| 7 | + MoreVertical, |
| 8 | + Plus, |
| 9 | + Table2, |
| 10 | + Trash2Icon |
| 11 | + } from 'lucide-svelte' |
4 | 12 | import { Pane, Splitpanes } from 'svelte-splitpanes' |
5 | 13 | import { ClearableInput, Drawer, DrawerContent } from './common' |
6 | 14 | import { sendUserToast } from '$lib/toast' |
7 | | - import { type ColumnDef } from './apps/components/display/dbtable/utils' |
| 15 | + import { type ColumnDef, type DbFeatures } from './apps/components/display/dbtable/utils' |
8 | 16 | import DBTable from './DBTable.svelte' |
9 | 17 | import type { IDbSchemaOps, IDbTableOps } from './dbOps' |
10 | 18 | import DropdownV2 from './DropdownV2.svelte' |
|
16 | 24 | import Select from './select/Select.svelte' |
17 | 25 | import { safeSelectItems } from './select/utils.svelte' |
18 | 26 | import type { Snippet } from 'svelte' |
| 27 | + import { |
| 28 | + dbSupportsTransactionalDdl, |
| 29 | + diffTableEditorValues |
| 30 | + } from './apps/components/display/dbtable/queries/alterTable' |
| 31 | + import { resource } from 'runed' |
| 32 | + import { capitalize, pluralize } from '$lib/utils' |
19 | 33 |
|
20 | 34 | /** Represents a selected table with its schema */ |
21 | 35 | export interface SelectedTable { |
|
42 | 56 | selectedTables?: SelectedTable[] |
43 | 57 | /** Tables that are already added and should show as disabled */ |
44 | 58 | disabledTables?: SelectedTable[] |
| 59 | + features?: DbFeatures |
45 | 60 | } |
46 | 61 | let { |
47 | 62 | dbType, |
|
58 | 73 | dbSelector, |
59 | 74 | multiSelectMode = false, |
60 | 75 | selectedTables = $bindable([]), |
61 | | - disabledTables = [] |
| 76 | + disabledTables = [], |
| 77 | + features |
62 | 78 | }: Props = $props() |
63 | 79 |
|
64 | 80 | // Helper to check if a table is selected in multi-select mode |
|
187 | 203 | | (ConfirmationModal['$$prop_def'] & { onConfirm: () => void }) |
188 | 204 | | undefined = $state() |
189 | 205 |
|
190 | | - let dbTableEditorState: { open: boolean } = $state({ open: false }) |
| 206 | + let dbTableEditorState: |
| 207 | + | { open: boolean; alterTableKey?: undefined } |
| 208 | + | { open: true; alterTableKey: string } = $state({ |
| 209 | + open: false |
| 210 | + }) |
| 211 | + let dbTableEditorAlterTableData = resource( |
| 212 | + () => dbTableEditorState.alterTableKey, |
| 213 | + async (table) => { |
| 214 | + if (!table) return |
| 215 | + let tableKey2 = |
| 216 | + dbSupportsSchemas && selected.schemaKey ? `${selected.schemaKey}.${table}` : table |
| 217 | + return await dbSchemaOps.onFetchTableEditorDefinition({ |
| 218 | + table: table, |
| 219 | + getColDefs: () => getColDefs(tableKey2) |
| 220 | + }) |
| 221 | + } |
| 222 | + ) |
| 223 | +
|
191 | 224 | let newSchemaDialogOpen = $state(false) |
192 | 225 | let newSchemaName = $state('') |
193 | 226 |
|
|
297 | 330 | <span class="truncate text-ellipsis grow text-left text-tertiary text-xs" |
298 | 331 | >{schemaKey}</span |
299 | 332 | > |
300 | | - <span class="text-2xs text-tertiary mr-2 group-hover:hidden">{schemaTables.length}</span> |
| 333 | + <span class="text-2xs text-tertiary mr-2 group-hover:hidden"> |
| 334 | + {schemaTables.length} |
| 335 | + </span> |
301 | 336 | <!-- Delete schema button (on hover) --> |
302 | 337 | <button |
303 | 338 | class="hidden group-hover:flex p-1 hover:bg-red-100 dark:hover:bg-red-900/30 rounded transition-colors mr-1" |
|
331 | 366 | {#each schemaTables as tableKey} |
332 | 367 | {@const isDisabled = isTableDisabled(schemaKey, tableKey)} |
333 | 368 | {@const isChecked = isTableSelected(schemaKey, tableKey) || isDisabled} |
334 | | - {@const isCurrentPreview = selected.schemaKey === schemaKey && selected.tableKey === tableKey} |
| 369 | + {@const isCurrentPreview = |
| 370 | + selected.schemaKey === schemaKey && selected.tableKey === tableKey} |
335 | 371 | <div |
336 | 372 | class={'group w-full text-sm font-normal flex gap-2 items-center h-8 cursor-pointer pl-7 pr-1 ' + |
337 | 373 | (isCurrentPreview ? 'bg-gray-500/25' : 'hover:bg-gray-500/10') + |
|
439 | 475 | askingForConfirmation = undefined |
440 | 476 | } |
441 | 477 | }) |
| 478 | + }, |
| 479 | + { |
| 480 | + displayName: 'Alter table', |
| 481 | + icon: EditIcon, |
| 482 | + action: () => { |
| 483 | + dbTableEditorState = { |
| 484 | + open: true, |
| 485 | + alterTableKey: tableKey |
| 486 | + } |
| 487 | + } |
442 | 488 | } |
443 | 489 | ]} |
444 | 490 | class="w-fit" |
|
490 | 536 | open={dbTableEditorState.open} |
491 | 537 | on:close={() => (dbTableEditorState = { open: false })} |
492 | 538 | > |
493 | | - <DrawerContent on:close={() => (dbTableEditorState = { open: false })} title="Create a new table"> |
494 | | - <DbTableEditor |
495 | | - {dbSchema} |
496 | | - currentSchema={selected.schemaKey} |
497 | | - onConfirm={async (values) => { |
498 | | - await dbSchemaOps.onCreate({ values, schema: selected.schemaKey }) |
499 | | - refresh?.() |
500 | | - dbTableEditorState = { open: false } |
501 | | - }} |
502 | | - {dbType} |
503 | | - previewSql={(values) => dbSchemaOps.previewCreateSql({ values, schema: selected.schemaKey })} |
504 | | - /> |
| 539 | + <DrawerContent |
| 540 | + on:close={() => (dbTableEditorState = { open: false })} |
| 541 | + title={dbTableEditorState.alterTableKey |
| 542 | + ? `Alter ${dbTableEditorState.alterTableKey}` |
| 543 | + : 'Create a new table'} |
| 544 | + > |
| 545 | + {#key dbTableEditorState.alterTableKey} |
| 546 | + {#if !dbTableEditorState.alterTableKey || dbTableEditorAlterTableData.current} |
| 547 | + <DbTableEditor |
| 548 | + {features} |
| 549 | + {dbSchema} |
| 550 | + currentSchema={selected.schemaKey} |
| 551 | + initialValues={dbTableEditorAlterTableData.current} |
| 552 | + onConfirm={async ({ values }) => { |
| 553 | + if (dbTableEditorState.alterTableKey && dbTableEditorAlterTableData.current) { |
| 554 | + let diff = diffTableEditorValues(dbTableEditorAlterTableData.current, values) |
| 555 | + await dbSchemaOps.onAlter({ schema: selected.schemaKey, values: diff }) |
| 556 | + } else { |
| 557 | + await dbSchemaOps.onCreate({ values, schema: selected.schemaKey }) |
| 558 | + } |
| 559 | + refresh?.() |
| 560 | + sendUserToast( |
| 561 | + dbTableEditorState.alterTableKey |
| 562 | + ? dbTableEditorState.alterTableKey + ' updated!' |
| 563 | + : values.name + ' created!' |
| 564 | + ) |
| 565 | + dbTableEditorState = { open: false } |
| 566 | + }} |
| 567 | + {dbType} |
| 568 | + computePreview={({ values }) => { |
| 569 | + if (dbTableEditorState.alterTableKey && dbTableEditorAlterTableData.current) { |
| 570 | + let diff = diffTableEditorValues(dbTableEditorAlterTableData.current, values) |
| 571 | + let queries = dbSchemaOps.previewAlterSql({ |
| 572 | + values: diff, |
| 573 | + schema: selected.schemaKey |
| 574 | + }) |
| 575 | + let alert = !dbSupportsTransactionalDdl(dbType) |
| 576 | + ? { |
| 577 | + title: capitalize(dbType) + ' does not support transactional DDL', |
| 578 | + body: 'Any of these statements failing may leave your database in an intermediate state.' |
| 579 | + } |
| 580 | + : undefined |
| 581 | + return { sql: queries.join('\n'), ...(alert ? { alert } : {}) } |
| 582 | + } else { |
| 583 | + return { sql: dbSchemaOps.previewCreateSql({ values, schema: selected.schemaKey }) } |
| 584 | + } |
| 585 | + }} |
| 586 | + computeBtnProps={({ values }) => { |
| 587 | + if (dbTableEditorState.alterTableKey && dbTableEditorAlterTableData.current) { |
| 588 | + let diff = diffTableEditorValues(dbTableEditorAlterTableData.current, values) |
| 589 | + let queries = dbSchemaOps.previewAlterSql({ |
| 590 | + values: diff, |
| 591 | + schema: selected.schemaKey |
| 592 | + }) |
| 593 | + if (!queries.length) { |
| 594 | + return { text: 'No changes detected', disabled: true } |
| 595 | + } |
| 596 | + return { |
| 597 | + text: `Alter table (${pluralize(queries.length, 'change')} detected)` |
| 598 | + } |
| 599 | + } else { |
| 600 | + return { text: 'Create table' } |
| 601 | + } |
| 602 | + }} |
| 603 | + /> |
| 604 | + {:else if dbTableEditorAlterTableData.loading} |
| 605 | + <Loader2 class="animate-spin" size={32} /> |
| 606 | + {:else} |
| 607 | + <p class="text-sm text-tertiary">Failed to load table definition.</p> |
| 608 | + <p>{dbTableEditorAlterTableData.error}</p> |
| 609 | + {/if} |
| 610 | + {/key} |
505 | 611 | </DrawerContent> |
506 | 612 | </Drawer> |
507 | 613 |
|
|
0 commit comments