feat(datatables): create tables and schemas from any data table in the tree

The New table / New schema rows now show under every data table, not only
the one the manager is pointed at. Creating outside it switches to that data
table first, and the request rides through the parent because the switch
re-mounts the manager.

Tree rows also read uniformly now — the background alone marks the current
one — with the create rows set apart in secondary.
This commit is contained in:
Diego Imbert
2026-08-25 19:47:50 +02:00
parent 48294239e5
commit 77965f1458
4 changed files with 72 additions and 44 deletions
+55 -32
View File
@@ -47,6 +47,10 @@
table: string
}
/** A create started from a row of another data table. Switching data table
* re-mounts this component, so the request has to travel through the parent. */
export type PendingCreate = { kind: 'table'; schema: string } | { kind: 'schema' }
type Props = {
dbType: DbType
dbSchema: DBSchema
@@ -66,6 +70,7 @@
datatableTree?: DataTableTables[]
datatableTreeLoading?: boolean
onSelectDatatable?: (datatable: string) => void
pendingCreate?: PendingCreate | undefined
/** Row-menu actions on a data table, run against that row's data table. */
onDatatableAction?: (datatable: string, action: DatatableRowAction) => void
canManageDatatable?: boolean
@@ -95,6 +100,7 @@
datatableTree,
datatableTreeLoading,
onSelectDatatable,
pendingCreate = $bindable(undefined),
onDatatableAction,
canManageDatatable = false,
multiSelectMode = false,
@@ -266,11 +272,6 @@
: 'group-hover:opacity-0 ' + (current ? 'opacity-0 ' : 'opacity-100 ')) +
(open ? '' : '-rotate-90')
/** Every row in the tree reads the same way: what you are looking at now is
* emphasized, everything else recedes. */
const rowText = (current: boolean) =>
current ? 'text-primary font-semibold' : 'text-secondary font-normal'
/** Reveal a node, dropping a stale "closed" that would hide a new selection. */
function reveal(dt: string | undefined, schemaKey?: string) {
const next = new Map(expandOverrides)
@@ -293,6 +294,40 @@
selected = { schemaKey, tableKey }
}
function startCreateTable(dt: string | undefined, schemaKey: string) {
if (dt !== undefined && dt !== currentDatatable) {
selectedSchemaKey = schemaKey
pendingCreate = { kind: 'table', schema: schemaKey }
onSelectDatatable?.(dt)
return
}
selected = { schemaKey, tableKey: undefined }
dbTableEditorState = { open: true }
}
function startCreateSchema(dt: string | undefined) {
if (dt !== undefined && dt !== currentDatatable) {
pendingCreate = { kind: 'schema' }
onSelectDatatable?.(dt)
return
}
newSchemaDialogOpen = true
}
// Finishes a create requested before the switch, now that this component is
// mounted against the data table it targeted.
$effect(() => {
const req = pendingCreate
if (!req || !schemaKeys.length) return
pendingCreate = undefined
if (req.kind === 'schema') {
newSchemaDialogOpen = true
} else if (schemaKeys.includes(req.schema)) {
selected = { schemaKey: req.schema, tableKey: undefined }
dbTableEditorState = { open: true }
}
})
let search = $state('')
let selected: {
schemaKey?: undefined | string
@@ -415,8 +450,7 @@
{@const dtOpen = isExpanded(root.datatable)}
{#if root.datatable !== undefined}
<button
class={'group w-full text-sm flex gap-2 items-center h-8 cursor-pointer pl-3 pr-1 hover:bg-gray-500/10 ' +
rowText(root.datatable === currentDatatable)}
class="group w-full text-xs font-normal text-primary flex gap-2 items-center h-8 cursor-pointer pl-3 pr-1 hover:bg-gray-500/10"
onclick={() => toggle(root.datatable)}
>
{#if multiSelectMode}
@@ -484,12 +518,8 @@
{@const indent = root.datatable !== undefined ? 'pl-7' : 'pl-3'}
{#if dbSupportsSchemas}
<button
class={'group w-full text-sm flex gap-2 items-center h-8 cursor-pointer pr-1 hover:bg-gray-500/10 ' +
indent +
' ' +
rowText(
root.datatable === currentDatatable && sc.schemaKey === selected.schemaKey
)}
class={'group w-full text-xs font-normal text-primary flex gap-2 items-center h-8 cursor-pointer pr-1 hover:bg-gray-500/10 ' +
indent}
onclick={() => toggle(root.datatable, sc.schemaKey)}
>
{#if multiSelectMode}
@@ -550,11 +580,9 @@
selected.schemaKey === sc.schemaKey &&
selected.tableKey === tableKey}
<button
class={'group w-full text-sm flex gap-2 items-center h-8 cursor-pointer pr-1 ' +
class={'group w-full text-xs font-normal text-primary flex gap-2 items-center h-8 cursor-pointer pr-1 ' +
tableIndent +
' ' +
rowText(isSelected) +
' ' +
(isSelected ? 'bg-surface-secondary' : 'hover:bg-surface-hover')}
onclick={() => selectTable(root.datatable, sc.schemaKey, tableKey)}
>
@@ -630,26 +658,21 @@
{/if}
</button>
{/each}
{#if root.datatable === currentDatatable || root.datatable === undefined}
<button
class={'w-full text-sm font-normal flex gap-2 items-center h-8 cursor-pointer pr-1 hover:bg-gray-500/10 text-tertiary ' +
tableIndent}
onclick={() => {
selected = { schemaKey: sc.schemaKey, tableKey: undefined }
dbTableEditorState = { open: true }
}}
>
<Plus class="shrink-0" size={14} />
<span class="text-xs">New table</span>
</button>
{/if}
<button
class={'w-full text-xs font-normal flex gap-2 items-center h-8 cursor-pointer pr-1 hover:bg-gray-500/10 text-secondary ' +
tableIndent}
onclick={() => startCreateTable(root.datatable, sc.schemaKey)}
>
<Plus class="shrink-0" size={14} />
<span class="text-xs">New table</span>
</button>
{/if}
{/each}
{#if dbSupportsSchemas && (root.datatable === currentDatatable || root.datatable === undefined) && search.trim() === ''}
{#if dbSupportsSchemas && search.trim() === ''}
<button
class={'w-full text-sm font-normal flex gap-2 items-center h-8 cursor-pointer pr-1 hover:bg-gray-500/10 text-tertiary ' +
class={'w-full text-xs font-normal flex gap-2 items-center h-8 cursor-pointer pr-1 hover:bg-gray-500/10 text-secondary ' +
(root.datatable !== undefined ? 'pl-7' : 'pl-3')}
onclick={() => (newSchemaDialogOpen = true)}
onclick={() => startCreateSchema(root.datatable)}
>
<Plus class="shrink-0" size={14} />
<span class="text-xs">New schema</span>
@@ -23,7 +23,7 @@
import type { DbInput } from './dbTypes'
import { getDbSchemas, loadAllTablesMetaData } from './apps/components/display/dbtable/metadata'
import type { SelectedTable } from './DBManager.svelte'
import type { PendingCreate, SelectedTable } from './DBManager.svelte'
import { getDbFeatures } from './apps/components/display/dbtable/dbFeatures'
import { resource } from 'runed'
import ConfirmationModal from './common/confirmationModal/ConfirmationModal.svelte'
@@ -43,6 +43,7 @@
datatableTree?: DataTableTables[]
datatableTreeLoading?: boolean
onSelectDatatable?: (datatable: string) => void
pendingCreate?: PendingCreate | undefined
onDatatableAction?: (datatable: string, action: DatatableRowAction) => void
canManageDatatable?: boolean
/** Enable multi-select mode with checkboxes in sidebar */
@@ -70,6 +71,7 @@
datatableTree,
datatableTreeLoading,
onSelectDatatable,
pendingCreate = $bindable(undefined),
onDatatableAction,
canManageDatatable,
multiSelectMode = false,
@@ -323,6 +325,7 @@
{datatableTree}
{datatableTreeLoading}
{onSelectDatatable}
bind:pendingCreate
{onDatatableAction}
{canManageDatatable}
{onImport}
@@ -5,14 +5,9 @@
import Drawer from './common/drawer/Drawer.svelte'
import DrawerContent from './common/drawer/DrawerContent.svelte'
import Select from './select/Select.svelte'
import {
ArrowLeft,
Copy,
Expand,
Minimize,
RefreshCcw,
} from 'lucide-svelte'
import { ArrowLeft, Copy, Expand, Minimize, RefreshCcw } from 'lucide-svelte'
import DBManagerContent from './DBManagerContent.svelte'
import type { PendingCreate } from './DBManager.svelte'
import DataTableMigrationsButton from './workspaceSettings/DataTableMigrationsButton.svelte'
import DataTablePermissionsButton from './workspaceSettings/DataTablePermissionsButton.svelte'
import { resource } from 'runed'
@@ -40,6 +35,10 @@
// the editor that opened it (set via openDrawer), else the nav workspace.
let ws = $derived(uriState.workspace ?? $workspaceStore)
// A create started on a data table other than the current one: survives the
// re-mount the switch causes.
let pendingCreate = $state<PendingCreate | undefined>(undefined)
// Every data table with its schemas and tables, in one call: this is what the
// left pane's tree navigates, so it has to cover the data tables the user is
// not currently on, not just the selected one.
@@ -84,7 +83,6 @@
uriState.selectedRole !== undefined))
)
// Settle the role before anything queries the data table: the schema and
// metadata fetches run as whatever role the input carries, so leaving it unset
// until the user touches the picker would send the first — and cached — round
@@ -259,6 +257,7 @@
datatableTree={uriState.isDatatableInput ? datatables.current : undefined}
datatableTreeLoading={datatables.loading}
onSelectDatatable={(dt) => (uriState.selectedDatatable = dt)}
bind:pendingCreate
canManageDatatable={!!($superadmin || $userStore?.is_admin)}
onDatatableAction={runDatatableAction}
bind:workerTag={() => workerTag.tag, (v) => (workerTag.tag = v)}
@@ -268,8 +267,7 @@
onImport={enableImportExport
? (mode) => ((importDrawerOpen = true), (importBehavior = mode))
: undefined}
>
</DBManagerContent>
></DBManagerContent>
{/key}
{/if}
{#snippet actions()}
@@ -10,7 +10,7 @@
import { ArrowLeft, Expand, Minimize, Plus, RefreshCcw } from 'lucide-svelte'
import DBManagerContent from '../DBManagerContent.svelte'
import type { DbInput } from '../dbTypes'
import type { SelectedTable } from '../DBManager.svelte'
import type { PendingCreate, SelectedTable } from '../DBManager.svelte'
import { getRawAppOperatingWorkspace } from './rawAppWorkspace'
import { useDbManagerTag } from '../dbManagerTag.svelte'
import DbWorkerTagButton from '../DbWorkerTagButton.svelte'
@@ -40,6 +40,9 @@
// Multi-select mode: selected tables
let selectedTables = $state<SelectedTable[]>([])
// Survives the re-mount a data table switch causes.
let pendingCreate = $state<PendingCreate | undefined>(undefined)
// Selected schema/table from DBManager (for preview)
let selectedSchemaKey = $state<string | undefined>(undefined)
let selectedTableKey = $state<string | undefined>(undefined)
@@ -181,6 +184,7 @@
datatableTree={datatableTree.current}
datatableTreeLoading={datatableTree.loading}
onSelectDatatable={(dt) => (selectedDatatable = dt)}
bind:pendingCreate
/>
{/key}
{:else}