mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-27 16:02:43 +00:00
* feat: replace ag-grid in the db manager table viewer with a native grid Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: add user-managed data, diagram and sql editor tabs to the db manager Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: add schema autocomplete to the db manager sql editor and polish its layout Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: add joined foreign key columns and draggable tabs to the db manager Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: move db manager tabs on drop instead of mid-drag Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: open followed foreign keys in a new db manager tab Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: scroll large tables, drop stale joins and return to the last tab on close Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: tolerate the db manager tabs going away while switching data table Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: keep saved joined columns while metadata loads, test joins in every dialect Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: put the db manager grid on the input surface in dark mode Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: address db manager review nits on joins, boolean keys and sql quoting Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: keep the dark border color on the left pinned column edge Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: tone down the db manager tree menu icons Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: write json and jsonb values from the db manager through a text cast Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: dim unrelated diagram tables less on hover Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: match json columns as text when deleting a db manager row Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: compare postgres json columns as text in exact db manager filters Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: pick the columns the db manager grid shows Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: toggle every db manager column from one checkbox Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: word joined columns as a view in the db manager Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: keep data table roles and access visible when unavailable, with the reason Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: keep data table and instance roles visible in settings when unavailable Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: hide a db manager column from its header menu Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: format db manager columns with a unit, significant digits and color rules Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: drop the before/after hints from the db manager unit picker Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: write the euro after the amount and keep units off non-numeric values Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: color rule presets, bold and italic, layered and reorderable rules in the db manager Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: decimals, thousands separator, compact notation and alignment in db manager column formats Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: compact db manager format controls, a notation toggle group and a reset button Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: db manager format and filter nits Keep the value's scale when decimals are auto, read boolean color-rule conditions as booleans, let a rule's text color reach foreign-key links, close the formatter when the columns picker opens, and filter BigQuery complex columns through TO_JSON_STRING. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
154 lines
5.2 KiB
TypeScript
154 lines
5.2 KiB
TypeScript
import { randomUUID } from '$lib/utils/uuid'
|
|
import { getLocalSetting, storeLocalSetting } from '$lib/utils'
|
|
|
|
export type DbManagerTabKind = 'data' | 'diagram' | 'sql'
|
|
|
|
export type DbManagerDiagramTable = { datatable?: string; schema: string; table: string }
|
|
|
|
export type DbManagerTab =
|
|
| { id: string; kind: 'data'; schema?: string; table?: string }
|
|
/** `tables` stays undefined until the diagram's first draw, automatic or by hand. */
|
|
| { id: string; kind: 'diagram'; tables?: DbManagerDiagramTable[] }
|
|
| { id: string; kind: 'sql'; code?: string }
|
|
|
|
export type DbManagerDataTab = Extract<DbManagerTab, { kind: 'data' }>
|
|
|
|
type StoredTabs = { tabs: DbManagerTab[]; activeId: string }
|
|
|
|
/**
|
|
* The DB manager's tabs over one database, kept in localStorage under `storageKey`. What a tab
|
|
* shows is stored (its table, the diagram's tables, the query); how it was left — scroll,
|
|
* filters, results — lives only in its mounted view.
|
|
*/
|
|
export class DbManagerTabs {
|
|
tabs = $state<DbManagerTab[]>([])
|
|
activeId = $state('')
|
|
/** Tabs by last use, most recent first: where closing the active tab goes back to, and which
|
|
* data tab the tree opens a table in when the active tab is not one. */
|
|
#recency = $state<string[]>([])
|
|
private storageKey: string | undefined
|
|
private kinds: DbManagerTabKind[]
|
|
|
|
constructor(storageKey: string | undefined, kinds: DbManagerTabKind[]) {
|
|
this.storageKey = storageKey
|
|
this.kinds = kinds
|
|
const stored = this.#load()
|
|
this.tabs = stored?.tabs.length
|
|
? stored.tabs
|
|
: kinds.map((kind) => ({ id: randomUUID(), kind }) as DbManagerTab)
|
|
this.activeId = this.tabs.some((t) => t.id === stored?.activeId)
|
|
? stored!.activeId
|
|
: this.tabs[0].id
|
|
this.#recency = this.tabs.map((t) => t.id)
|
|
this.#touch(this.activeId)
|
|
}
|
|
|
|
get active(): DbManagerTab {
|
|
return this.tabs.find((t) => t.id === this.activeId) ?? this.tabs[0]
|
|
}
|
|
|
|
/** The data tab the tree's selection belongs to: the active one, else the last used. */
|
|
get currentData(): DbManagerDataTab | undefined {
|
|
const id = this.#recency.find((r) => this.tabs.find((t) => t.id === r)?.kind === 'data')
|
|
return this.tabs.find((t): t is DbManagerDataTab => t.id === id && t.kind === 'data')
|
|
}
|
|
|
|
activate(id: string) {
|
|
if (!this.tabs.some((t) => t.id === id)) return
|
|
this.activeId = id
|
|
this.#touch(id)
|
|
this.#save()
|
|
}
|
|
|
|
add(kind: DbManagerTabKind, init: Partial<DbManagerTab> = {}): DbManagerTab {
|
|
const tab = { ...init, id: randomUUID(), kind } as DbManagerTab
|
|
this.tabs.push(tab)
|
|
this.activate(tab.id)
|
|
return tab
|
|
}
|
|
|
|
/** Closing the last tab leaves a fresh data tab: the manager always has somewhere to show rows. */
|
|
close(id: string) {
|
|
const index = this.tabs.findIndex((t) => t.id === id)
|
|
if (index === -1) return
|
|
this.tabs.splice(index, 1)
|
|
this.#recency = this.#recency.filter((r) => r !== id)
|
|
if (!this.tabs.length) {
|
|
this.add('data')
|
|
return
|
|
}
|
|
if (this.activeId === id) this.activate(this.#recency[0] ?? this.tabs[0].id)
|
|
else this.#save()
|
|
}
|
|
|
|
/** Moves a tab next to another, on the given side of it. */
|
|
move(id: string, targetId: string, side: 'before' | 'after') {
|
|
if (id === targetId) return
|
|
const from = this.tabs.findIndex((t) => t.id === id)
|
|
if (from === -1) return
|
|
const [tab] = this.tabs.splice(from, 1)
|
|
const target = this.tabs.findIndex((t) => t.id === targetId)
|
|
if (target === -1) {
|
|
this.tabs.splice(from, 0, tab)
|
|
return
|
|
}
|
|
this.tabs.splice(side === 'before' ? target : target + 1, 0, tab)
|
|
this.#save()
|
|
}
|
|
|
|
update<T extends DbManagerTab>(id: string, patch: Partial<Omit<T, 'id' | 'kind'>>) {
|
|
const tab = this.tabs.find((t) => t.id === id)
|
|
if (!tab) return
|
|
if (Object.entries(patch).every(([k, v]) => (tab as any)[k] === v)) return
|
|
Object.assign(tab, patch)
|
|
this.#save()
|
|
}
|
|
|
|
/** The current data tab, activated; one is opened when none is left. */
|
|
focusData(): DbManagerDataTab {
|
|
const tab = this.currentData ?? (this.add('data') as DbManagerDataTab)
|
|
if (this.activeId !== tab.id) this.activate(tab.id)
|
|
return tab
|
|
}
|
|
|
|
/** A table named from outside (the URL): shown in the data tab already on it, else in the
|
|
* current data tab. */
|
|
openTable(schema: string | undefined, table: string) {
|
|
const open = this.tabs.find(
|
|
(t): t is DbManagerDataTab => t.kind === 'data' && t.table === table && t.schema === schema
|
|
)
|
|
if (open) {
|
|
this.activate(open.id)
|
|
return
|
|
}
|
|
this.update<DbManagerDataTab>(this.focusData().id, { schema, table })
|
|
}
|
|
|
|
#touch(id: string) {
|
|
this.#recency = [id, ...this.#recency.filter((r) => r !== id)]
|
|
}
|
|
|
|
#load(): StoredTabs | undefined {
|
|
if (!this.storageKey) return undefined
|
|
try {
|
|
const parsed = JSON.parse(getLocalSetting(this.storageKey) ?? 'null')
|
|
if (!parsed || !Array.isArray(parsed.tabs)) return undefined
|
|
// A kind this database cannot show, e.g. a diagram saved before it became unsupported.
|
|
const tabs = (parsed.tabs as DbManagerTab[]).filter(
|
|
(t) => t && typeof t.id === 'string' && this.kinds.includes(t.kind)
|
|
)
|
|
return { tabs, activeId: parsed.activeId }
|
|
} catch {
|
|
return undefined
|
|
}
|
|
}
|
|
|
|
#save() {
|
|
if (!this.storageKey) return
|
|
try {
|
|
const stored: StoredTabs = { tabs: $state.snapshot(this.tabs), activeId: this.activeId }
|
|
storeLocalSetting(this.storageKey, JSON.stringify(stored))
|
|
} catch {}
|
|
}
|
|
}
|