sqlite-hub 2.0.1 → 2.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 +14 -9
- package/docs/MCP.md +8 -0
- package/docs/changelog.md +10 -0
- package/docs/guidelines/DESIGN.md +64 -0
- package/frontend/assets/mockups/backups_1_1920.webp +0 -0
- package/frontend/assets/mockups/backups_2_compare_drawer_1920.webp +0 -0
- package/frontend/assets/mockups/backups_3_create_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_4_edit_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_5_restore_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_6_delete_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/charts_1_1920.webp +0 -0
- package/frontend/assets/mockups/charts_2_query_detail_1920.webp +0 -0
- package/frontend/assets/mockups/charts_3_create_query_chart_modal_1920.webp +0 -0
- package/frontend/assets/mockups/charts_4_edit_query_chart_modal_1920.webp +0 -0
- package/frontend/assets/mockups/charts_5_delete_query_chart_modal_1920.webp +0 -0
- package/frontend/assets/mockups/charts_6_copy_column_modal_1920.webp +0 -0
- package/frontend/assets/mockups/connections_1_1920.webp +0 -0
- package/frontend/assets/mockups/connections_2_create_connection_modal_1920.webp +0 -0
- package/frontend/assets/mockups/connections_3_open_connection_modal_1920.webp +0 -0
- package/frontend/assets/mockups/data_1_1920.webp +0 -0
- package/frontend/assets/mockups/data_2_roweditor_1920.webp +0 -0
- package/frontend/assets/mockups/data_3_generate_data_modal_1920.webp +0 -0
- package/frontend/assets/mockups/data_4_data_export_modal_1920.webp +0 -0
- package/frontend/assets/mockups/documents_1_1920.webp +0 -0
- package/frontend/assets/mockups/documents_2_document_insert_table_modal_1920.webp +0 -0
- package/frontend/assets/mockups/documents_3_document_insert_note_modal_1920.webp +0 -0
- package/frontend/assets/mockups/logs_1_1920.webp +0 -0
- package/frontend/assets/mockups/media_tagging_queue_1_1920.webp +0 -0
- package/frontend/assets/mockups/media_tagging_setup_1_1920.webp +0 -0
- package/frontend/assets/mockups/media_tagging_setup_2_create_media_tagging_tag_table_modal_1920.webp +0 -0
- package/frontend/assets/mockups/media_tagging_setup_3_create_media_tagging_mapping_table_modal_1920.webp +0 -0
- package/frontend/assets/mockups/overview_1_1920.webp +0 -0
- package/frontend/assets/mockups/settings_1_1920.webp +0 -0
- package/frontend/assets/mockups/sql_editor_1_1920.webp +0 -0
- package/frontend/assets/mockups/sql_editor_2_query_detail_1920.webp +0 -0
- package/frontend/assets/mockups/sql_editor_3_query_export_modal_1920.webp +0 -0
- package/frontend/assets/mockups/structure_1_1920.webp +0 -0
- package/frontend/assets/mockups/structure_2_generate_types_modal_1920.webp +0 -0
- package/frontend/assets/mockups/structure_3_generate_types_modal_1920.webp +0 -0
- package/frontend/assets/mockups/table_designer_1_1920.webp +0 -0
- package/frontend/assets/mockups/table_designer_2_table_designer_constraints_modal_1920.webp +0 -0
- package/frontend/js/api.js +11 -0
- package/frontend/js/app.js +238 -8
- package/frontend/js/components/badges.js +16 -0
- package/frontend/js/components/connectionCard.js +35 -0
- package/frontend/js/components/modal.js +116 -245
- package/frontend/js/components/queryHistoryDetail.js +25 -45
- package/frontend/js/components/rowEditorPanel.js +7 -60
- package/frontend/js/components/structureGraph.js +292 -13
- package/frontend/js/components/tableDesignerConstraintsDrawer.js +400 -0
- package/frontend/js/components/tableDesignerEditor.js +51 -16
- package/frontend/js/components/tableDesignerSidebar.js +9 -5
- package/frontend/js/store.js +551 -7
- package/frontend/js/utils/connectionRegistry.js +76 -0
- package/frontend/js/utils/tableDesigner.js +431 -62
- package/frontend/js/views/backups.js +2 -5
- package/frontend/js/views/charts.js +20 -10
- package/frontend/js/views/connections.js +103 -3
- package/frontend/js/views/data.js +19 -5
- package/frontend/js/views/structure.js +28 -5
- package/frontend/js/views/tableAdvisor.js +14 -6
- package/frontend/js/views/tableDesigner.js +44 -9
- package/frontend/styles/components.css +676 -79
- package/frontend/styles/structure-graph.css +49 -0
- package/frontend/styles/tailwind.generated.css +1 -1
- package/package.json +1 -1
- package/server/routes/connections.js +12 -0
- package/server/routes/data.js +1 -1
- package/server/routes/tableDesigner.js +15 -1
- package/server/services/mcpToolService.js +28 -0
- package/server/services/sqlite/connectionManager.js +23 -2
- package/server/services/sqlite/dataBrowserService.js +21 -0
- package/server/services/sqlite/introspection.js +39 -1
- package/server/services/sqlite/structureService.js +66 -11
- package/server/services/sqlite/tableAdvisor.js +6 -0
- package/server/services/sqlite/tableDesigner/schemaMapping.js +7 -1
- package/server/services/sqlite/tableDesigner/sql.js +31 -2
- package/server/services/sqlite/tableDesigner/validation.js +69 -9
- package/server/services/sqlite/tableDesignerService.js +110 -5
- package/server/services/storage/appStateStore.js +303 -4
- package/frontend/assets/mockups/backups_2_create_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_3_edit_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_4_restore_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_5_delete_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/data_3_data_export_modal_1920.webp +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { showToast } from '../store.js';
|
|
1
|
+
import { clearStructureSelection, showToast } from '../store.js';
|
|
2
2
|
import { replaceChildrenFromRenderedMarkup } from '../utils/dom.js';
|
|
3
3
|
import { escapeHtml, formatNumber } from '../utils/format.js';
|
|
4
4
|
|
|
@@ -56,10 +56,68 @@ function getTableId(tableName) {
|
|
|
56
56
|
return `table:${tableName}`;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
function isShadowRelationship(edgeData) {
|
|
60
|
+
return edgeData?.relationshipKind === 'shadow';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function resolveShadowOwnerTableName(shadowTable, tables, tableMap) {
|
|
64
|
+
if (!shadowTable?.isShadow) {
|
|
65
|
+
return '';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const explicitOwnerName = String(shadowTable.shadowOwnerTable ?? '');
|
|
69
|
+
const explicitOwner = tableMap.get(explicitOwnerName);
|
|
70
|
+
|
|
71
|
+
if (explicitOwner?.isVirtual) {
|
|
72
|
+
return explicitOwnerName;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
tables
|
|
77
|
+
.filter(table => table.isVirtual && shadowTable.name.startsWith(`${table.name}_`))
|
|
78
|
+
.sort((left, right) => right.name.length - left.name.length)[0]?.name ?? ''
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function unquoteSqlIdentifier(identifier) {
|
|
83
|
+
const value = String(identifier ?? '').trim();
|
|
84
|
+
|
|
85
|
+
if (value.length >= 2 && value[0] === '"' && value.at(-1) === '"') {
|
|
86
|
+
return value.slice(1, -1).replaceAll('""', '"');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (value.length >= 2 && value[0] === '`' && value.at(-1) === '`') {
|
|
90
|
+
return value.slice(1, -1).replaceAll('``', '`');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (value.length >= 2 && value[0] === '[' && value.at(-1) === ']') {
|
|
94
|
+
return value.slice(1, -1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return value;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function extractVirtualTableModule(ddl = '') {
|
|
101
|
+
const match = String(ddl ?? '').match(
|
|
102
|
+
/\bUSING\s+("[^"]+(?:""[^"]*)*"|`[^`]+(?:``[^`]*)*`|\[[^\]]+\]|[A-Za-z_][A-Za-z0-9_]*)/i,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
return match ? unquoteSqlIdentifier(match[1]) : '';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function resolveVirtualTableModule(table) {
|
|
109
|
+
if (!table?.isVirtual) {
|
|
110
|
+
return '';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return String(table.virtualModule ?? '').trim() || extractVirtualTableModule(table.ddl);
|
|
114
|
+
}
|
|
115
|
+
|
|
59
116
|
function getSchemaSignature(schema) {
|
|
60
117
|
return JSON.stringify(
|
|
61
118
|
(schema?.tables ?? []).map(table => ({
|
|
62
119
|
name: table.name,
|
|
120
|
+
virtualModule: table.virtualModule ?? null,
|
|
63
121
|
columns: (table.columns ?? []).map(column => column.name),
|
|
64
122
|
foreignKeys: (table.foreignKeys ?? []).map(foreignKey => ({
|
|
65
123
|
referencedTable: foreignKey.referencedTable,
|
|
@@ -237,6 +295,18 @@ function createColumnFlags(column, foreignKeyColumns) {
|
|
|
237
295
|
return flags.join('');
|
|
238
296
|
}
|
|
239
297
|
|
|
298
|
+
function renderTableKindFlag(tableData) {
|
|
299
|
+
if (tableData?.isVirtual) {
|
|
300
|
+
return '<span class="structure-graph__flag is-virtual" title="Virtual table">Virtual</span>';
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (tableData?.isShadow) {
|
|
304
|
+
return '<span class="structure-graph__flag is-shadow" title="Shadow table">Shadow</span>';
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return '';
|
|
308
|
+
}
|
|
309
|
+
|
|
240
310
|
function quoteSqlIdentifier(identifier) {
|
|
241
311
|
return `"${String(identifier ?? '').replaceAll('"', '""')}"`;
|
|
242
312
|
}
|
|
@@ -298,6 +368,47 @@ function buildJoinSql(edgeData) {
|
|
|
298
368
|
}
|
|
299
369
|
|
|
300
370
|
function renderJoinInspector(edgeData) {
|
|
371
|
+
if (isShadowRelationship(edgeData)) {
|
|
372
|
+
const sourceTable = edgeData?.sourceTable || '';
|
|
373
|
+
const targetTable = edgeData?.targetTable || '';
|
|
374
|
+
|
|
375
|
+
return `
|
|
376
|
+
<div class="structure-graph__panel">
|
|
377
|
+
<div class="space-y-3">
|
|
378
|
+
<div class="structure-graph__panel-heading">
|
|
379
|
+
<div class="structure-graph__eyebrow">Shadow Link</div>
|
|
380
|
+
<button
|
|
381
|
+
class="query-history-icon-button"
|
|
382
|
+
data-structure-graph-action="toggle-inspector"
|
|
383
|
+
type="button"
|
|
384
|
+
aria-label="Hide inspector"
|
|
385
|
+
>
|
|
386
|
+
<span class="material-symbols-outlined text-[18px]">close</span>
|
|
387
|
+
</button>
|
|
388
|
+
</div>
|
|
389
|
+
<div class="structure-graph__title">${escapeHtml(sourceTable)} → ${escapeHtml(targetTable)}</div>
|
|
390
|
+
<div class="structure-graph__subtitle">Shadow table relationship</div>
|
|
391
|
+
</div>
|
|
392
|
+
|
|
393
|
+
<section class="structure-graph__section">
|
|
394
|
+
<div class="structure-graph__section-title">Relationship</div>
|
|
395
|
+
<div class="structure-graph__join-flow">
|
|
396
|
+
<div class="structure-graph__join-table">${escapeHtml(sourceTable)}</div>
|
|
397
|
+
<span class="material-symbols-outlined structure-graph__join-arrow" aria-hidden="true">arrow_forward</span>
|
|
398
|
+
<div class="structure-graph__join-table">${escapeHtml(targetTable)}</div>
|
|
399
|
+
</div>
|
|
400
|
+
<div class="structure-graph__join-condition-list">
|
|
401
|
+
<div class="structure-graph__join-condition">
|
|
402
|
+
<span>Virtual table</span>
|
|
403
|
+
<span aria-hidden="true">→</span>
|
|
404
|
+
<span>Shadow table</span>
|
|
405
|
+
</div>
|
|
406
|
+
</div>
|
|
407
|
+
</section>
|
|
408
|
+
</div>
|
|
409
|
+
`;
|
|
410
|
+
}
|
|
411
|
+
|
|
301
412
|
const sourceTable = edgeData?.sourceTable || '';
|
|
302
413
|
const targetTable = edgeData?.targetTable || '';
|
|
303
414
|
const conditions = getJoinConditions(edgeData);
|
|
@@ -429,7 +540,10 @@ export function renderInspector(tableData) {
|
|
|
429
540
|
<span class="material-symbols-outlined text-[18px]">close</span>
|
|
430
541
|
</button>
|
|
431
542
|
</div>
|
|
432
|
-
<div class="
|
|
543
|
+
<div class="flex min-w-0 flex-wrap items-center gap-2">
|
|
544
|
+
<div class="structure-graph__title min-w-0">${escapeHtml(tableData.name)}</div>
|
|
545
|
+
${renderTableKindFlag(tableData)}
|
|
546
|
+
</div>
|
|
433
547
|
</div>
|
|
434
548
|
|
|
435
549
|
<div class="structure-graph__summary">
|
|
@@ -474,19 +588,51 @@ export function renderInspector(tableData) {
|
|
|
474
588
|
export function buildGraphElements(schema) {
|
|
475
589
|
const tables = schema?.tables ?? [];
|
|
476
590
|
const tableMap = new Map(tables.map(table => [table.name, table]));
|
|
477
|
-
const nodes = tables.map(table =>
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
table,
|
|
486
|
-
|
|
487
|
-
|
|
591
|
+
const nodes = tables.map(table => {
|
|
592
|
+
const isVirtual = Boolean(table.isVirtual);
|
|
593
|
+
const isShadow = Boolean(table.isShadow);
|
|
594
|
+
const virtualModule = resolveVirtualTableModule(table);
|
|
595
|
+
const moduleWidth = virtualModule ? virtualModule.length * 8 + 72 : 0;
|
|
596
|
+
|
|
597
|
+
return {
|
|
598
|
+
group: 'nodes',
|
|
599
|
+
classes: [isVirtual ? 'virtual-table' : '', isShadow ? 'shadow-table' : ''].filter(Boolean).join(' '),
|
|
600
|
+
data: {
|
|
601
|
+
id: getTableId(table.name),
|
|
602
|
+
label: table.name,
|
|
603
|
+
tableName: table.name,
|
|
604
|
+
tableKind: table.tableKind ?? 'table',
|
|
605
|
+
isVirtual,
|
|
606
|
+
isShadow,
|
|
607
|
+
virtualModule,
|
|
608
|
+
width: Math.max(isVirtual ? 208 : 184, table.name.length * 9 + 64, moduleWidth),
|
|
609
|
+
height: isVirtual ? (virtualModule ? 82 : 70) : 56,
|
|
610
|
+
table,
|
|
611
|
+
},
|
|
612
|
+
};
|
|
613
|
+
});
|
|
488
614
|
const edges = [];
|
|
489
615
|
|
|
616
|
+
tables.forEach(table => {
|
|
617
|
+
const shadowOwnerTableName = resolveShadowOwnerTableName(table, tables, tableMap);
|
|
618
|
+
|
|
619
|
+
if (!shadowOwnerTableName || !tableMap.has(shadowOwnerTableName)) {
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
edges.push({
|
|
624
|
+
group: 'edges',
|
|
625
|
+
classes: 'shadow-link',
|
|
626
|
+
data: {
|
|
627
|
+
source: getTableId(shadowOwnerTableName),
|
|
628
|
+
target: getTableId(table.name),
|
|
629
|
+
sourceTable: shadowOwnerTableName,
|
|
630
|
+
targetTable: table.name,
|
|
631
|
+
relationshipKind: 'shadow',
|
|
632
|
+
},
|
|
633
|
+
});
|
|
634
|
+
});
|
|
635
|
+
|
|
490
636
|
tables.forEach(table => {
|
|
491
637
|
(table.foreignKeys ?? []).forEach((foreignKey, foreignKeyIndex) => {
|
|
492
638
|
if (!tableMap.has(foreignKey.referencedTable)) {
|
|
@@ -582,6 +728,27 @@ export function createCytoscapeInstance(container, elements) {
|
|
|
582
728
|
'transition-duration': '140ms',
|
|
583
729
|
},
|
|
584
730
|
},
|
|
731
|
+
{
|
|
732
|
+
selector: 'node.virtual-table',
|
|
733
|
+
style: {
|
|
734
|
+
'background-color': '#3d1238',
|
|
735
|
+
'border-color': '#ff4fa3',
|
|
736
|
+
'border-width': 2.4,
|
|
737
|
+
color: '#ffe8f4',
|
|
738
|
+
'font-size': 12.5,
|
|
739
|
+
'text-wrap': 'wrap',
|
|
740
|
+
'text-max-width': 174,
|
|
741
|
+
},
|
|
742
|
+
},
|
|
743
|
+
{
|
|
744
|
+
selector: 'node.shadow-table',
|
|
745
|
+
style: {
|
|
746
|
+
'background-color': '#221f1a',
|
|
747
|
+
'border-color': '#8f8a75',
|
|
748
|
+
'border-style': 'dashed',
|
|
749
|
+
color: '#f3ecd0',
|
|
750
|
+
},
|
|
751
|
+
},
|
|
585
752
|
{
|
|
586
753
|
selector: 'node.selected',
|
|
587
754
|
style: {
|
|
@@ -643,6 +810,22 @@ export function createCytoscapeInstance(container, elements) {
|
|
|
643
810
|
'transition-duration': '120ms',
|
|
644
811
|
},
|
|
645
812
|
},
|
|
813
|
+
{
|
|
814
|
+
selector: 'edge.shadow-link',
|
|
815
|
+
style: {
|
|
816
|
+
width: 1.8,
|
|
817
|
+
label: '',
|
|
818
|
+
'line-color': '#8f8a75',
|
|
819
|
+
'line-style': 'dashed',
|
|
820
|
+
'target-arrow-color': '#8f8a75',
|
|
821
|
+
'target-arrow-shape': 'vee',
|
|
822
|
+
'arrow-scale': 0.9,
|
|
823
|
+
'text-background-opacity': 0,
|
|
824
|
+
'text-border-opacity': 0,
|
|
825
|
+
'text-outline-width': 0,
|
|
826
|
+
'z-index': 0,
|
|
827
|
+
},
|
|
828
|
+
},
|
|
646
829
|
{
|
|
647
830
|
selector: 'edge.related',
|
|
648
831
|
style: {
|
|
@@ -714,6 +897,21 @@ function showEdgeReadout(edge, renderedPosition) {
|
|
|
714
897
|
return;
|
|
715
898
|
}
|
|
716
899
|
|
|
900
|
+
if (isShadowRelationship(edge.data())) {
|
|
901
|
+
const sourceTable = edge.data('sourceTable') || edge.source().data('tableName') || '?';
|
|
902
|
+
const targetTable = edge.data('targetTable') || edge.target().data('tableName') || '?';
|
|
903
|
+
|
|
904
|
+
currentGraph.edgeReadout.innerHTML = `
|
|
905
|
+
<div class="structure-graph__edge-readout-meta">Shadow table</div>
|
|
906
|
+
<div class="structure-graph__edge-readout-path">
|
|
907
|
+
${escapeHtml(sourceTable)} <span aria-hidden="true">→</span> ${escapeHtml(targetTable)}
|
|
908
|
+
</div>
|
|
909
|
+
`;
|
|
910
|
+
currentGraph.edgeReadout.removeAttribute('hidden');
|
|
911
|
+
positionEdgeReadout(renderedPosition);
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
|
|
717
915
|
const sourceTable = edge.data('sourceTable') || edge.source().data('tableName') || '?';
|
|
718
916
|
const targetTable = edge.data('targetTable') || edge.target().data('tableName') || '?';
|
|
719
917
|
const sourceColumn = edge.data('sourceColumn') || '?';
|
|
@@ -741,6 +939,65 @@ function hideEdgeReadout() {
|
|
|
741
939
|
currentGraph.edgeReadout.setAttribute('hidden', 'hidden');
|
|
742
940
|
}
|
|
743
941
|
|
|
942
|
+
function clearVirtualModuleBadges(graph = currentGraph) {
|
|
943
|
+
if (!graph?.virtualModuleBadges) {
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
graph.virtualModuleBadges.forEach(badge => badge.remove());
|
|
948
|
+
graph.virtualModuleBadges.clear();
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
function syncVirtualModuleBadges(graph = currentGraph) {
|
|
952
|
+
if (!graph?.cy || !graph.canvasShell || !graph.virtualModuleBadges) {
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
const activeNodeIds = new Set();
|
|
957
|
+
|
|
958
|
+
graph.cy.nodes('.virtual-table').forEach(node => {
|
|
959
|
+
const moduleName = String(node.data('virtualModule') ?? '').trim();
|
|
960
|
+
|
|
961
|
+
if (!moduleName) {
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
const nodeId = node.id();
|
|
966
|
+
activeNodeIds.add(nodeId);
|
|
967
|
+
|
|
968
|
+
let badge = graph.virtualModuleBadges.get(nodeId);
|
|
969
|
+
|
|
970
|
+
if (!badge) {
|
|
971
|
+
badge = document.createElement('div');
|
|
972
|
+
badge.className = 'structure-graph__virtual-module-badge';
|
|
973
|
+
badge.dataset.structureVirtualModuleBadge = node.data('tableName') || '';
|
|
974
|
+
graph.canvasShell.appendChild(badge);
|
|
975
|
+
graph.virtualModuleBadges.set(nodeId, badge);
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
badge.textContent = moduleName;
|
|
979
|
+
badge.classList.toggle('is-dimmed', node.hasClass('dimmed'));
|
|
980
|
+
badge.classList.toggle('is-related', node.hasClass('related'));
|
|
981
|
+
badge.classList.toggle('is-selected', node.hasClass('selected'));
|
|
982
|
+
|
|
983
|
+
const position = node.renderedPosition();
|
|
984
|
+
const renderedHeight = Number(node.renderedHeight?.() ?? 0);
|
|
985
|
+
const yOffset = Math.max(12, Math.min(22, renderedHeight * 0.24));
|
|
986
|
+
const scale = Math.max(0.72, Math.min(1, graph.cy.zoom()));
|
|
987
|
+
|
|
988
|
+
badge.style.left = `${position.x}px`;
|
|
989
|
+
badge.style.top = `${position.y + yOffset}px`;
|
|
990
|
+
badge.style.transform = `translate(-50%, -50%) scale(${scale})`;
|
|
991
|
+
});
|
|
992
|
+
|
|
993
|
+
graph.virtualModuleBadges.forEach((badge, nodeId) => {
|
|
994
|
+
if (!activeNodeIds.has(nodeId)) {
|
|
995
|
+
badge.remove();
|
|
996
|
+
graph.virtualModuleBadges.delete(nodeId);
|
|
997
|
+
}
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
|
|
744
1001
|
function createGraphStateSnapshot(graph = currentGraph) {
|
|
745
1002
|
if (!graph?.cy) {
|
|
746
1003
|
return null;
|
|
@@ -858,6 +1115,8 @@ function destroyCurrentGraph() {
|
|
|
858
1115
|
currentGraph.resizeObserver.disconnect();
|
|
859
1116
|
}
|
|
860
1117
|
|
|
1118
|
+
clearVirtualModuleBadges(currentGraph);
|
|
1119
|
+
|
|
861
1120
|
if (currentGraph.resizeHandler) {
|
|
862
1121
|
window.removeEventListener('resize', currentGraph.resizeHandler);
|
|
863
1122
|
}
|
|
@@ -987,6 +1246,8 @@ function clearSelection() {
|
|
|
987
1246
|
syncEntryHighlights();
|
|
988
1247
|
replaceChildrenFromRenderedMarkup(currentGraph.inspector, getDefaultInspectorMarkup());
|
|
989
1248
|
updateOpenDataButton();
|
|
1249
|
+
syncVirtualModuleBadges();
|
|
1250
|
+
clearStructureSelection();
|
|
990
1251
|
}
|
|
991
1252
|
|
|
992
1253
|
function applyTableSelection(node, { focus = true } = {}) {
|
|
@@ -1002,6 +1263,7 @@ function applyTableSelection(node, { focus = true } = {}) {
|
|
|
1002
1263
|
highlightConnectedElements(currentGraph.cy, node);
|
|
1003
1264
|
syncEntryHighlights([tableData.name]);
|
|
1004
1265
|
updateOpenDataButton();
|
|
1266
|
+
syncVirtualModuleBadges();
|
|
1005
1267
|
|
|
1006
1268
|
if (focus) {
|
|
1007
1269
|
currentGraph.cy.animate(
|
|
@@ -1033,6 +1295,7 @@ function applyEdgeSelection(edge) {
|
|
|
1033
1295
|
highlightConnectedElements(currentGraph.cy, edge);
|
|
1034
1296
|
syncEntryHighlights([], [edge.data('sourceTable'), edge.data('targetTable')]);
|
|
1035
1297
|
updateOpenDataButton();
|
|
1298
|
+
syncVirtualModuleBadges();
|
|
1036
1299
|
return edge;
|
|
1037
1300
|
}
|
|
1038
1301
|
|
|
@@ -1202,6 +1465,7 @@ function runLayout(cy, onStop, { randomize = false } = {}) {
|
|
|
1202
1465
|
|
|
1203
1466
|
applyReadableLayoutPositions(cy, { variant: currentGraph?.layoutVariant ?? 0 });
|
|
1204
1467
|
cy.fit(cy.elements(), 90);
|
|
1468
|
+
syncVirtualModuleBadges();
|
|
1205
1469
|
|
|
1206
1470
|
if (typeof onStop === 'function') {
|
|
1207
1471
|
onStop();
|
|
@@ -1235,6 +1499,8 @@ function restorePersistedViewport(cy, persistedState) {
|
|
|
1235
1499
|
cy.pan(persistedState.pan);
|
|
1236
1500
|
}
|
|
1237
1501
|
|
|
1502
|
+
syncVirtualModuleBadges();
|
|
1503
|
+
|
|
1238
1504
|
return true;
|
|
1239
1505
|
}
|
|
1240
1506
|
|
|
@@ -1375,6 +1641,7 @@ export async function mountStructureGraph(snapshot) {
|
|
|
1375
1641
|
persistStateOnDestroy: true,
|
|
1376
1642
|
selectedEdgeId: null,
|
|
1377
1643
|
selectedTableName: null,
|
|
1644
|
+
virtualModuleBadges: new Map(),
|
|
1378
1645
|
};
|
|
1379
1646
|
|
|
1380
1647
|
currentGraph.cleanup.push(setupToolbar(cy));
|
|
@@ -1407,14 +1674,20 @@ export async function mountStructureGraph(snapshot) {
|
|
|
1407
1674
|
});
|
|
1408
1675
|
|
|
1409
1676
|
cy.on('dragfree', 'node', () => {
|
|
1677
|
+
syncVirtualModuleBadges();
|
|
1410
1678
|
scheduleGraphStatePersist(0);
|
|
1411
1679
|
});
|
|
1412
1680
|
|
|
1681
|
+
cy.on('position drag', 'node', () => {
|
|
1682
|
+
syncVirtualModuleBadges();
|
|
1683
|
+
});
|
|
1684
|
+
|
|
1413
1685
|
cy.on('mouseover', 'edge', event => {
|
|
1414
1686
|
const edge = event.target;
|
|
1415
1687
|
highlightConnectedElements(cy, edge);
|
|
1416
1688
|
syncEntryHighlights([], [edge.data('sourceTable'), edge.data('targetTable')]);
|
|
1417
1689
|
showEdgeReadout(edge, event.renderedPosition);
|
|
1690
|
+
syncVirtualModuleBadges();
|
|
1418
1691
|
});
|
|
1419
1692
|
|
|
1420
1693
|
cy.on('mousemove', 'edge', event => {
|
|
@@ -1424,10 +1697,12 @@ export async function mountStructureGraph(snapshot) {
|
|
|
1424
1697
|
cy.on('mouseout', 'edge', () => {
|
|
1425
1698
|
hideEdgeReadout();
|
|
1426
1699
|
restoreGraphState();
|
|
1700
|
+
syncVirtualModuleBadges();
|
|
1427
1701
|
});
|
|
1428
1702
|
|
|
1429
1703
|
cy.on('pan zoom', () => {
|
|
1430
1704
|
hideEdgeReadout();
|
|
1705
|
+
syncVirtualModuleBadges();
|
|
1431
1706
|
scheduleGraphStatePersist(300);
|
|
1432
1707
|
});
|
|
1433
1708
|
|
|
@@ -1465,14 +1740,18 @@ export async function mountStructureGraph(snapshot) {
|
|
|
1465
1740
|
});
|
|
1466
1741
|
}
|
|
1467
1742
|
|
|
1743
|
+
syncVirtualModuleBadges();
|
|
1744
|
+
|
|
1468
1745
|
if (typeof ResizeObserver === 'function') {
|
|
1469
1746
|
currentGraph.resizeObserver = new ResizeObserver(() => {
|
|
1470
1747
|
cy.resize();
|
|
1748
|
+
syncVirtualModuleBadges();
|
|
1471
1749
|
});
|
|
1472
1750
|
currentGraph.resizeObserver.observe(canvas);
|
|
1473
1751
|
} else {
|
|
1474
1752
|
currentGraph.resizeHandler = () => {
|
|
1475
1753
|
cy.resize();
|
|
1754
|
+
syncVirtualModuleBadges();
|
|
1476
1755
|
};
|
|
1477
1756
|
window.addEventListener('resize', currentGraph.resizeHandler);
|
|
1478
1757
|
}
|