@antelopejs/dms-database 0.0.13 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -5
- package/dist/db/models/query_history.model.d.ts +1 -0
- package/dist/db/models/query_history.model.js +6 -0
- package/dist/db/models/query_history.model.js.map +1 -1
- package/dist/db/tables/query_history.table.d.ts +3 -0
- package/dist/db/tables/query_history.table.js +12 -0
- package/dist/db/tables/query_history.table.js.map +1 -1
- package/dist/db/tables/saved_queries.table.d.ts +1 -0
- package/dist/db/tables/saved_queries.table.js +4 -0
- package/dist/db/tables/saved_queries.table.js.map +1 -1
- package/dist/i18n/messages.d.ts +39 -0
- package/dist/i18n/messages.js +64 -0
- package/dist/i18n/messages.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -7
- package/dist/index.js.map +1 -1
- package/dist/module.d.ts +11 -0
- package/dist/module.js +55 -0
- package/dist/module.js.map +1 -0
- package/dist/pages/data.d.ts +1 -1
- package/dist/pages/data.js +9 -6
- package/dist/pages/data.js.map +1 -1
- package/dist/pages/diagram.d.ts +9 -0
- package/dist/pages/diagram.js +45 -0
- package/dist/pages/diagram.js.map +1 -0
- package/dist/pages/index.d.ts +1 -0
- package/dist/pages/index.js +1 -0
- package/dist/pages/index.js.map +1 -1
- package/dist/pages/overview.d.ts +4 -1
- package/dist/pages/overview.js +113 -6
- package/dist/pages/overview.js.map +1 -1
- package/dist/pages/query.d.ts +1 -1
- package/dist/pages/query.js +22 -6
- package/dist/pages/query.js.map +1 -1
- package/dist/pages/schemas.d.ts +2 -1
- package/dist/pages/schemas.js +143 -6
- package/dist/pages/schemas.js.map +1 -1
- package/dist/routes/data.d.ts +8 -0
- package/dist/routes/data.js +84 -21
- package/dist/routes/data.js.map +1 -1
- package/dist/routes/index.d.ts +1 -0
- package/dist/routes/index.js +1 -0
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/overview.d.ts +12 -0
- package/dist/routes/overview.js +55 -0
- package/dist/routes/overview.js.map +1 -0
- package/dist/routes/query.d.ts +5 -1
- package/dist/routes/query.js +162 -27
- package/dist/routes/query.js.map +1 -1
- package/dist/routes/tables.d.ts +15 -12
- package/dist/routes/tables.js +95 -55
- package/dist/routes/tables.js.map +1 -1
- package/dist/service/catalog.d.ts +3 -0
- package/dist/service/catalog.js +26 -0
- package/dist/service/catalog.js.map +1 -0
- package/dist/service/health.d.ts +3 -3
- package/dist/service/health.js +17 -14
- package/dist/service/health.js.map +1 -1
- package/dist/service/introspect.d.ts +3 -1
- package/dist/service/introspect.js +131 -43
- package/dist/service/introspect.js.map +1 -1
- package/dist/service/overview.d.ts +9 -0
- package/dist/service/overview.js +113 -0
- package/dist/service/overview.js.map +1 -0
- package/dist/service/queryInspection.d.ts +15 -0
- package/dist/service/queryInspection.js +107 -0
- package/dist/service/queryInspection.js.map +1 -0
- package/dist/service/references.d.ts +9 -0
- package/dist/service/references.js +52 -0
- package/dist/service/references.js.map +1 -0
- package/dist/service/schemaFilters.d.ts +26 -0
- package/dist/service/schemaFilters.js +42 -0
- package/dist/service/schemaFilters.js.map +1 -0
- package/dist/service/stagedSerialization.d.ts +2 -0
- package/dist/service/stagedSerialization.js +38 -10
- package/dist/service/stagedSerialization.js.map +1 -1
- package/dist/service/types.d.ts +11 -1
- package/dist/types/constants.d.ts +2 -0
- package/dist/types/constants.js +3 -1
- package/dist/types/constants.js.map +1 -1
- package/dist/utils/query.d.ts +0 -2
- package/dist/utils/query.js +0 -20
- package/dist/utils/query.js.map +1 -1
- package/frontend-vue/app/{composables → build/composables}/useDataBrowserGrid.ts +65 -50
- package/frontend-vue/app/build/composables/useDataBrowserTabs.ts +366 -0
- package/frontend-vue/app/build/composables/useDatabaseHealth.ts +50 -0
- package/frontend-vue/app/build/composables/useDatabaseSchemas.ts +140 -0
- package/frontend-vue/app/{composables → build/composables}/useDiagramPersistence.ts +47 -39
- package/frontend-vue/app/build/composables/useQueryRuntime.ts +217 -0
- package/frontend-vue/app/build/composables/useQueryStore.ts +162 -0
- package/frontend-vue/app/build/data/cellValues.ts +132 -0
- package/frontend-vue/app/build/data/instanceOptions.ts +36 -0
- package/frontend-vue/app/build/data/pickerMenus.ts +175 -0
- package/frontend-vue/app/build/data/recentTables.ts +48 -0
- package/frontend-vue/app/build/data/stagedEdits.ts +117 -0
- package/frontend-vue/app/build/data/stagedWrites.ts +64 -0
- package/frontend-vue/app/build/data/useStagedSave.ts +298 -0
- package/frontend-vue/app/build/diagram/autosave.ts +72 -0
- package/frontend-vue/app/build/diagram/graph.ts +585 -0
- package/frontend-vue/app/build/diagram/history.ts +178 -0
- package/frontend-vue/app/build/diagram/shortcuts.ts +62 -0
- package/frontend-vue/app/build/diagram/sync.ts +137 -0
- package/frontend-vue/app/build/query/completion.ts +100 -0
- package/frontend-vue/app/build/query/csv.ts +32 -0
- package/frontend-vue/app/build/query/examples.ts +38 -0
- package/frontend-vue/app/build/query/runs.ts +168 -0
- package/frontend-vue/app/build/query/tableColumns.ts +35 -0
- package/frontend-vue/app/build/query/target.ts +143 -0
- package/frontend-vue/app/build/schemas/filterState.ts +128 -0
- package/frontend-vue/app/build/schemas/useSchemasListing.ts +62 -0
- package/frontend-vue/app/build/utils/databaseLinks.ts +98 -0
- package/frontend-vue/app/build/utils/fieldTypes.ts +198 -0
- package/frontend-vue/app/build/utils/relativeTime.ts +19 -0
- package/frontend-vue/app/{utils → build/utils}/stagedSerialization.ts +7 -1
- package/frontend-vue/app/components/ConnectionStatus.vue +122 -0
- package/frontend-vue/app/components/DataBrowser.vue +126 -0
- package/frontend-vue/app/components/DataBrowserSidebar.vue +293 -210
- package/frontend-vue/app/components/DataBrowserTabBar.vue +185 -93
- package/frontend-vue/app/components/DataFilterBar.vue +163 -0
- package/frontend-vue/app/components/DataGrid.vue +737 -499
- package/frontend-vue/app/components/DataGridCell.vue +125 -77
- package/frontend-vue/app/components/DataGridHeaderCell.vue +119 -81
- package/frontend-vue/app/components/DiagramNoteNode.vue +81 -63
- package/frontend-vue/app/components/DiagramRelationEdge.vue +57 -27
- package/frontend-vue/app/components/DiagramStubNode.vue +16 -17
- package/frontend-vue/app/components/DiagramTableNode.vue +106 -68
- package/frontend-vue/app/components/InstancePicker.vue +195 -0
- package/frontend-vue/app/components/JsonCellPanel.vue +30 -29
- package/frontend-vue/app/components/QueryBuilderPanel.vue +290 -238
- package/frontend-vue/app/components/QueryConsole.vue +726 -0
- package/frontend-vue/app/components/QueryLibrary.vue +314 -0
- package/frontend-vue/app/components/QueryResultPanel.vue +556 -126
- package/frontend-vue/app/components/RowDrawer.vue +520 -0
- package/frontend-vue/app/components/SchemaDiagram.vue +1016 -1051
- package/frontend-vue/app/components/SchemaPicker.vue +86 -0
- package/frontend-vue/app/components/SchemasFilterBar.vue +201 -0
- package/frontend-vue/app/components/TableInspector.vue +741 -0
- package/frontend-vue/app/config/shortcuts-registry.ts +65 -0
- package/frontend-vue/dms.frontend.ts +5 -14
- package/frontend-vue/i18n/locales/database-en-GB.json +526 -193
- package/frontend-vue/i18n/locales/database-fr-FR.json +528 -195
- package/frontend-vue/package.json +2 -1
- package/frontend-vue/pnpm-lock.yaml +2601 -858
- package/frontend-vue/pnpm-workspace.yaml +1 -1
- package/frontend-vue/tests/dataBrowser.test.ts +206 -0
- package/frontend-vue/tests/diagram.test.ts +516 -0
- package/frontend-vue/tests/helpers.test.ts +205 -0
- package/frontend-vue/tests/instanceOptions.test.ts +53 -0
- package/frontend-vue/tests/locales.test.ts +56 -0
- package/frontend-vue/tests/persistence.test.ts +106 -5
- package/frontend-vue/tests/pickerMenus.test.ts +254 -0
- package/frontend-vue/tests/queryConsole.test.ts +282 -0
- package/frontend-vue/tests/queryRuntime.test.ts +2 -2
- package/frontend-vue/tests/registration.test.ts +36 -25
- package/frontend-vue/tests/schemasFilters.test.ts +115 -0
- package/package.json +9 -6
- package/frontend-vue/app/components/QueryFavoritesPanel.vue +0 -127
- package/frontend-vue/app/components/QueryHistoryPanel.vue +0 -134
- package/frontend-vue/app/components/SchemaTableInspector.vue +0 -278
- package/frontend-vue/app/composables/useDataBrowserTabs.ts +0 -255
- package/frontend-vue/app/composables/useDatabaseHealth.ts +0 -50
- package/frontend-vue/app/composables/useDatabaseSchemas.ts +0 -92
- package/frontend-vue/app/composables/useQueryRuntime.ts +0 -118
- package/frontend-vue/app/composables/useQueryStore.ts +0 -117
- package/frontend-vue/app/composables/useTableViewRowClick.ts +0 -22
- package/frontend-vue/app/custom-pages/database/data.vue +0 -41
- package/frontend-vue/app/custom-pages/database/overview.vue +0 -367
- package/frontend-vue/app/custom-pages/database/query.vue +0 -436
- package/frontend-vue/app/custom-pages/database/schemas.vue +0 -218
- package/frontend-vue/app/utils/databaseFieldModifiers.ts +0 -33
- package/frontend-vue/app/utils/databaseFieldTypeIcons.ts +0 -224
- package/frontend-vue/app/utils/dmsTableView.ts +0 -83
- package/frontend-vue/app/utils/relativeTime.ts +0 -18
|
@@ -1,100 +1,118 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
text: string;
|
|
5
|
-
color: string | null;
|
|
6
|
-
width: number;
|
|
7
|
-
height: number;
|
|
8
|
-
onTextChange: (noteId: string, text: string) => void;
|
|
9
|
-
onDelete: (noteId: string) => void;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const props = defineProps<{ data: NoteNodeData; selected: boolean }>();
|
|
2
|
+
import { onClickOutside } from '@vueuse/core'
|
|
3
|
+
import type { NoteNodeData } from '../build/diagram/graph'
|
|
13
4
|
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const NOTE_PLACEHOLDER_INK = "rgba(66, 32, 6, 0.45)";
|
|
5
|
+
// A sticky note on the diagram: double-click to write, saved with the layout.
|
|
6
|
+
// A click anywhere else, or Escape, ends the edit.
|
|
7
|
+
const props = defineProps<{
|
|
8
|
+
data: NoteNodeData
|
|
9
|
+
selected?: boolean
|
|
10
|
+
/** Set by Vue Flow once it has measured the node. */
|
|
11
|
+
dimensions?: { width: number; height: number }
|
|
12
|
+
}>()
|
|
23
13
|
|
|
24
|
-
const
|
|
25
|
-
const
|
|
14
|
+
const root = useTemplateRef<HTMLElement>('root')
|
|
15
|
+
const textarea = useTemplateRef<HTMLTextAreaElement>('textarea')
|
|
16
|
+
const editing = ref(false)
|
|
17
|
+
const textBuffer = ref(props.data.text)
|
|
26
18
|
|
|
27
19
|
watch(
|
|
28
20
|
() => props.data.text,
|
|
29
|
-
(
|
|
30
|
-
if (!editing.value) textBuffer.value =
|
|
21
|
+
(value) => {
|
|
22
|
+
if (!editing.value) textBuffer.value = value
|
|
31
23
|
},
|
|
32
|
-
)
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
// Vue Flow shows a new node only once it has measured it: until then the
|
|
27
|
+
// field cannot take the focus. It is tried again when the node's size comes in.
|
|
28
|
+
let focusPending = false
|
|
29
|
+
|
|
30
|
+
function focusField() {
|
|
31
|
+
const field = textarea.value
|
|
32
|
+
if (!focusPending || !field || !editing.value) return
|
|
33
|
+
field.focus()
|
|
34
|
+
if (document.activeElement !== field) return
|
|
35
|
+
focusPending = false
|
|
36
|
+
field.setSelectionRange(field.value.length, field.value.length)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
watch(() => props.dimensions?.width, focusField, { flush: 'post' })
|
|
40
|
+
|
|
41
|
+
async function startEdit() {
|
|
42
|
+
if (editing.value) return
|
|
43
|
+
textBuffer.value = props.data.text
|
|
44
|
+
editing.value = true
|
|
45
|
+
focusPending = true
|
|
46
|
+
await nextTick()
|
|
47
|
+
focusField()
|
|
48
|
+
}
|
|
33
49
|
|
|
34
50
|
function commitText() {
|
|
35
|
-
editing.value
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
51
|
+
if (!editing.value) return
|
|
52
|
+
editing.value = false
|
|
53
|
+
focusPending = false
|
|
54
|
+
if (textBuffer.value !== props.data.text) {
|
|
55
|
+
props.data.onTextChange(props.data.noteId, textBuffer.value)
|
|
39
56
|
}
|
|
57
|
+
props.data.onEditEnd(props.data.noteId)
|
|
40
58
|
}
|
|
59
|
+
|
|
60
|
+
// The canvas swallows the pointer down that would blur the field: a click on
|
|
61
|
+
// the pane or a table has to end the edit itself.
|
|
62
|
+
onClickOutside(root, () => {
|
|
63
|
+
if (editing.value) commitText()
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
// A note just created opens ready to write.
|
|
67
|
+
onMounted(() => {
|
|
68
|
+
if (props.data.autoEdit) startEdit()
|
|
69
|
+
})
|
|
41
70
|
</script>
|
|
42
71
|
|
|
43
72
|
<template>
|
|
44
73
|
<div
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
width: `${data.width}px`,
|
|
51
|
-
height: `${data.height}px`,
|
|
52
|
-
}"
|
|
53
|
-
@dblclick="editing = true"
|
|
74
|
+
ref="root"
|
|
75
|
+
class="bg-warning/10 border-warning/40 text-toned relative flex flex-col overflow-hidden rounded-sm border shadow-sm backdrop-blur-sm"
|
|
76
|
+
:class="{ 'ring-warning ring-2': selected || editing }"
|
|
77
|
+
:style="{ width: `${data.width}px`, height: `${data.height}px` }"
|
|
78
|
+
@dblclick="startEdit"
|
|
54
79
|
>
|
|
55
|
-
<!-- Header strip: gives the note a recognisable sticky-note frame and
|
|
56
|
-
hosts the delete affordance without overlapping the text. -->
|
|
57
80
|
<div
|
|
58
|
-
class="flex
|
|
59
|
-
:style="{
|
|
60
|
-
background: NOTE_HEADER_BG,
|
|
61
|
-
borderBottom: `1px solid ${NOTE_HEADER_BORDER}`,
|
|
62
|
-
}"
|
|
81
|
+
class="text-warning flex h-7 shrink-0 cursor-grab items-center gap-1.5 px-3"
|
|
63
82
|
>
|
|
64
|
-
<UIcon
|
|
65
|
-
|
|
66
|
-
class="
|
|
67
|
-
|
|
68
|
-
|
|
83
|
+
<UIcon name="i-ph-note-pencil" class="size-3.5" />
|
|
84
|
+
<span
|
|
85
|
+
class="font-mono text-[10px] font-semibold uppercase tracking-[0.12em]"
|
|
86
|
+
>
|
|
87
|
+
{{ $t('dms_database.diagram.notes.label') }}
|
|
88
|
+
</span>
|
|
69
89
|
<button
|
|
70
|
-
v-if="selected"
|
|
90
|
+
v-if="selected || editing"
|
|
71
91
|
type="button"
|
|
72
|
-
class="
|
|
73
|
-
:style="{ color: NOTE_ACCENT }"
|
|
92
|
+
class="nodrag hover:bg-warning/20 ml-auto rounded p-0.5"
|
|
74
93
|
:aria-label="$t('dms_database.diagram.notes.delete')"
|
|
94
|
+
:title="$t('dms_database.diagram.notes.delete')"
|
|
95
|
+
@pointerdown.stop
|
|
75
96
|
@click.stop="data.onDelete(data.noteId)"
|
|
76
97
|
>
|
|
77
|
-
<UIcon name="i-ph-
|
|
98
|
+
<UIcon name="i-ph-trash" class="size-3.5" />
|
|
78
99
|
</button>
|
|
79
100
|
</div>
|
|
80
|
-
<!-- Body. Fixed dark ink so the text stays readable on the light note in
|
|
81
|
-
any app theme (the previous theme-aware text colour washed out). -->
|
|
82
101
|
<textarea
|
|
83
102
|
v-if="editing"
|
|
103
|
+
ref="textarea"
|
|
84
104
|
v-model="textBuffer"
|
|
85
|
-
class="flex-1
|
|
86
|
-
:style="{ color: NOTE_INK }"
|
|
105
|
+
class="nodrag nowheel w-full flex-1 resize-none bg-transparent px-3 pb-3 text-[12.5px] leading-normal outline-none"
|
|
87
106
|
:placeholder="$t('dms_database.diagram.notes.placeholder')"
|
|
88
|
-
autofocus
|
|
89
107
|
@blur="commitText"
|
|
90
|
-
@keydown.escape.prevent="commitText"
|
|
108
|
+
@keydown.escape.prevent.stop="commitText"
|
|
91
109
|
/>
|
|
92
110
|
<div
|
|
93
111
|
v-else
|
|
94
|
-
class="
|
|
95
|
-
:
|
|
112
|
+
class="w-full flex-1 cursor-text overflow-auto whitespace-pre-wrap break-words px-3 pb-3 text-[12.5px] leading-normal"
|
|
113
|
+
:class="data.text ? '' : 'text-dimmed'"
|
|
96
114
|
>
|
|
97
|
-
{{ data.text || $t(
|
|
115
|
+
{{ data.text || $t('dms_database.diagram.notes.placeholder') }}
|
|
98
116
|
</div>
|
|
99
117
|
</div>
|
|
100
118
|
</template>
|
|
@@ -1,34 +1,46 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
BaseEdge,
|
|
4
|
+
type EdgeProps,
|
|
5
|
+
Position,
|
|
6
|
+
getBezierPath,
|
|
7
|
+
} from '@vue-flow/core'
|
|
8
|
+
import { EDGE_ACTIVE_COLOR, type EdgeEnds } from '../build/diagram/graph'
|
|
3
9
|
|
|
4
|
-
|
|
10
|
+
// A relation column pointing at a table. The relations of the selected table
|
|
11
|
+
// are drawn in the primary colour; the others stay quiet.
|
|
12
|
+
const props = defineProps<EdgeProps<EdgeEnds>>()
|
|
5
13
|
|
|
6
|
-
const
|
|
7
|
-
|
|
14
|
+
const edgeStyle = computed(() =>
|
|
15
|
+
props.data?.active
|
|
16
|
+
? { ...(props.style as object), stroke: EDGE_ACTIVE_COLOR, strokeWidth: 2 }
|
|
17
|
+
: props.style,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
const ARC_MIN_OFFSET = 60
|
|
21
|
+
const ARC_DY_FACTOR = 0.35
|
|
8
22
|
|
|
9
23
|
function sideSign(position: Position): 1 | -1 | 0 {
|
|
10
|
-
if (position === Position.Right) return 1
|
|
11
|
-
if (position === Position.Left) return -1
|
|
12
|
-
return 0
|
|
24
|
+
if (position === Position.Right) return 1
|
|
25
|
+
if (position === Position.Left) return -1
|
|
26
|
+
return 0
|
|
13
27
|
}
|
|
14
28
|
|
|
15
29
|
const computedPath = computed<[string, number, number]>(() => {
|
|
16
|
-
const sourceSign = sideSign(props.sourcePosition)
|
|
17
|
-
const targetSign = sideSign(props.targetPosition)
|
|
30
|
+
const sourceSign = sideSign(props.sourcePosition)
|
|
31
|
+
const targetSign = sideSign(props.targetPosition)
|
|
18
32
|
if (sourceSign !== 0 && targetSign !== 0) {
|
|
19
|
-
const dy = Math.abs(props.targetY - props.sourceY)
|
|
20
|
-
const offset = Math.max(ARC_MIN_OFFSET, dy * ARC_DY_FACTOR)
|
|
21
|
-
const cx1 = props.sourceX + sourceSign * offset
|
|
22
|
-
const cy1 = props.sourceY
|
|
23
|
-
const cx2 = props.targetX + targetSign * offset
|
|
24
|
-
const cy2 = props.targetY
|
|
25
|
-
const d = `M${props.sourceX},${props.sourceY} C${cx1},${cy1} ${cx2},${cy2} ${props.targetX},${props.targetY}
|
|
33
|
+
const dy = Math.abs(props.targetY - props.sourceY)
|
|
34
|
+
const offset = Math.max(ARC_MIN_OFFSET, dy * ARC_DY_FACTOR)
|
|
35
|
+
const cx1 = props.sourceX + sourceSign * offset
|
|
36
|
+
const cy1 = props.sourceY
|
|
37
|
+
const cx2 = props.targetX + targetSign * offset
|
|
38
|
+
const cy2 = props.targetY
|
|
39
|
+
const d = `M${props.sourceX},${props.sourceY} C${cx1},${cy1} ${cx2},${cy2} ${props.targetX},${props.targetY}`
|
|
26
40
|
// Label sits at the bezier midpoint (t = 0.5).
|
|
27
|
-
const labelX =
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
(props.sourceY + 3 * cy1 + 3 * cy2 + props.targetY) / 8;
|
|
31
|
-
return [d, labelX, labelY];
|
|
41
|
+
const labelX = (props.sourceX + 3 * cx1 + 3 * cx2 + props.targetX) / 8
|
|
42
|
+
const labelY = (props.sourceY + 3 * cy1 + 3 * cy2 + props.targetY) / 8
|
|
43
|
+
return [d, labelX, labelY]
|
|
32
44
|
}
|
|
33
45
|
const [d, lx, ly] = getBezierPath({
|
|
34
46
|
sourceX: props.sourceX,
|
|
@@ -37,9 +49,9 @@ const computedPath = computed<[string, number, number]>(() => {
|
|
|
37
49
|
targetX: props.targetX,
|
|
38
50
|
targetY: props.targetY,
|
|
39
51
|
targetPosition: props.targetPosition,
|
|
40
|
-
})
|
|
41
|
-
return [d, lx, ly]
|
|
42
|
-
})
|
|
52
|
+
})
|
|
53
|
+
return [d, lx, ly]
|
|
54
|
+
})
|
|
43
55
|
</script>
|
|
44
56
|
|
|
45
57
|
<template>
|
|
@@ -48,7 +60,7 @@ const computedPath = computed<[string, number, number]>(() => {
|
|
|
48
60
|
:path="computedPath[0]"
|
|
49
61
|
:marker-end="markerEnd"
|
|
50
62
|
:marker-start="markerStart"
|
|
51
|
-
:style="
|
|
63
|
+
:style="edgeStyle"
|
|
52
64
|
:label-x="computedPath[1]"
|
|
53
65
|
:label-y="computedPath[2]"
|
|
54
66
|
:label="undefined"
|
|
@@ -56,13 +68,31 @@ const computedPath = computed<[string, number, number]>(() => {
|
|
|
56
68
|
/>
|
|
57
69
|
<text
|
|
58
70
|
v-if="label"
|
|
59
|
-
class="
|
|
71
|
+
class="diagram-edge-label"
|
|
72
|
+
:class="{ 'diagram-edge-label--active': data?.active }"
|
|
60
73
|
:x="computedPath[1]"
|
|
61
74
|
:y="computedPath[2]"
|
|
62
75
|
text-anchor="middle"
|
|
63
76
|
dominant-baseline="middle"
|
|
64
|
-
:style="labelStyle"
|
|
65
77
|
>
|
|
66
78
|
{{ label }}
|
|
67
79
|
</text>
|
|
68
80
|
</template>
|
|
81
|
+
|
|
82
|
+
<style scoped>
|
|
83
|
+
/* Theme tokens, with a halo of the canvas colour so the line that crosses
|
|
84
|
+
the label does not run through its letters. */
|
|
85
|
+
.diagram-edge-label {
|
|
86
|
+
fill: var(--ui-text-muted);
|
|
87
|
+
stroke: var(--ui-bg-muted);
|
|
88
|
+
stroke-width: 4px;
|
|
89
|
+
stroke-linejoin: round;
|
|
90
|
+
paint-order: stroke;
|
|
91
|
+
font: 500 10.5px var(--font-mono, ui-monospace, monospace);
|
|
92
|
+
pointer-events: none;
|
|
93
|
+
user-select: none;
|
|
94
|
+
}
|
|
95
|
+
.diagram-edge-label--active {
|
|
96
|
+
fill: var(--ui-primary);
|
|
97
|
+
}
|
|
98
|
+
</style>
|
|
@@ -1,31 +1,30 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { Handle, Position } from
|
|
2
|
+
import { Handle, Position } from '@vue-flow/core'
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
targetSchema: string;
|
|
6
|
-
targetTable: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
defineProps<{ data: StubNodeData }>();
|
|
4
|
+
import type { StubNodeData } from '../build/diagram/graph'
|
|
10
5
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
router.replace({ query: { ...route.query, schema: schemaId } });
|
|
16
|
-
}
|
|
6
|
+
// A table a relation points to that is not drawn: one of another schema, or
|
|
7
|
+
// one the focus leaves out. The dashed chip opens it: in its own schema's
|
|
8
|
+
// diagram, or here, out of the focus.
|
|
9
|
+
defineProps<{ data: StubNodeData }>()
|
|
17
10
|
</script>
|
|
18
11
|
|
|
19
12
|
<template>
|
|
20
13
|
<button
|
|
21
14
|
type="button"
|
|
22
|
-
class="diagram-stub-node flex items-center gap-1
|
|
23
|
-
|
|
15
|
+
class="diagram-stub-node border-accented bg-muted text-muted hover:border-primary hover:text-highlighted relative flex h-7 items-center gap-1.5 rounded-sm border border-dashed px-2.5 font-mono text-[11.5px] transition-colors"
|
|
16
|
+
:title="
|
|
17
|
+
$t('dms_database.diagram.stub_title', {
|
|
18
|
+
schema: data.targetSchema,
|
|
19
|
+
table: data.targetTable,
|
|
20
|
+
})
|
|
21
|
+
"
|
|
22
|
+
@click="data.onOpenSchema(data.targetSchema, data.targetTable)"
|
|
24
23
|
>
|
|
25
24
|
<Handle id="stub::left::target" type="target" :position="Position.Left" />
|
|
26
25
|
<Handle id="stub::right::target" type="target" :position="Position.Right" />
|
|
27
|
-
<UIcon name="i-ph-arrow-
|
|
28
|
-
<span
|
|
26
|
+
<UIcon name="i-ph-arrow-square-out" class="text-dimmed size-3.5" />
|
|
27
|
+
<span>{{ data.targetSchema }}.{{ data.targetTable }}</span>
|
|
29
28
|
</button>
|
|
30
29
|
</template>
|
|
31
30
|
|
|
@@ -1,86 +1,124 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { Handle, Position } from
|
|
2
|
+
import { Handle, Position } from '@vue-flow/core'
|
|
3
|
+
import type { TableNodeData } from '../build/diagram/graph'
|
|
4
|
+
import {
|
|
5
|
+
COLUMN_ROLE_CLASSES,
|
|
6
|
+
type ColumnRole,
|
|
7
|
+
describeField,
|
|
8
|
+
describeModifier,
|
|
9
|
+
isPrimaryKey,
|
|
10
|
+
} from '../build/utils/fieldTypes'
|
|
3
11
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
// One table of the diagram (D-09): each column with its type as mono text and
|
|
13
|
+
// a neutral icon. Colour only marks structure: the primary key, and the
|
|
14
|
+
// relation columns, which light up with the selected table's relations.
|
|
15
|
+
|
|
16
|
+
// The canvas keeps the selection in `data.selected`: Vue Flow's own one is
|
|
17
|
+
// off for tables, so one table at most is drawn selected.
|
|
18
|
+
const props = defineProps<{ data: TableNodeData }>()
|
|
19
|
+
|
|
20
|
+
const SIDES = ['left', 'right'] as const
|
|
21
|
+
const KINDS = ['source', 'target'] as const
|
|
22
|
+
const HANDLE_POSITIONS = { left: Position.Left, right: Position.Right } as const
|
|
10
23
|
|
|
11
|
-
|
|
24
|
+
const relationTargets = computed(
|
|
25
|
+
() =>
|
|
26
|
+
new Map(
|
|
27
|
+
props.data.table.relations.map((relation) => [
|
|
28
|
+
relation.fromField,
|
|
29
|
+
relation,
|
|
30
|
+
]),
|
|
31
|
+
),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
const rows = computed(() =>
|
|
35
|
+
Object.entries(props.data.table.fields).map(([name, descriptor]) => {
|
|
36
|
+
const key = isPrimaryKey(name, props.data.table.indexes ?? {})
|
|
37
|
+
const relation = relationTargets.value.get(name)
|
|
38
|
+
const role: ColumnRole = key ? 'key' : relation ? 'relation' : 'plain'
|
|
39
|
+
const type = describeField(descriptor, name)
|
|
40
|
+
return {
|
|
41
|
+
name,
|
|
42
|
+
role,
|
|
43
|
+
icon: key ? 'i-ph-key' : relation ? 'i-ph-arrow-right' : type.icon,
|
|
44
|
+
type: relation ? relation.toTable : type.label,
|
|
45
|
+
modifiers: (props.data.table.modifiers?.[name] ?? []).map(
|
|
46
|
+
describeModifier,
|
|
47
|
+
),
|
|
48
|
+
highlighted: props.data.highlighted.includes(name),
|
|
49
|
+
}
|
|
50
|
+
}),
|
|
51
|
+
)
|
|
12
52
|
</script>
|
|
13
53
|
|
|
14
54
|
<template>
|
|
15
|
-
<div
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
55
|
+
<div
|
|
56
|
+
class="diagram-table-node bg-default w-[232px] cursor-grab rounded-md border text-xs shadow-sm transition-[opacity,border-color,box-shadow]"
|
|
57
|
+
:class="[
|
|
58
|
+
data.selected
|
|
59
|
+
? 'border-primary shadow-[0_0_0_3px_var(--ui-color-primary-500)]/20 ring-primary/30 ring-2'
|
|
60
|
+
: 'border-accented',
|
|
61
|
+
data.dimmed ? 'opacity-45' : '',
|
|
62
|
+
]"
|
|
63
|
+
>
|
|
64
|
+
<template v-for="side in SIDES" :key="side">
|
|
65
|
+
<Handle
|
|
66
|
+
v-for="kind in KINDS"
|
|
67
|
+
:id="`__node__::${side}::${kind}`"
|
|
68
|
+
:key="kind"
|
|
69
|
+
:type="kind"
|
|
70
|
+
:position="HANDLE_POSITIONS[side]"
|
|
71
|
+
/>
|
|
72
|
+
</template>
|
|
21
73
|
|
|
22
|
-
<div
|
|
23
|
-
|
|
24
|
-
|
|
74
|
+
<div
|
|
75
|
+
class="border-default flex h-9 items-center gap-2 border-b pl-3 pr-2.5"
|
|
76
|
+
>
|
|
77
|
+
<UIcon name="i-ph-table" class="text-primary size-[15px] shrink-0" />
|
|
78
|
+
<span
|
|
79
|
+
class="text-highlighted truncate font-mono text-[12.5px] font-semibold"
|
|
80
|
+
>
|
|
81
|
+
{{ data.tableName }}
|
|
82
|
+
</span>
|
|
25
83
|
</div>
|
|
26
84
|
|
|
27
85
|
<ul class="py-1">
|
|
28
86
|
<li
|
|
29
|
-
v-for="
|
|
30
|
-
:key="
|
|
31
|
-
class="relative flex items-center
|
|
87
|
+
v-for="row in rows"
|
|
88
|
+
:key="row.name"
|
|
89
|
+
class="relative flex h-6 items-center gap-1.5 pl-3 pr-2.5 font-mono"
|
|
90
|
+
:class="[
|
|
91
|
+
row.role === 'relation' ? 'text-primary' : 'text-toned',
|
|
92
|
+
row.highlighted ? 'bg-primary/10' : '',
|
|
93
|
+
]"
|
|
32
94
|
>
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
95
|
+
<template v-for="side in SIDES" :key="side">
|
|
96
|
+
<Handle
|
|
97
|
+
v-for="kind in KINDS"
|
|
98
|
+
:id="`${row.name}::${side}::${kind}`"
|
|
99
|
+
:key="kind"
|
|
100
|
+
:type="kind"
|
|
101
|
+
:position="HANDLE_POSITIONS[side]"
|
|
102
|
+
/>
|
|
103
|
+
</template>
|
|
104
|
+
<UIcon
|
|
105
|
+
:name="row.icon"
|
|
106
|
+
class="size-3 shrink-0"
|
|
107
|
+
:class="
|
|
108
|
+
row.role === 'plain' ? 'text-dimmed' : COLUMN_ROLE_CLASSES[row.role]
|
|
109
|
+
"
|
|
47
110
|
/>
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
:
|
|
111
|
+
<span class="truncate">{{ row.name }}</span>
|
|
112
|
+
<UIcon
|
|
113
|
+
v-for="modifier in row.modifiers"
|
|
114
|
+
:key="modifier.id"
|
|
115
|
+
:name="modifier.icon"
|
|
116
|
+
class="text-dimmed size-3 shrink-0"
|
|
117
|
+
:title="modifier.labelKey ? $t(modifier.labelKey) : modifier.id"
|
|
52
118
|
/>
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
<UTooltip
|
|
56
|
-
v-if="isPrimaryKey(String(fieldName), data.indexes)"
|
|
57
|
-
text="Primary key"
|
|
58
|
-
>
|
|
59
|
-
<UIcon name="i-ph-key" class="text-amber-500 size-4 shrink-0" />
|
|
60
|
-
</UTooltip>
|
|
61
|
-
<template
|
|
62
|
-
v-for="modifierId in data.modifiers?.[String(fieldName)] ?? []"
|
|
63
|
-
:key="modifierId"
|
|
64
|
-
>
|
|
65
|
-
<UTooltip :text="resolveModifierIcon(modifierId).label">
|
|
66
|
-
<UIcon
|
|
67
|
-
:name="resolveModifierIcon(modifierId).icon"
|
|
68
|
-
:class="[resolveModifierIcon(modifierId).color, 'size-4 shrink-0']"
|
|
69
|
-
/>
|
|
70
|
-
</UTooltip>
|
|
71
|
-
</template>
|
|
72
|
-
<span class="truncate ml-1">{{ fieldName }}</span>
|
|
119
|
+
<span class="text-dimmed ml-auto max-w-24 truncate pl-2 text-[10.5px]">
|
|
120
|
+
{{ row.type }}
|
|
73
121
|
</span>
|
|
74
|
-
|
|
75
|
-
<UTooltip :text="inferFieldTypeIcon(fieldType, String(fieldName)).label">
|
|
76
|
-
<UIcon
|
|
77
|
-
:name="inferFieldTypeIcon(fieldType, String(fieldName)).icon"
|
|
78
|
-
:class="[
|
|
79
|
-
inferFieldTypeIcon(fieldType, String(fieldName)).color,
|
|
80
|
-
'size-4 shrink-0',
|
|
81
|
-
]"
|
|
82
|
-
/>
|
|
83
|
-
</UTooltip>
|
|
84
122
|
</li>
|
|
85
123
|
</ul>
|
|
86
124
|
</div>
|