feat: persistent Db manager state in URI (#8134)
* DB Manager state in URL
* Fix state not saving
* shorted uri params
* infer db_type from prefix
* Revert "infer db_type from prefix"
This reverts commit 7415fbed3d.
* dbm syntax
* infer database type
* Omit main and public
* remove legacy #dbmanager:
* Preserve hash
* nit
* Fix remaining dbManagerDrawer objects
This commit is contained in:
@@ -6,27 +6,20 @@
|
||||
import DrawerContent from './common/drawer/DrawerContent.svelte'
|
||||
import Select from './select/Select.svelte'
|
||||
import { ArrowLeft, Expand, LoaderCircle, Minimize, RefreshCcw } from 'lucide-svelte'
|
||||
import type { DbInput } from './dbTypes'
|
||||
import DBManagerContent from './DBManagerContent.svelte'
|
||||
import { resource } from 'runed'
|
||||
import { untrack } from 'svelte'
|
||||
import type { DbManagerUriState } from './dbManagerDrawerModel.svelte'
|
||||
|
||||
interface Props {
|
||||
uriState: DbManagerUriState
|
||||
/** Z-index offset for the drawer, useful when opening from within modals */
|
||||
offset?: number
|
||||
}
|
||||
|
||||
let { offset = 0 }: Props = $props()
|
||||
let { uriState, offset = 0 }: Props = $props()
|
||||
|
||||
let input: DbInput | undefined = $state()
|
||||
let open = $derived(!!input)
|
||||
|
||||
// For datatable inputs, track the selected datatable separately
|
||||
let selectedDatatable = $state<string | undefined>(undefined)
|
||||
|
||||
// Check if input is a datatable type
|
||||
const isDatatableInput = $derived(
|
||||
input?.type === 'database' && input.resourcePath.startsWith('datatable://')
|
||||
)
|
||||
let open = $derived(uriState.open)
|
||||
|
||||
// Load available datatables when drawer opens with datatable input
|
||||
const datatables = resource<string[]>([], async () => {
|
||||
@@ -39,16 +32,6 @@
|
||||
}
|
||||
})
|
||||
|
||||
// Computed input that updates when selectedDatatable changes
|
||||
const effectiveInput: DbInput | undefined = $derived.by(() => {
|
||||
if (!input) return undefined
|
||||
if (!isDatatableInput || !selectedDatatable) return input
|
||||
return {
|
||||
...input,
|
||||
resourcePath: `datatable://${selectedDatatable}`
|
||||
}
|
||||
})
|
||||
|
||||
const datatableItems = $derived(
|
||||
datatables.current.map((dt) => ({
|
||||
value: dt,
|
||||
@@ -56,32 +39,26 @@
|
||||
}))
|
||||
)
|
||||
|
||||
export function openDrawer(nInput: DbInput) {
|
||||
input = nInput
|
||||
if (isDatatableInput) {
|
||||
datatables.refetch()
|
||||
// Refetch datatables when switching to a datatable input
|
||||
$effect(() => {
|
||||
if (uriState.isDatatableInput) {
|
||||
untrack(() => datatables.refetch())
|
||||
}
|
||||
// If it's a datatable input, extract the datatable name for the selector
|
||||
if (nInput.type === 'database' && nInput.resourcePath.startsWith('datatable://')) {
|
||||
selectedDatatable = nInput.resourcePath.replace('datatable://', '')
|
||||
datatables.refetch()
|
||||
} else {
|
||||
selectedDatatable = undefined
|
||||
}
|
||||
}
|
||||
export function closeDrawer() {
|
||||
input = undefined
|
||||
selectedDatatable = undefined
|
||||
})
|
||||
|
||||
function handleClose() {
|
||||
uriState.closeDrawer()
|
||||
dbManagerContent?.clearReplResult()
|
||||
if (window.location.hash.startsWith('#dbmanager:'))
|
||||
history.replaceState('', document.title, window.location.href.replace(/#dbmanager:.*$/, ''))
|
||||
}
|
||||
|
||||
let windowWidth = $state(window.innerWidth)
|
||||
let expand = $state(false)
|
||||
|
||||
$effect(() => {
|
||||
if (!open) expand = false
|
||||
if (!open) {
|
||||
expand = false
|
||||
uriState.closeDrawer()
|
||||
}
|
||||
})
|
||||
|
||||
let dbManagerContent: DBManagerContent | undefined = $state()
|
||||
@@ -96,7 +73,7 @@
|
||||
size={expand ? `${windowWidth}px` : '1200px'}
|
||||
preventEscape
|
||||
{offset}
|
||||
on:close={closeDrawer}
|
||||
on:close={handleClose}
|
||||
>
|
||||
<DrawerContent
|
||||
title={hasReplResult ? 'Query Result' : 'Database Manager'}
|
||||
@@ -104,18 +81,24 @@
|
||||
if (hasReplResult) {
|
||||
dbManagerContent?.clearReplResult()
|
||||
} else {
|
||||
closeDrawer()
|
||||
handleClose()
|
||||
}
|
||||
}}
|
||||
CloseIcon={hasReplResult ? ArrowLeft : undefined}
|
||||
noPadding
|
||||
id="db-manager-drawer"
|
||||
>
|
||||
{#if effectiveInput && $workspaceStore}
|
||||
{#key selectedDatatable}
|
||||
<DBManagerContent bind:this={dbManagerContent} input={effectiveInput} bind:hasReplResult>
|
||||
{#if uriState.effectiveInput && $workspaceStore}
|
||||
{#key uriState.selectedDatatable}
|
||||
<DBManagerContent
|
||||
bind:this={dbManagerContent}
|
||||
input={uriState.effectiveInput}
|
||||
bind:hasReplResult
|
||||
bind:selectedSchemaKey={uriState.selectedSchema}
|
||||
bind:selectedTableKey={uriState.selectedTable}
|
||||
>
|
||||
{#snippet dbSelector()}
|
||||
{#if isDatatableInput}
|
||||
{#if uriState.isDatatableInput}
|
||||
{#if datatables.loading}
|
||||
<div class="flex items-center gap-2 text-tertiary ml-2">
|
||||
<LoaderCircle size={14} class="animate-spin" />
|
||||
@@ -125,7 +108,7 @@
|
||||
<Select
|
||||
transformInputSelectedText={(s) => `Datatable: ${s}`}
|
||||
items={datatableItems}
|
||||
bind:value={selectedDatatable}
|
||||
bind:value={uriState.selectedDatatable}
|
||||
placeholder="Select data table"
|
||||
size="md"
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { WorkspaceService } from '$lib/gen'
|
||||
import { globalDbManagerDrawer, workspaceStore } from '$lib/stores'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
|
||||
import Select from './select/Select.svelte'
|
||||
import ExploreAssetButton, { assetCanBeExplored } from './ExploreAssetButton.svelte'
|
||||
@@ -31,7 +31,7 @@
|
||||
let datatables = usePromise(() =>
|
||||
WorkspaceService.listDataTables({ workspace: $workspaceStore ?? '' })
|
||||
)
|
||||
let dbManagerDrawer = $derived(globalDbManagerDrawer.val)
|
||||
|
||||
</script>
|
||||
|
||||
<div class={className}>
|
||||
@@ -49,7 +49,6 @@
|
||||
<ExploreAssetButton
|
||||
class="mt-1 w-fit"
|
||||
asset={{ kind: 'datatable', path: value }}
|
||||
{dbManagerDrawer}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { WorkspaceService } from '$lib/gen'
|
||||
import { globalDbManagerDrawer, workspaceStore } from '$lib/stores'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
|
||||
import Select from './select/Select.svelte'
|
||||
import ExploreAssetButton, { assetCanBeExplored } from './ExploreAssetButton.svelte'
|
||||
@@ -31,7 +31,7 @@
|
||||
let ducklakes = usePromise(() =>
|
||||
WorkspaceService.listDucklakes({ workspace: $workspaceStore ?? '' })
|
||||
)
|
||||
let dbManagerDrawer = $derived(globalDbManagerDrawer.val)
|
||||
|
||||
</script>
|
||||
|
||||
<div class={className}>
|
||||
@@ -49,7 +49,6 @@
|
||||
<ExploreAssetButton
|
||||
class="mt-1 w-fit"
|
||||
asset={{ kind: 'ducklake', path: value }}
|
||||
{dbManagerDrawer}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -16,9 +16,8 @@
|
||||
import { isDbType } from '$lib/components/dbTypes'
|
||||
import { formatAsset, type Asset } from '$lib/components/assets/lib'
|
||||
import { Button, ButtonType } from '$lib/components/common'
|
||||
import DbManagerDrawer from '$lib/components/DBManagerDrawer.svelte'
|
||||
import S3FilePicker from '$lib/components/S3FilePicker.svelte'
|
||||
import { userStore } from '$lib/stores'
|
||||
import { globalDbManagerDrawer, userStore } from '$lib/stores'
|
||||
import { isS3Uri } from '$lib/utils'
|
||||
import { Database, File } from 'lucide-svelte'
|
||||
import DucklakeIcon from './icons/DucklakeIcon.svelte'
|
||||
@@ -27,7 +26,6 @@
|
||||
asset,
|
||||
_resourceMetadata,
|
||||
s3FilePicker,
|
||||
dbManagerDrawer,
|
||||
onClick,
|
||||
class: className = '',
|
||||
noText = false,
|
||||
@@ -38,7 +36,6 @@
|
||||
asset: Asset
|
||||
_resourceMetadata?: { resource_type?: string }
|
||||
s3FilePicker?: S3FilePicker
|
||||
dbManagerDrawer?: DbManagerDrawer
|
||||
onClick?: () => void
|
||||
class?: string
|
||||
noText?: boolean
|
||||
@@ -46,6 +43,8 @@
|
||||
btnClasses?: string
|
||||
disabled?: boolean
|
||||
} = $props()
|
||||
|
||||
let dbManagerDrawer = $derived(globalDbManagerDrawer.val)
|
||||
const assetUri = $derived(formatAsset(asset))
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { ResourceService, WorkspaceService } from '$lib/gen'
|
||||
import { globalDbManagerDrawer, workspaceStore } from '$lib/stores'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { onMount, untrack } from 'svelte'
|
||||
import AppConnect from './AppConnectDrawer.svelte'
|
||||
import ResourceEditorDrawer from './ResourceEditorDrawer.svelte'
|
||||
@@ -174,7 +174,6 @@
|
||||
|
||||
let appConnect: AppConnect | undefined = $state()
|
||||
let resourceEditor: ResourceEditorDrawer | undefined = $state()
|
||||
let dbManagerDrawer = $derived(globalDbManagerDrawer.val)
|
||||
let hovering = $state(false)
|
||||
let isDatatableSelected = $derived(value?.startsWith('datatable://') ?? false)
|
||||
</script>
|
||||
@@ -317,7 +316,6 @@
|
||||
class="mt-1"
|
||||
_resourceMetadata={{ resource_type: resourceType }}
|
||||
asset={{ kind: 'resource', path: value }}
|
||||
{dbManagerDrawer}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -148,7 +148,9 @@
|
||||
(v) => {
|
||||
v.maxTs ? (filters.val.max_ts = new Date(v.maxTs)) : delete filters.val.max_ts
|
||||
v.minTs ? (filters.val.min_ts = new Date(v.minTs)) : delete filters.val.min_ts
|
||||
v.timeframe ? (filters.val.timeframe = v.timeframe) : delete filters.val.timeframe
|
||||
v.timeframe && v.timeframe !== 'Latest runs'
|
||||
? (filters.val.timeframe = v.timeframe)
|
||||
: delete filters.val.timeframe
|
||||
}
|
||||
)
|
||||
let timeframe = $derived(_timeframe.val)
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
type Props = {
|
||||
s3FilePicker?: any | undefined
|
||||
dbManagerDrawer?: any | undefined
|
||||
resourceEditorDrawer?: ResourceEditorDrawer | undefined
|
||||
resourceDataCache: Record<string, string | undefined>
|
||||
asset: Asset
|
||||
@@ -18,7 +17,6 @@
|
||||
}
|
||||
let {
|
||||
s3FilePicker,
|
||||
dbManagerDrawer,
|
||||
resourceEditorDrawer,
|
||||
resourceDataCache,
|
||||
asset,
|
||||
@@ -71,7 +69,6 @@
|
||||
<ExploreAssetButton
|
||||
{asset}
|
||||
{s3FilePicker}
|
||||
{dbManagerDrawer}
|
||||
onClick={() => onClick?.()}
|
||||
noText
|
||||
_resourceMetadata={{ resource_type: resourceDataCacheValue }}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
} from './lib'
|
||||
import { untrack } from 'svelte'
|
||||
import { ResourceService, WorkspaceService } from '$lib/gen'
|
||||
import { globalDbManagerDrawer, workspaceStore } from '$lib/stores'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import Tooltip from '../meltComponents/Tooltip.svelte'
|
||||
import Tooltip2 from '../Tooltip.svelte'
|
||||
import ResourceEditorDrawer from '../ResourceEditorDrawer.svelte'
|
||||
@@ -48,7 +48,6 @@
|
||||
let blueBgDiv: HTMLDivElement | undefined = $state()
|
||||
|
||||
let s3FilePicker: S3FilePicker | undefined = $state()
|
||||
let dbManagerDrawer = $derived(globalDbManagerDrawer.val)
|
||||
let resourceEditorDrawer: ResourceEditorDrawer | undefined = $state()
|
||||
let isOpen = $state(false)
|
||||
let resourceDataCache: Record<string, string | undefined> = $state({})
|
||||
@@ -209,7 +208,6 @@
|
||||
onClick={() => (isOpen = false)}
|
||||
{asset}
|
||||
{resourceDataCache}
|
||||
{dbManagerDrawer}
|
||||
{resourceEditorDrawer}
|
||||
{s3FilePicker}
|
||||
{ducklakeNotFound}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { ResourceService, type Job } from '$lib/gen'
|
||||
import { inferAssets } from '$lib/infer'
|
||||
import { globalDbManagerDrawer, workspaceStore } from '$lib/stores'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { usePromise } from '$lib/svelte5Utils.svelte'
|
||||
import { pruneNullishArray, uniqueBy } from '$lib/utils'
|
||||
import ResourceEditorDrawer from '../ResourceEditorDrawer.svelte'
|
||||
@@ -64,7 +64,6 @@
|
||||
})
|
||||
|
||||
let s3FilePicker: S3FilePicker | undefined = $state()
|
||||
let dbManagerDrawer = $derived(globalDbManagerDrawer.val)
|
||||
let resourceEditorDrawer: ResourceEditorDrawer | undefined = $state()
|
||||
</script>
|
||||
|
||||
@@ -86,7 +85,6 @@
|
||||
<AssetButtons
|
||||
{asset}
|
||||
{resourceDataCache}
|
||||
{dbManagerDrawer}
|
||||
{resourceEditorDrawer}
|
||||
{s3FilePicker}
|
||||
/>
|
||||
|
||||
207
frontend/src/lib/components/dbManagerDrawerModel.svelte.ts
Normal file
207
frontend/src/lib/components/dbManagerDrawerModel.svelte.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
import { z } from 'zod'
|
||||
import { useSearchParams } from '$lib/svelte5UtilsKit.svelte'
|
||||
import type { DbInput, DbType } from './dbTypes'
|
||||
import { isDbType } from './dbTypes'
|
||||
|
||||
/**
|
||||
* Single URL param `dbm` encodes the full DB manager state:
|
||||
* firstSegment~path~schema.table
|
||||
*
|
||||
* firstSegment:
|
||||
* datatable – database with datatable:// resource (resourceType always postgresql)
|
||||
* ducklake – ducklake connection
|
||||
* postgresql, mysql, … – regular database (the segment IS the resource type)
|
||||
*
|
||||
* schema.table (third segment, optional):
|
||||
* schema.table – both
|
||||
* .table – table only
|
||||
* schema. – schema only
|
||||
* (omitted) – neither
|
||||
*
|
||||
* Default schemas (omitted from URL, restored on parse):
|
||||
* datatable → public
|
||||
* ducklake → main
|
||||
*
|
||||
* Examples:
|
||||
* datatable~main~.customers (schema "public" implied)
|
||||
* ducklake~main~.orders (schema "main" implied)
|
||||
* postgresql~$res:u/user/my_pg~public.customers
|
||||
*/
|
||||
|
||||
const dbManagerSchema = z.object({
|
||||
dbm: z.string().nullable()
|
||||
})
|
||||
|
||||
interface ParsedDbm {
|
||||
type: 'database' | 'datatable' | 'ducklake'
|
||||
path: string
|
||||
resType?: string
|
||||
schema?: string
|
||||
table?: string
|
||||
}
|
||||
|
||||
function parseDbm(raw: unknown): ParsedDbm | null {
|
||||
if (!raw || typeof raw !== 'string') return null
|
||||
const parts = raw.split('~')
|
||||
if (parts.length < 2 || !parts[1]) return null
|
||||
|
||||
const firstSeg = parts[0]
|
||||
const path = parts[1]
|
||||
const schemaTable = parts[2] ?? ''
|
||||
|
||||
let type: ParsedDbm['type']
|
||||
let resType: string | undefined
|
||||
if (firstSeg === 'datatable') {
|
||||
type = 'datatable'
|
||||
} else if (firstSeg === 'ducklake') {
|
||||
type = 'ducklake'
|
||||
} else if (isDbType(firstSeg)) {
|
||||
type = 'database'
|
||||
resType = firstSeg
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
|
||||
let schema: string | undefined
|
||||
let table: string | undefined
|
||||
if (schemaTable) {
|
||||
const dotIdx = schemaTable.indexOf('.')
|
||||
if (dotIdx === 0) {
|
||||
table = schemaTable.slice(1) || undefined
|
||||
} else if (dotIdx === schemaTable.length - 1) {
|
||||
schema = schemaTable.slice(0, -1) || undefined
|
||||
} else if (dotIdx > 0) {
|
||||
schema = schemaTable.slice(0, dotIdx)
|
||||
table = schemaTable.slice(dotIdx + 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Restore default schema when omitted for datatable/ducklake
|
||||
if (!schema && table && type in defaultSchemas) {
|
||||
schema = defaultSchemas[type]
|
||||
}
|
||||
|
||||
return { type, path, resType, schema, table }
|
||||
}
|
||||
|
||||
const defaultSchemas: Record<string, string> = { datatable: 'public', ducklake: 'main' }
|
||||
|
||||
function buildDbm(p: ParsedDbm): string {
|
||||
const firstSeg = p.type === 'database' ? p.resType! : p.type
|
||||
const schema = p.schema === defaultSchemas[p.type] ? undefined : p.schema
|
||||
let schemaTable = ''
|
||||
if (schema && p.table) {
|
||||
schemaTable = `${schema}.${p.table}`
|
||||
} else if (p.table) {
|
||||
schemaTable = `.${p.table}`
|
||||
} else if (schema) {
|
||||
schemaTable = `${schema}.`
|
||||
}
|
||||
return schemaTable ? `${firstSeg}~${p.path}~${schemaTable}` : `${firstSeg}~${p.path}`
|
||||
}
|
||||
|
||||
export interface DbManagerUriState {
|
||||
readonly input: DbInput | undefined
|
||||
readonly effectiveInput: DbInput | undefined
|
||||
readonly isDatatableInput: boolean
|
||||
selectedDatatable: string | undefined
|
||||
selectedSchema: string | undefined
|
||||
selectedTable: string | undefined
|
||||
readonly open: boolean
|
||||
openDrawer: (nInput: DbInput) => void
|
||||
closeDrawer: () => void
|
||||
}
|
||||
|
||||
export function useDbManagerUriState(): DbManagerUriState {
|
||||
const params = useSearchParams(dbManagerSchema)
|
||||
|
||||
const parsed = $derived(parseDbm(params.dbm))
|
||||
|
||||
let input: DbInput | undefined = $derived.by(() => {
|
||||
if (!parsed) return undefined
|
||||
if (parsed.type === 'ducklake') {
|
||||
return {
|
||||
type: 'ducklake' as const,
|
||||
ducklake: parsed.path,
|
||||
specificTable: parsed.table
|
||||
}
|
||||
}
|
||||
// datatable or database
|
||||
const resType = parsed.type === 'datatable' ? 'postgresql' : parsed.resType
|
||||
if (!isDbType(resType ?? undefined)) return undefined
|
||||
return {
|
||||
type: 'database' as const,
|
||||
resourceType: resType as DbType,
|
||||
resourcePath: parsed.type === 'datatable' ? `datatable://${parsed.path}` : parsed.path,
|
||||
specificSchema: parsed.schema,
|
||||
specificTable: parsed.table
|
||||
}
|
||||
})
|
||||
|
||||
const isDatatableInput = $derived(parsed?.type === 'datatable')
|
||||
|
||||
function updateField(updates: Partial<ParsedDbm>) {
|
||||
const p = parseDbm(params.dbm)
|
||||
if (!p) return
|
||||
Object.assign(p, updates)
|
||||
params.dbm = buildDbm(p)
|
||||
}
|
||||
|
||||
function openDrawer(nInput: DbInput) {
|
||||
if (nInput.type === 'database') {
|
||||
const isDatatable = nInput.resourcePath.startsWith('datatable://')
|
||||
params.dbm = buildDbm({
|
||||
type: isDatatable ? 'datatable' : 'database',
|
||||
path: isDatatable ? nInput.resourcePath.slice('datatable://'.length) : nInput.resourcePath,
|
||||
resType: isDatatable ? undefined : nInput.resourceType,
|
||||
schema: nInput.specificSchema,
|
||||
table: nInput.specificTable
|
||||
})
|
||||
} else {
|
||||
params.dbm = buildDbm({
|
||||
type: 'ducklake',
|
||||
path: nInput.ducklake,
|
||||
table: nInput.specificTable
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function closeDrawer() {
|
||||
params.dbm = null
|
||||
}
|
||||
|
||||
return {
|
||||
get input() {
|
||||
return input
|
||||
},
|
||||
get effectiveInput() {
|
||||
return input
|
||||
},
|
||||
get isDatatableInput() {
|
||||
return isDatatableInput
|
||||
},
|
||||
get selectedDatatable() {
|
||||
return parsed?.type === 'datatable' ? parsed.path : undefined
|
||||
},
|
||||
set selectedDatatable(v: string | undefined) {
|
||||
if (v) updateField({ path: v })
|
||||
},
|
||||
get selectedSchema() {
|
||||
return parsed?.schema
|
||||
},
|
||||
set selectedSchema(v: string | undefined) {
|
||||
updateField({ schema: v })
|
||||
},
|
||||
get selectedTable() {
|
||||
return parsed?.table
|
||||
},
|
||||
set selectedTable(v: string | undefined) {
|
||||
updateField({ table: v })
|
||||
},
|
||||
get open() {
|
||||
return !!input
|
||||
},
|
||||
openDrawer,
|
||||
closeDrawer
|
||||
}
|
||||
}
|
||||
@@ -235,7 +235,7 @@
|
||||
import type { Edge, Node } from '@xyflow/svelte'
|
||||
|
||||
import { getNodeColorClasses, NODE } from '../../util'
|
||||
import { globalDbManagerDrawer, userStore } from '$lib/stores'
|
||||
import { userStore } from '$lib/stores'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { slide } from 'svelte/transition'
|
||||
import AssetColumnBadges from '$lib/components/assets/AssetColumnBadges.svelte'
|
||||
@@ -313,7 +313,6 @@
|
||||
noText
|
||||
buttonVariant="accent"
|
||||
s3FilePicker={flowGraphAssetsCtx?.val.s3FilePicker}
|
||||
dbManagerDrawer={globalDbManagerDrawer.val}
|
||||
_resourceMetadata={cachedResourceMetadata}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
flow: `/flows/get/${path}`,
|
||||
app: `/apps/get/${path}`,
|
||||
raw_app: `/apps_raw/get/${path}`,
|
||||
asset: `#dbmanager:${path}`
|
||||
asset: '#'
|
||||
}[kind]
|
||||
}
|
||||
export function getFavoriteLabel(path: string, kind: FavoriteKind): string {
|
||||
|
||||
@@ -8,14 +8,12 @@
|
||||
import CustomInstanceDbWizardModal from './CustomInstanceDbWizardModal.svelte'
|
||||
import { ArrowRight, TriangleAlert } from 'lucide-svelte'
|
||||
import type { ConfirmationModalHandle } from '../common/confirmationModal/asyncConfirmationModal.svelte'
|
||||
import DBManagerDrawer from '../DBManagerDrawer.svelte'
|
||||
import type { Snippet } from 'svelte'
|
||||
|
||||
type Props = {
|
||||
value: string | undefined
|
||||
customInstanceDbs: ResourceReturn<ListCustomInstanceDbsResponse>
|
||||
confirmationModal: ConfirmationModalHandle
|
||||
dbManagerDrawer: DBManagerDrawer | undefined
|
||||
wizardBottomHint?: Snippet | undefined
|
||||
class?: string
|
||||
tag?: CustomInstanceDbTag
|
||||
@@ -24,7 +22,6 @@
|
||||
value = $bindable(),
|
||||
customInstanceDbs,
|
||||
confirmationModal,
|
||||
dbManagerDrawer,
|
||||
wizardBottomHint,
|
||||
class: className,
|
||||
tag
|
||||
@@ -90,7 +87,6 @@
|
||||
<CustomInstanceDbWizardModal
|
||||
{customInstanceDbs}
|
||||
{confirmationModal}
|
||||
{dbManagerDrawer}
|
||||
{tag}
|
||||
bottomHint={wizardBottomHint}
|
||||
bind:opened={
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
type Props = {
|
||||
customInstanceDbs: ResourceReturn<ListCustomInstanceDbsResponse>
|
||||
confirmationModal: ConfirmationModalHandle
|
||||
dbManagerDrawer: any | undefined
|
||||
bottomHint?: Snippet | undefined
|
||||
opened: { status: CustomInstanceDb | undefined; dbname: string } | undefined
|
||||
tag?: CustomInstanceDbTag
|
||||
@@ -33,7 +32,6 @@
|
||||
let {
|
||||
customInstanceDbs,
|
||||
confirmationModal,
|
||||
dbManagerDrawer,
|
||||
bottomHint,
|
||||
opened = $bindable(),
|
||||
tag
|
||||
@@ -76,7 +74,6 @@
|
||||
class="flex-1"
|
||||
asset={{ kind: 'resource', path: 'CUSTOM_INSTANCE_DB/' + dbname }}
|
||||
_resourceMetadata={{ resource_type: 'postgresql' }}
|
||||
{dbManagerDrawer}
|
||||
disabled={!$isCustomInstanceDbEnabled || !enableManageButton}
|
||||
onClick={() => (opened = undefined)}
|
||||
/>
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
import { random_adj } from '../random_positive_adjetive'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { SettingService, WorkspaceService, type GetSettingsResponse } from '$lib/gen'
|
||||
import { globalDbManagerDrawer, workspaceStore } from '$lib/stores'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { createAsyncConfirmationModal } from '../common/confirmationModal/asyncConfirmationModal.svelte'
|
||||
import ConfirmationModal from '../common/confirmationModal/ConfirmationModal.svelte'
|
||||
import { resource } from 'runed'
|
||||
@@ -141,7 +141,6 @@
|
||||
}
|
||||
|
||||
let confirmationModal = createAsyncConfirmationModal()
|
||||
let dbManagerDrawer = $derived(globalDbManagerDrawer.val)
|
||||
let dirtyMap = $derived.by(() => {
|
||||
const map: Record<string, boolean> = {}
|
||||
for (let i = 0; i < tempSettings.dataTables.length; i++) {
|
||||
@@ -245,7 +244,6 @@
|
||||
<CustomInstanceDbSelect
|
||||
class="flex-1"
|
||||
{confirmationModal}
|
||||
{dbManagerDrawer}
|
||||
{customInstanceDbs}
|
||||
bind:value={dataTable.database.resource_path}
|
||||
tag="datatable"
|
||||
@@ -265,7 +263,6 @@
|
||||
<ExploreAssetButton
|
||||
class="h-9"
|
||||
asset={{ kind: 'datatable', path: dataTable.name }}
|
||||
{dbManagerDrawer}
|
||||
disabled
|
||||
/>
|
||||
</svelte:fragment>
|
||||
@@ -275,7 +272,6 @@
|
||||
<ExploreAssetButton
|
||||
class="h-9"
|
||||
asset={{ kind: 'datatable', path: dataTable.name }}
|
||||
{dbManagerDrawer}
|
||||
/>
|
||||
{/if}
|
||||
</Cell>
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
import { SettingService, WorkspaceService } from '$lib/gen'
|
||||
import type { GetSettingsResponse } from '$lib/gen'
|
||||
|
||||
import { globalDbManagerDrawer, workspaceStore } from '$lib/stores'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import ExploreAssetButton from '../ExploreAssetButton.svelte'
|
||||
import Tooltip from '../Tooltip.svelte'
|
||||
@@ -187,7 +187,6 @@
|
||||
'Where the data is actually stored, in parquet format. You need to configure a workspace storage first'
|
||||
}
|
||||
|
||||
let dbManagerDrawer = $derived(globalDbManagerDrawer.val)
|
||||
let confirmationModal = createAsyncConfirmationModal()
|
||||
</script>
|
||||
|
||||
@@ -293,7 +292,6 @@
|
||||
bind:value={ducklake.catalog.resource_path}
|
||||
{customInstanceDbs}
|
||||
{confirmationModal}
|
||||
{dbManagerDrawer}
|
||||
tag="ducklake"
|
||||
>
|
||||
{#snippet wizardBottomHint()}
|
||||
@@ -372,7 +370,6 @@
|
||||
{:else}
|
||||
<ExploreAssetButton
|
||||
asset={{ kind: 'ducklake', path: ducklake.name }}
|
||||
{dbManagerDrawer}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
import { getLocalSetting, type StateStore } from './utils'
|
||||
import { createState } from './svelte5Utils.svelte'
|
||||
import { DEFAULT_HUB_BASE_URL } from './hub'
|
||||
import type { DbManagerUriState } from './components/dbManagerDrawerModel.svelte'
|
||||
|
||||
export interface UserExt {
|
||||
email: string
|
||||
@@ -126,9 +127,7 @@ export const codeCompletionSessionEnabled = writable<boolean>(
|
||||
|
||||
export const usedTriggerKinds = writable<string[]>([])
|
||||
|
||||
export let globalDbManagerDrawer: StateStore<any | undefined> = createState({
|
||||
val: undefined
|
||||
})
|
||||
export let globalDbManagerDrawer: StateStore<DbManagerUriState | undefined> = { val: undefined }
|
||||
|
||||
type SQLBaseSchema = {
|
||||
[schemaKey: string]: {
|
||||
|
||||
@@ -107,9 +107,10 @@ export function useSearchParams<S extends z.ZodType>(schema: S): SearchParamsRes
|
||||
} else {
|
||||
sp.set(key, serializeParam(v))
|
||||
}
|
||||
const hash = window.location.hash
|
||||
const newUrl = sp.toString()
|
||||
? `${window.location.pathname}?${sp}`
|
||||
: window.location.pathname
|
||||
? `${window.location.pathname}?${sp}${hash}`
|
||||
: `${window.location.pathname}${hash}`
|
||||
history.replaceState(history.state, '', newUrl)
|
||||
},
|
||||
enumerable: true,
|
||||
|
||||
@@ -11,13 +11,7 @@
|
||||
UserService,
|
||||
WorkspaceService
|
||||
} from '$lib/gen'
|
||||
import {
|
||||
capitalize,
|
||||
classNames,
|
||||
getModifierKey,
|
||||
parseDbInputFromAssetSyntax,
|
||||
sendUserToast
|
||||
} from '$lib/utils'
|
||||
import { capitalize, classNames, getModifierKey, sendUserToast } from '$lib/utils'
|
||||
import WorkspaceMenu from '$lib/components/sidebar/WorkspaceMenu.svelte'
|
||||
import SidebarContent from '$lib/components/sidebar/SidebarContent.svelte'
|
||||
import CriticalAlertModal from '$lib/components/sidebar/CriticalAlertModal.svelte'
|
||||
@@ -66,8 +60,8 @@
|
||||
import AiChatLayout from '$lib/components/copilot/chat/AiChatLayout.svelte'
|
||||
import { DEFAULT_HUB_BASE_URL } from '$lib/hub'
|
||||
import DBManagerDrawer from '$lib/components/DBManagerDrawer.svelte'
|
||||
import { watchOnce } from 'runed'
|
||||
import { useIsDarkMode } from '$lib/components/DarkModeObserver.svelte'
|
||||
import { useDbManagerUriState } from '$lib/components/dbManagerDrawerModel.svelte'
|
||||
interface Props {
|
||||
children?: import('svelte').Snippet
|
||||
}
|
||||
@@ -439,18 +433,8 @@
|
||||
untrack(() => loadProtectionRules(workspace))
|
||||
}
|
||||
})
|
||||
watchOnce(
|
||||
() => globalDbManagerDrawer.val,
|
||||
() => {
|
||||
if (!globalDbManagerDrawer.val) return
|
||||
const hash = window.location.hash
|
||||
if (hash.startsWith('#dbmanager:')) {
|
||||
const [_, path] = hash.split('#dbmanager:')
|
||||
const dbInput = parseDbInputFromAssetSyntax(path)
|
||||
if (dbInput) globalDbManagerDrawer.val?.openDrawer(dbInput)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
globalDbManagerDrawer.val = useDbManagerUriState()
|
||||
</script>
|
||||
|
||||
<svelte:window bind:innerWidth />
|
||||
@@ -786,6 +770,6 @@
|
||||
<CenteredModal title="Loading user..." loading={true}></CenteredModal>
|
||||
{/if}
|
||||
|
||||
{#if $workspaceStore}
|
||||
<DBManagerDrawer bind:this={globalDbManagerDrawer.val} />
|
||||
{#if $workspaceStore && globalDbManagerDrawer.val}
|
||||
<DBManagerDrawer uriState={globalDbManagerDrawer.val} />
|
||||
{/if}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
let assets = $derived(_assets.current?.flatMap((page) => page.assets))
|
||||
|
||||
let s3FilePicker: S3FilePicker | undefined = $state()
|
||||
let dbManagerDrawer = $derived(globalDbManagerDrawer.val) as any
|
||||
let assetsUsageDropdown: AssetsUsageDrawer | undefined = $state()
|
||||
|
||||
let allS3Storages = resource(
|
||||
@@ -192,7 +191,6 @@
|
||||
<ExploreAssetButton
|
||||
asset={{ kind: props.assetKind, path: item.value }}
|
||||
{s3FilePicker}
|
||||
{dbManagerDrawer}
|
||||
btnClasses="dark:bg-surface"
|
||||
/>
|
||||
</div>
|
||||
@@ -337,7 +335,6 @@
|
||||
<ExploreAssetButton
|
||||
{asset}
|
||||
{s3FilePicker}
|
||||
{dbManagerDrawer}
|
||||
_resourceMetadata={asset.metadata}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -35,8 +35,7 @@
|
||||
enterpriseLicense,
|
||||
userStore,
|
||||
workspaceStore,
|
||||
userWorkspaces,
|
||||
globalDbManagerDrawer
|
||||
userWorkspaces
|
||||
} from '$lib/stores'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import {
|
||||
@@ -557,8 +556,6 @@
|
||||
}
|
||||
})
|
||||
|
||||
let dbManagerDrawer = $derived(globalDbManagerDrawer.val) as any
|
||||
|
||||
let showTable = $derived(
|
||||
tab == 'workspace' || tab == 'states' || tab == 'cache' || tab == 'theme'
|
||||
)
|
||||
@@ -1064,7 +1061,6 @@
|
||||
{#if path && assetCanBeExplored({ kind: 'resource', path }, { resource_type }) && !$userStore?.operator}
|
||||
<ExploreAssetButton
|
||||
asset={{ kind: 'resource', path }}
|
||||
{dbManagerDrawer}
|
||||
_resourceMetadata={{ resource_type }}
|
||||
class="w-24"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user