diff --git a/frontend/src/lib/components/DBManager.svelte b/frontend/src/lib/components/DBManager.svelte index abd2d263ca..6e91c74933 100644 --- a/frontend/src/lib/components/DBManager.svelte +++ b/frontend/src/lib/components/DBManager.svelte @@ -31,16 +31,18 @@ diffTableEditorValues } from './apps/components/display/dbtable/queries/alterTable' import { resource } from 'runed' - import type { Snippet } from 'svelte' import { capitalize, onlyAlphaNumAndUnderscore, pluralize } from '$lib/utils' import type { DbFeatures } from './apps/components/display/dbtable/dbFeatures' import Star from './Star.svelte' import type { Asset, DataTableTables } from '$lib/gen' import type { DatatableRowAction } from './dbTypes' import TextInput from './text_input/TextInput.svelte' + import Checkbox from './common/checkbox/Checkbox.svelte' /** Represents a selected table with its schema */ export interface SelectedTable { + /** Absent when the tree has no data table level (a plain database). */ + datatable?: string schema: string table: string } @@ -61,9 +63,6 @@ /** Every data table with its schemas and tables. Present only when the manager * is on a data table — that is what puts a data-table level at the top of the * tree; otherwise the tree starts at schemas. */ - /** Multi-select pickers still choose their data table with a Select above the - * list; the tree below is the navigator for the manager's normal mode. */ - dbSelector?: Snippet<[]> datatableTree?: DataTableTables[] datatableTreeLoading?: boolean onSelectDatatable?: (datatable: string) => void @@ -93,7 +92,6 @@ initialTableKey, selectedSchemaKey = $bindable(undefined), selectedTableKey = $bindable(undefined), - dbSelector, datatableTree, datatableTreeLoading, onSelectDatatable, @@ -107,68 +105,60 @@ onImport }: Props = $props() - // Helper to check if a table is selected in multi-select mode - function isTableSelected(schema: string, table: string): boolean { - return selectedTables.some((t) => t.schema === schema && t.table === table) + const sameTable = (a: SelectedTable, b: SelectedTable) => + a.datatable === b.datatable && a.schema === b.schema && a.table === b.table + + function isTableSelected(t: SelectedTable): boolean { + return selectedTables.some((s) => sameTable(s, t)) } - // Helper to check if a table is disabled (already added) - function isTableDisabled(schema: string, table: string): boolean { - return disabledTables.some((t) => t.schema === schema && t.table === table) + /** Already added by the caller: shown ticked and locked. */ + function isTableDisabled(t: SelectedTable): boolean { + return disabledTables.some((s) => sameTable(s, t)) } - // Toggle table selection in multi-select mode - function toggleTableSelection(schema: string, table: string) { - if (isTableDisabled(schema, table)) return + function toggleTableSelection(t: SelectedTable) { + if (isTableDisabled(t)) return + selectedTables = isTableSelected(t) + ? selectedTables.filter((s) => !sameTable(s, t)) + : [...selectedTables, t] + } - const idx = selectedTables.findIndex((t) => t.schema === schema && t.table === table) - if (idx >= 0) { - selectedTables = selectedTables.filter((_, i) => i !== idx) - } else { - selectedTables = [...selectedTables, { schema, table }] + /** Every table under a node, as it is currently rendered — so a batch toggle + * acts on what the user can see, search filter included. */ + function tablesUnder(datatable: string | undefined, schemaKey?: string): SelectedTable[] { + return treeRoots + .filter((r) => r.datatable === datatable) + .flatMap((r) => + r.schemas + .filter((sc) => schemaKey === undefined || sc.schemaKey === schemaKey) + .flatMap((sc) => sc.tables.map((table) => ({ datatable, schema: sc.schemaKey, table }))) + ) + } + + /** Tri-state of a node's batch checkbox. Locked tables count as ticked, so a + * node whose tables were all already added reads as full rather than empty. */ + function batchState( + datatable: string | undefined, + schemaKey?: string + ): { checked: boolean; indeterminate: boolean; disabled: boolean } { + const tables = tablesUnder(datatable, schemaKey) + const selectable = tables.filter((t) => !isTableDisabled(t)) + const n = tables.filter((t) => isTableSelected(t) || isTableDisabled(t)).length + return { + checked: tables.length > 0 && n === tables.length, + indeterminate: n > 0 && n < tables.length, + disabled: selectable.length === 0 } } - // Get tables for a schema (filtered by search) - function getTablesForSchema(schema: string): string[] { - const tables = Object.keys(dbSchema.schema[schema] ?? {}) - if (search) { - return tables.filter((t) => t.toLowerCase().includes(search.toLowerCase())).sort() - } - return tables.sort() - } - - // Check if all selectable tables in a schema are selected - function isSchemaFullySelected(schema: string): boolean { - const tables = getTablesForSchema(schema) - if (tables.length === 0) return false - const selectableTables = tables.filter((t) => !isTableDisabled(schema, t)) - if (selectableTables.length === 0) return true // All disabled means "fully selected" - return selectableTables.every((t) => isTableSelected(schema, t)) - } - - // Check if some (but not all) tables in a schema are selected - function isSchemaPartiallySelected(schema: string): boolean { - const tables = getTablesForSchema(schema) - const selectableTables = tables.filter((t) => !isTableDisabled(schema, t)) - const selectedCount = selectableTables.filter((t) => isTableSelected(schema, t)).length - return selectedCount > 0 && selectedCount < selectableTables.length - } - - // Toggle all tables in a schema - function toggleSchemaSelection(schema: string) { - const tables = getTablesForSchema(schema) - const selectableTables = tables.filter((t) => !isTableDisabled(schema, t)) - - if (isSchemaFullySelected(schema)) { - // Deselect all selectable tables in this schema - selectedTables = selectedTables.filter((t) => t.schema !== schema) + function toggleBatch(datatable: string | undefined, schemaKey?: string) { + const selectable = tablesUnder(datatable, schemaKey).filter((t) => !isTableDisabled(t)) + if (selectable.every((t) => isTableSelected(t))) { + selectedTables = selectedTables.filter((s) => !selectable.some((t) => sameTable(s, t))) } else { - // Select all selectable tables in this schema - const newSelections = selectableTables - .filter((t) => !isTableSelected(schema, t)) - .map((t) => ({ schema, table: t })) - selectedTables = [...selectedTables, ...newSelections] + const missing = selectable.filter((t) => !isTableSelected(t)) + selectedTables = [...selectedTables, ...missing] } } @@ -400,196 +390,38 @@
- {#if multiSelectMode && dbSelector} - {@render dbSelector()} - {/if}
- {#if multiSelectMode} - - {#if dbSupportsSchemas} - - - {/if} - {#each schemaKeys as schemaKey} - {@const schemaTables = getTablesForSchema(schemaKey)} - {@const isFullySelected = isSchemaFullySelected(schemaKey)} - {@const isPartiallySelected = isSchemaPartiallySelected(schemaKey)} - {@const hasNoTables = schemaTables.length === 0} - -
{ - if (!hasNoTables) { - toggleSchemaSelection(schemaKey) - } - }} - onkeydown={(e) => { - if (e.key === 'Enter' || e.key === ' ') { - if (!hasNoTables) { - toggleSchemaSelection(schemaKey) - } - } - }} - > - {#if hasNoTables} - - - {:else} - - e.stopPropagation()} - onchange={() => toggleSchemaSelection(schemaKey)} - /> - - {/if} - {schemaKey} - - {schemaTables.length} - - - -
- - {#each schemaTables as tableKey} - {@const isDisabled = isTableDisabled(schemaKey, tableKey)} - {@const isChecked = isTableSelected(schemaKey, tableKey) || isDisabled} - {@const isCurrentPreview = - selected.schemaKey === schemaKey && selected.tableKey === tableKey} -
{ - selected.schemaKey = schemaKey - selected.tableKey = tableKey - toggleTableSelection(schemaKey, tableKey) - }} - onkeydown={(e) => { - if (e.key === 'Enter' || e.key === ' ') { - selected.schemaKey = schemaKey - selected.tableKey = tableKey - toggleTableSelection(schemaKey, tableKey) - } - }} - > - - e.stopPropagation()} - onchange={() => toggleTableSelection(schemaKey, tableKey)} - /> - - -

{tableKey}

- - -
- {/each} - - - {/each} - {:else} - - {#if datatableTreeLoading && (datatableTree?.length ?? 0) === 0} -
- - Loading... -
- {/if} - {#each treeRoots as root (root.datatable ?? '')} - {@const dtOpen = isExpanded(root.datatable)} - {#if root.datatable !== undefined} - + {/if} +
+ + {/if} + {#if dtOpen} + {#if root.error} +

{root.error}

{/if} - {#if dtOpen} - {#if root.error} -

{root.error}

- {/if} - {#each root.schemas as sc (sc.schemaKey)} - {@const schemaOpen = isExpanded(root.datatable, sc.schemaKey)} - {@const indent = root.datatable !== undefined ? 'pl-7' : 'pl-3'} - {#if dbSupportsSchemas} - - {/if} - {#if schemaOpen || !dbSupportsSchemas} - {@const tableIndent = dbSupportsSchemas - ? root.datatable !== undefined - ? 'pl-11' - : 'pl-7' - : root.datatable !== undefined - ? 'pl-7' - : 'pl-3'} - {#each sc.tables as tableKey (tableKey)} - {@const isSelected = - root.datatable === currentDatatable && - selected.schemaKey === sc.schemaKey && - selected.tableKey === tableKey} - - {/each} - {#if root.datatable === currentDatatable || root.datatable === undefined} - - {/if} - {/if} - {/each} - {#if dbSupportsSchemas && (root.datatable === currentDatatable || root.datatable === undefined) && search.trim() === ''} - {/if} + {#if schemaOpen || !dbSupportsSchemas} + {@const tableIndent = dbSupportsSchemas + ? root.datatable !== undefined + ? 'pl-11' + : 'pl-7' + : root.datatable !== undefined + ? 'pl-7' + : 'pl-3'} + {#each sc.tables as tableKey (tableKey)} + {@const entry = { + datatable: root.datatable, + schema: sc.schemaKey, + table: tableKey + }} + {@const isSelected = multiSelectMode + ? isTableSelected(entry) || isTableDisabled(entry) + : root.datatable === currentDatatable && + selected.schemaKey === sc.schemaKey && + selected.tableKey === tableKey} + + {/each} + {#if !multiSelectMode && (root.datatable === currentDatatable || root.datatable === undefined)} + + {/if} + {/if} + {/each} + {#if dbSupportsSchemas && !multiSelectMode && (root.datatable === currentDatatable || root.datatable === undefined) && search.trim() === ''} + {/if} - {/each} - {/if} + {/if} + {/each}
diff --git a/frontend/src/lib/components/DBManagerContent.svelte b/frontend/src/lib/components/DBManagerContent.svelte index 6cbdb8f39f..22ff273418 100644 --- a/frontend/src/lib/components/DBManagerContent.svelte +++ b/frontend/src/lib/components/DBManagerContent.svelte @@ -1,6 +1,5 @@