From ec5d971913364a164a80717c5f96762433d2a62a Mon Sep 17 00:00:00 2001 From: blankll Date: Wed, 22 Jul 2026 23:23:20 +0800 Subject: [PATCH 01/26] feat(sidebar): add ER diagram button to database toolbar Add a visible ER diagram button next to the refresh button in the database selector toolbar. Previously users could only access the ER diagram via right-click context menu on a table in the schema tree. Co-authored-by: Sisyphus --- src/components/sidebar/DatabaseSelectorRow.vue | 12 ++++++++++++ src/lang/enUS.ts | 1 + src/lang/zhCN.ts | 1 + 3 files changed, 14 insertions(+) diff --git a/src/components/sidebar/DatabaseSelectorRow.vue b/src/components/sidebar/DatabaseSelectorRow.vue index 153600b..a6a9914 100644 --- a/src/components/sidebar/DatabaseSelectorRow.vue +++ b/src/components/sidebar/DatabaseSelectorRow.vue @@ -31,6 +31,7 @@ type ActionKind | 'backupDatabase' | 'exportDatabase' | 'dropDatabase' + | 'showErDiagram' const props = defineProps() @@ -150,6 +151,17 @@ watch(() => props.connectionId, async (connId) => { + + - - - - {{ - t('components.databaseBrowser.erDiagram.zoom', { - percentage: Math.round(zoomLevel * 100), - }) - }} - + +
+ + + {{ + t('components.databaseBrowser.erDiagram.zoom', { + percentage: Math.round(zoomLevel * 100), + }) + }} + + +
@@ -672,7 +752,9 @@ onMounted(() => { {{ col.name }} {{ col.data_type }} - 🔑 + + + 🔗 - + > + + + + + diff --git a/src/lang/enUS.ts b/src/lang/enUS.ts index 9c0a2f1..ddd98d4 100644 --- a/src/lang/enUS.ts +++ b/src/lang/enUS.ts @@ -1117,7 +1117,10 @@ export const enUS = { }, erDiagram: { title: 'ER Diagram', - zoom: 'Zoom: {percentage}%', + zoom: '{percentage}%', + zoomIn: 'Zoom In', + zoomOut: 'Zoom Out', + allSchemas: 'All Schemas', fitToScreen: 'Fit to Screen', searchPlaceholder: 'Filter tables...', largeSchemaWarning: 'This schema has {count} tables. Displaying all may be dense. Continue?', diff --git a/src/lang/zhCN.ts b/src/lang/zhCN.ts index 3da97da..78eee86 100644 --- a/src/lang/zhCN.ts +++ b/src/lang/zhCN.ts @@ -1117,7 +1117,10 @@ export const zhCN = { }, erDiagram: { title: 'ER图表', - zoom: '缩放: {percentage}%', + zoom: '{percentage}%', + zoomIn: '放大', + zoomOut: '缩小', + allSchemas: '所有模式', fitToScreen: '适应屏幕', searchPlaceholder: '过滤表...', largeSchemaWarning: '此模式包含 {count} 张表,显示全部可能会过于密集,是否继续?', From b48ba5d00232e0086572111ed6af9258c8f0955b Mon Sep 17 00:00:00 2001 From: blankll Date: Wed, 22 Jul 2026 23:36:41 +0800 Subject: [PATCH 07/26] fix(er-diagram): fetch schemas from backend if not cached Schema selector was not showing because available schemas may not be populated in the store yet. Add fallback to call databaseStore.fetchSchemas when cache is empty. Co-authored-by: Sisyphus --- src/components/er-diagram/ErDiagramView.vue | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/components/er-diagram/ErDiagramView.vue b/src/components/er-diagram/ErDiagramView.vue index 815126c..c213a24 100644 --- a/src/components/er-diagram/ErDiagramView.vue +++ b/src/components/er-diagram/ErDiagramView.vue @@ -273,8 +273,15 @@ async function fetchAvailableSchemas() { return try { const meta = databaseStore.metadata[props.connectionId] - if (meta?.schemas[props.database]) { - availableSchemas.value = meta.schemas[props.database] + const cached = meta?.schemas[props.database] + if (cached && cached.length > 0) { + availableSchemas.value = cached + } + else { + // Not cached yet — fetch from backend + await databaseStore.fetchSchemas(props.connectionId, props.database) + const refreshed = databaseStore.metadata[props.connectionId]?.schemas[props.database] + availableSchemas.value = refreshed ?? [] } } catch { From 7ad462570ebcece87949eed422b772942e383f71 Mon Sep 17 00:00:00 2001 From: blankll Date: Wed, 22 Jul 2026 23:39:36 +0800 Subject: [PATCH 08/26] fix(er-diagram): fix Select empty value issue and reorder zoom buttons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace empty string '' with '__all__' for the 'all schemas' option to avoid Radix Vue Select issues - Reorder zoom controls to: zoom out → zoom in → percentage (matching user expectation) - Add z-[100] to SelectContent to ensure dropdown renders above other elements - Add schemaParam computed to map '__all__' → null for API calls Co-authored-by: Sisyphus --- src/components/er-diagram/ErDiagramView.vue | 37 ++++++++++++--------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/components/er-diagram/ErDiagramView.vue b/src/components/er-diagram/ErDiagramView.vue index c213a24..9b19913 100644 --- a/src/components/er-diagram/ErDiagramView.vue +++ b/src/components/er-diagram/ErDiagramView.vue @@ -107,15 +107,20 @@ const svgContainerRef = ref(null) // ─── Schema selector ────────────────────────────── const databaseStore = useDatabaseStore() const availableSchemas = ref([]) -const localSchema = ref(props.schema ?? '') +const localSchema = ref(props.schema ?? '__all__') const supportsSchemas = computed(() => availableSchemas.value.length > 0, ) +// Map local schema value to API parameter: '__all__' → null (fetch all) +const schemaParam = computed(() => + localSchema.value === '__all__' ? null : localSchema.value, +) + watch(() => props.schema, (val) => { if (val !== undefined) - localSchema.value = val + localSchema.value = val ?? '__all__' }) // ─── Computed ───────────────────────────────────────── @@ -292,7 +297,7 @@ async function fetchAvailableSchemas() { async function fetchSchemaData() { loading.value = true try { - const schema = localSchema.value || null + const schema = schemaParam.value // Fetch tables and foreign keys in parallel const [tableList, fkList] = await Promise.all([ @@ -440,12 +445,12 @@ onMounted(async () => {
- - - + + {{ t('components.databaseBrowser.erDiagram.allSchemas') }} { - +
- - {{ - t('components.databaseBrowser.erDiagram.zoom', { - percentage: Math.round(zoomLevel * 100), - }) - }} - + + {{ + t('components.databaseBrowser.erDiagram.zoom', { + percentage: Math.round(zoomLevel * 100), + }) + }} +
From eea670bcf440f8788a4507cdc17e1c4ff6a5aa10 Mon Sep 17 00:00:00 2001 From: blankll Date: Wed, 22 Jul 2026 23:40:09 +0800 Subject: [PATCH 09/26] fix(er-diagram): fetch schemas via direct invoke instead of store Use invoke('list_schemas', ...) directly to avoid any Pinia store reactivity issues when the store hasn't cached schemas for the current database yet. Co-authored-by: Sisyphus --- src/components/er-diagram/ErDiagramView.vue | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/components/er-diagram/ErDiagramView.vue b/src/components/er-diagram/ErDiagramView.vue index 9b19913..63ea781 100644 --- a/src/components/er-diagram/ErDiagramView.vue +++ b/src/components/er-diagram/ErDiagramView.vue @@ -281,12 +281,18 @@ async function fetchAvailableSchemas() { const cached = meta?.schemas[props.database] if (cached && cached.length > 0) { availableSchemas.value = cached + return } - else { - // Not cached yet — fetch from backend - await databaseStore.fetchSchemas(props.connectionId, props.database) - const refreshed = databaseStore.metadata[props.connectionId]?.schemas[props.database] - availableSchemas.value = refreshed ?? [] + // Not cached — fetch from backend directly + const result = await invoke('list_schemas', { + connectionId: props.connectionId, + database: props.database, + }) + availableSchemas.value = result ?? [] + // Also cache in store for future use + const m = databaseStore.metadata[props.connectionId] + if (m && result) { + m.schemas[props.database] = result } } catch { From f292a449f6d4767d0db646a49d252cc2afbd6301 Mon Sep 17 00:00:00 2001 From: blankll Date: Wed, 22 Jul 2026 23:49:13 +0800 Subject: [PATCH 10/26] fix(er-diagram): fix lint error - remove useless v-bind on string literal Co-authored-by: Sisyphus --- src/components/er-diagram/ErDiagramView.vue | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/er-diagram/ErDiagramView.vue b/src/components/er-diagram/ErDiagramView.vue index 63ea781..e51a52d 100644 --- a/src/components/er-diagram/ErDiagramView.vue +++ b/src/components/er-diagram/ErDiagramView.vue @@ -455,7 +455,7 @@ onMounted(async () => { - + {{ t('components.databaseBrowser.erDiagram.allSchemas') }} @@ -593,7 +593,7 @@ onMounted(async () => { :title="t('components.databaseBrowser.erDiagram.zoomOut')" @click="zoomLevel = Math.max(0.1, +(zoomLevel / 1.3).toFixed(2))" > - + {{ t('components.databaseBrowser.erDiagram.zoom', { @@ -771,7 +771,7 @@ onMounted(async () => { {{ col.data_type }} - + - + - +
From 162699253ff89fb4b41fe7000e371a870aa5d9ec Mon Sep 17 00:00:00 2001 From: blankll Date: Wed, 22 Jul 2026 23:59:43 +0800 Subject: [PATCH 11/26] fix(er-diagram): improve interaction, add node dragging, fix layout issues - Click: remove opacity dimming on non-selected tables, just highlight selected + connected table cards - Drag: add mouse-drag to reposition table nodes on the canvas - Expand button: add whitespace-nowrap and increase button height to prevent truncation - Nullable indicator: remove confusing circle icon (NOT NULL is the default assumption) - Edge dimming: remove opacity reduction on non-highlighted edges Co-authored-by: Sisyphus --- src/components/er-diagram/ErDiagramView.vue | 74 ++++++++++++++++----- 1 file changed, 56 insertions(+), 18 deletions(-) diff --git a/src/components/er-diagram/ErDiagramView.vue b/src/components/er-diagram/ErDiagramView.vue index e51a52d..f7186e6 100644 --- a/src/components/er-diagram/ErDiagramView.vue +++ b/src/components/er-diagram/ErDiagramView.vue @@ -70,7 +70,7 @@ type RenderEdge = { const NODE_WIDTH = 220 const COL_HEIGHT = 28 const HEADER_HEIGHT = 36 -const EXPAND_BTN_HEIGHT = 28 +const EXPAND_BTN_HEIGHT = 30 const CARD_PADDING = 8 function calcNodeHeight(table: TableData, isExpanded: boolean): number { @@ -104,6 +104,11 @@ const error = ref(null) const showWarning = ref(false) const svgContainerRef = ref(null) +// ─── Node drag state ────────────────────────────── +const draggingNodeId = ref(null) +const dragStartPos = ref({ x: 0, y: 0 }) +const dragNodeStart = ref({ x: 0, y: 0 }) + // ─── Schema selector ────────────────────────────── const databaseStore = useDatabaseStore() const availableSchemas = ref([]) @@ -394,6 +399,42 @@ function endPan() { isPanning.value = false } +// ─── Node Drag Handlers ─────────────────────────── +function onNodeMouseDown(e: MouseEvent, nodeId: string) { + if (e.button !== 0) + return + const pos = nodePositions.value.get(nodeId) + if (!pos) + return + draggingNodeId.value = nodeId + dragStartPos.value = { x: e.clientX, y: e.clientY } + dragNodeStart.value = { x: pos.x, y: pos.y } + document.addEventListener('mousemove', onNodeMouseMove) + document.addEventListener('mouseup', onNodeMouseUp) +} + +function onNodeMouseMove(e: MouseEvent) { + if (!draggingNodeId.value) + return + const dx = (e.clientX - dragStartPos.value.x) / zoomLevel.value + const dy = (e.clientY - dragStartPos.value.y) / zoomLevel.value + const positions = new Map(nodePositions.value) + const current = positions.get(draggingNodeId.value) + if (current) { + positions.set(draggingNodeId.value, { + x: dragNodeStart.value.x + dx, + y: dragNodeStart.value.y + dy, + }) + nodePositions.value = positions + } +} + +function onNodeMouseUp() { + draggingNodeId.value = null + document.removeEventListener('mousemove', onNodeMouseMove) + document.removeEventListener('mouseup', onNodeMouseUp) +} + function onWheel(e: WheelEvent) { const delta = -e.deltaY / 500 zoomLevel.value = Math.max(0.1, Math.min(3, zoomLevel.value * (1 + delta))) @@ -733,7 +774,6 @@ onMounted(async () => { : 'hsl(var(--muted-foreground))' " :stroke-width="edge.isHighlighted ? 2 : 1" - :opacity="edge.isHighlighted ? 1 : 0.4" marker-end="url(#er-arrowhead)" /> @@ -743,7 +783,9 @@ onMounted(async () => { v-for="node in renderNodes" :key="node.id" :transform="`translate(${node.x}, ${node.y})`" - :opacity="selectedTableId ? (node.isHighlighted ? 1 : 0.3) : 1" + class="er-table-group" + :class="{ 'er-table-group--dragging': draggingNodeId === node.id }" + @mousedown.prevent="onNodeMouseDown($event, node.id)" @click.stop="selectTable(node.id)" > @@ -751,8 +793,6 @@ onMounted(async () => { class="er-table-card" :class="{ 'er-table-card--selected': selectedTableId === node.id, - 'er-table-card--dimmed': - selectedTableId && !node.isHighlighted, }" > @@ -785,9 +825,7 @@ onMounted(async () => { > - - - + @@ -795,7 +833,7 @@ onMounted(async () => {