@memberjunction/ng-dashboards 2.42.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/dist/AI/ai-dashboard.component.d.ts +54 -0
- package/dist/AI/ai-dashboard.component.d.ts.map +1 -0
- package/dist/AI/ai-dashboard.component.js +248 -0
- package/dist/AI/ai-dashboard.component.js.map +1 -0
- package/dist/AI/components/agents/agent-configuration.component.d.ts +41 -0
- package/dist/AI/components/agents/agent-configuration.component.d.ts.map +1 -0
- package/dist/AI/components/agents/agent-configuration.component.js +325 -0
- package/dist/AI/components/agents/agent-configuration.component.js.map +1 -0
- package/dist/AI/components/agents/agent-editor.component.d.ts +77 -0
- package/dist/AI/components/agents/agent-editor.component.d.ts.map +1 -0
- package/dist/AI/components/agents/agent-editor.component.js +869 -0
- package/dist/AI/components/agents/agent-editor.component.js.map +1 -0
- package/dist/AI/components/agents/agent-filter-panel.component.d.ts +33 -0
- package/dist/AI/components/agents/agent-filter-panel.component.d.ts.map +1 -0
- package/dist/AI/components/agents/agent-filter-panel.component.js +144 -0
- package/dist/AI/components/agents/agent-filter-panel.component.js.map +1 -0
- package/dist/AI/components/execution-monitoring.component.d.ts +13 -0
- package/dist/AI/components/execution-monitoring.component.d.ts.map +1 -0
- package/dist/AI/components/execution-monitoring.component.js +30 -0
- package/dist/AI/components/execution-monitoring.component.js.map +1 -0
- package/dist/AI/components/models/model-management.component.d.ts +73 -0
- package/dist/AI/components/models/model-management.component.d.ts.map +1 -0
- package/dist/AI/components/models/model-management.component.js +669 -0
- package/dist/AI/components/models/model-management.component.js.map +1 -0
- package/dist/AI/components/prompts/prompt-filter-panel.component.d.ts +49 -0
- package/dist/AI/components/prompts/prompt-filter-panel.component.d.ts.map +1 -0
- package/dist/AI/components/prompts/prompt-filter-panel.component.js +186 -0
- package/dist/AI/components/prompts/prompt-filter-panel.component.js.map +1 -0
- package/dist/AI/components/prompts/prompt-management.component.d.ts +113 -0
- package/dist/AI/components/prompts/prompt-management.component.d.ts.map +1 -0
- package/dist/AI/components/prompts/prompt-management.component.js +1316 -0
- package/dist/AI/components/prompts/prompt-management.component.js.map +1 -0
- package/dist/AI/components/system/system-config-filter-panel.component.d.ts +33 -0
- package/dist/AI/components/system/system-config-filter-panel.component.d.ts.map +1 -0
- package/dist/AI/components/system/system-config-filter-panel.component.js +146 -0
- package/dist/AI/components/system/system-config-filter-panel.component.js.map +1 -0
- package/dist/AI/components/system/system-configuration.component.d.ts +37 -0
- package/dist/AI/components/system/system-configuration.component.d.ts.map +1 -0
- package/dist/AI/components/system/system-configuration.component.js +311 -0
- package/dist/AI/components/system/system-configuration.component.js.map +1 -0
- package/dist/Actions/actions-management-dashboard.component.d.ts +50 -0
- package/dist/Actions/actions-management-dashboard.component.d.ts.map +1 -0
- package/dist/Actions/actions-management-dashboard.component.js +282 -0
- package/dist/Actions/actions-management-dashboard.component.js.map +1 -0
- package/dist/Actions/components/actions-list-view.component.d.ts +52 -0
- package/dist/Actions/components/actions-list-view.component.d.ts.map +1 -0
- package/dist/Actions/components/actions-list-view.component.js +366 -0
- package/dist/Actions/components/actions-list-view.component.js.map +1 -0
- package/dist/Actions/components/actions-overview.component.d.ts +71 -0
- package/dist/Actions/components/actions-overview.component.d.ts.map +1 -0
- package/dist/Actions/components/actions-overview.component.js +605 -0
- package/dist/Actions/components/actions-overview.component.js.map +1 -0
- package/dist/Actions/components/categories-list-view.component.d.ts +11 -0
- package/dist/Actions/components/categories-list-view.component.d.ts.map +1 -0
- package/dist/Actions/components/categories-list-view.component.js +35 -0
- package/dist/Actions/components/categories-list-view.component.js.map +1 -0
- package/dist/Actions/components/code-management.component.d.ts +11 -0
- package/dist/Actions/components/code-management.component.d.ts.map +1 -0
- package/dist/Actions/components/code-management.component.js +35 -0
- package/dist/Actions/components/code-management.component.js.map +1 -0
- package/dist/Actions/components/entity-integration.component.d.ts +11 -0
- package/dist/Actions/components/entity-integration.component.d.ts.map +1 -0
- package/dist/Actions/components/entity-integration.component.js +35 -0
- package/dist/Actions/components/entity-integration.component.js.map +1 -0
- package/dist/Actions/components/execution-monitoring.component.d.ts +83 -0
- package/dist/Actions/components/execution-monitoring.component.d.ts.map +1 -0
- package/dist/Actions/components/execution-monitoring.component.js +629 -0
- package/dist/Actions/components/execution-monitoring.component.js.map +1 -0
- package/dist/Actions/components/executions-list-view.component.d.ts +11 -0
- package/dist/Actions/components/executions-list-view.component.d.ts.map +1 -0
- package/dist/Actions/components/executions-list-view.component.js +35 -0
- package/dist/Actions/components/executions-list-view.component.js.map +1 -0
- package/dist/Actions/components/scheduled-actions.component.d.ts +11 -0
- package/dist/Actions/components/scheduled-actions.component.d.ts.map +1 -0
- package/dist/Actions/components/scheduled-actions.component.js +35 -0
- package/dist/Actions/components/scheduled-actions.component.js.map +1 -0
- package/dist/Actions/components/security-permissions.component.d.ts +11 -0
- package/dist/Actions/components/security-permissions.component.d.ts.map +1 -0
- package/dist/Actions/components/security-permissions.component.js +35 -0
- package/dist/Actions/components/security-permissions.component.js.map +1 -0
- package/dist/Actions/index.d.ts +11 -0
- package/dist/Actions/index.d.ts.map +1 -0
- package/dist/Actions/index.js +11 -0
- package/dist/Actions/index.js.map +1 -0
- package/dist/EntityAdmin/components/entity-details.component.d.ts +50 -0
- package/dist/EntityAdmin/components/entity-details.component.d.ts.map +1 -0
- package/dist/EntityAdmin/components/entity-details.component.js +684 -0
- package/dist/EntityAdmin/components/entity-details.component.js.map +1 -0
- package/dist/EntityAdmin/components/entity-filter-panel.component.d.ts +31 -0
- package/dist/EntityAdmin/components/entity-filter-panel.component.d.ts.map +1 -0
- package/dist/EntityAdmin/components/entity-filter-panel.component.js +162 -0
- package/dist/EntityAdmin/components/entity-filter-panel.component.js.map +1 -0
- package/dist/EntityAdmin/components/erd-composite.component.d.ts +73 -0
- package/dist/EntityAdmin/components/erd-composite.component.d.ts.map +1 -0
- package/dist/EntityAdmin/components/erd-composite.component.js +288 -0
- package/dist/EntityAdmin/components/erd-composite.component.js.map +1 -0
- package/dist/EntityAdmin/components/erd-diagram.component.d.ts +47 -0
- package/dist/EntityAdmin/components/erd-diagram.component.d.ts.map +1 -0
- package/dist/EntityAdmin/components/erd-diagram.component.js +618 -0
- package/dist/EntityAdmin/components/erd-diagram.component.js.map +1 -0
- package/dist/EntityAdmin/entity-admin-dashboard.component.d.ts +50 -0
- package/dist/EntityAdmin/entity-admin-dashboard.component.d.ts.map +1 -0
- package/dist/EntityAdmin/entity-admin-dashboard.component.js +190 -0
- package/dist/EntityAdmin/entity-admin-dashboard.component.js.map +1 -0
- package/dist/generic/base-dashboard.d.ts +65 -0
- package/dist/generic/base-dashboard.d.ts.map +1 -0
- package/dist/generic/base-dashboard.js +86 -0
- package/dist/generic/base-dashboard.js.map +1 -0
- package/dist/module.d.ts +43 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +141 -0
- package/dist/module.js.map +1 -0
- package/dist/public-api.d.ts +6 -0
- package/dist/public-api.d.ts.map +1 -0
- package/dist/public-api.js +18 -0
- package/dist/public-api.js.map +1 -0
- package/package.json +46 -0
|
@@ -0,0 +1,618 @@
|
|
|
1
|
+
import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core';
|
|
2
|
+
import * as d3 from 'd3';
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
const _c0 = ["erdContainer"];
|
|
5
|
+
export class ERDDiagramComponent {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.filteredEntities = [];
|
|
8
|
+
this.allEntityFields = [];
|
|
9
|
+
this.isRefreshingERD = false;
|
|
10
|
+
this.selectedEntityId = null;
|
|
11
|
+
this.entitySelected = new EventEmitter();
|
|
12
|
+
this.entityDeselected = new EventEmitter();
|
|
13
|
+
this.refreshERD = new EventEmitter();
|
|
14
|
+
this.nodes = [];
|
|
15
|
+
this.links = [];
|
|
16
|
+
this.lastKnownSize = { width: 0, height: 0 };
|
|
17
|
+
}
|
|
18
|
+
ngAfterViewInit() {
|
|
19
|
+
setTimeout(() => {
|
|
20
|
+
this.setupERD();
|
|
21
|
+
}, 100);
|
|
22
|
+
// Add resize observer to handle container size changes
|
|
23
|
+
this.setupResizeObserver();
|
|
24
|
+
}
|
|
25
|
+
ngOnChanges(changes) {
|
|
26
|
+
if (changes['filteredEntities'] && !changes['filteredEntities'].firstChange) {
|
|
27
|
+
this.setupERD();
|
|
28
|
+
}
|
|
29
|
+
if (changes['selectedEntityId'] && !changes['selectedEntityId'].firstChange) {
|
|
30
|
+
this.updateSelectionHighlighting();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
ngOnDestroy() {
|
|
34
|
+
if (this.simulation) {
|
|
35
|
+
this.simulation.stop();
|
|
36
|
+
}
|
|
37
|
+
if (this.resizeObserver) {
|
|
38
|
+
this.resizeObserver.disconnect();
|
|
39
|
+
}
|
|
40
|
+
if (this.resizeTimeout) {
|
|
41
|
+
clearTimeout(this.resizeTimeout);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
zoomIn() {
|
|
45
|
+
if (this.zoom) {
|
|
46
|
+
this.svg.transition().duration(300).call(this.zoom.scaleBy, 1.5);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
zoomOut() {
|
|
50
|
+
if (this.zoom) {
|
|
51
|
+
this.svg.transition().duration(300).call(this.zoom.scaleBy, 0.67);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
resetZoom() {
|
|
55
|
+
if (this.zoom) {
|
|
56
|
+
this.svg.transition().duration(500).call(this.zoom.transform, d3.zoomIdentity);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
setupERD() {
|
|
60
|
+
var _a;
|
|
61
|
+
if (!((_a = this.erdContainer) === null || _a === void 0 ? void 0 : _a.nativeElement)) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
this.clearVisualization();
|
|
65
|
+
this.createNodes();
|
|
66
|
+
this.createLinks();
|
|
67
|
+
this.createVisualization();
|
|
68
|
+
}
|
|
69
|
+
resizeSVG() {
|
|
70
|
+
var _a;
|
|
71
|
+
if (!this.svg || !((_a = this.erdContainer) === null || _a === void 0 ? void 0 : _a.nativeElement)) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const container = this.erdContainer.nativeElement;
|
|
75
|
+
const width = container.clientWidth || 800;
|
|
76
|
+
const height = container.clientHeight || 600;
|
|
77
|
+
// Prevent resize loops by checking if size actually changed
|
|
78
|
+
if (Math.abs(this.lastKnownSize.width - width) < 5 &&
|
|
79
|
+
Math.abs(this.lastKnownSize.height - height) < 5) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
this.lastKnownSize = { width, height };
|
|
83
|
+
// Use CSS for sizing instead of SVG attributes to prevent feedback loops
|
|
84
|
+
const svgElement = this.svg.node();
|
|
85
|
+
if (svgElement) {
|
|
86
|
+
svgElement.style.width = '100%';
|
|
87
|
+
svgElement.style.height = '100%';
|
|
88
|
+
}
|
|
89
|
+
// Set viewBox instead of width/height attributes
|
|
90
|
+
this.svg.attr('viewBox', `0 0 ${width} ${height}`);
|
|
91
|
+
// Update simulation center force
|
|
92
|
+
if (this.simulation) {
|
|
93
|
+
this.simulation.force('center', d3.forceCenter(width / 2, height / 2));
|
|
94
|
+
this.simulation.alpha(0.3).restart();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
triggerResize() {
|
|
98
|
+
// Manual resize trigger for external components (like splitter layout changes)
|
|
99
|
+
if (this.resizeTimeout) {
|
|
100
|
+
clearTimeout(this.resizeTimeout);
|
|
101
|
+
}
|
|
102
|
+
this.resizeTimeout = window.setTimeout(() => {
|
|
103
|
+
this.resizeSVG();
|
|
104
|
+
}, 100);
|
|
105
|
+
}
|
|
106
|
+
setupResizeObserver() {
|
|
107
|
+
var _a;
|
|
108
|
+
if (!((_a = this.erdContainer) === null || _a === void 0 ? void 0 : _a.nativeElement)) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
this.resizeObserver = new ResizeObserver((entries) => {
|
|
112
|
+
// Debounce resize events to prevent feedback loops
|
|
113
|
+
if (this.resizeTimeout) {
|
|
114
|
+
clearTimeout(this.resizeTimeout);
|
|
115
|
+
}
|
|
116
|
+
this.resizeTimeout = window.setTimeout(() => {
|
|
117
|
+
var _a;
|
|
118
|
+
// Double-check that container size actually changed
|
|
119
|
+
const container = (_a = this.erdContainer) === null || _a === void 0 ? void 0 : _a.nativeElement;
|
|
120
|
+
if (!container)
|
|
121
|
+
return;
|
|
122
|
+
const newWidth = container.clientWidth;
|
|
123
|
+
const newHeight = container.clientHeight;
|
|
124
|
+
// Only resize if there's a meaningful size change
|
|
125
|
+
if (Math.abs(this.lastKnownSize.width - newWidth) >= 5 ||
|
|
126
|
+
Math.abs(this.lastKnownSize.height - newHeight) >= 5) {
|
|
127
|
+
requestAnimationFrame(() => {
|
|
128
|
+
this.resizeSVG();
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}, 50);
|
|
132
|
+
});
|
|
133
|
+
this.resizeObserver.observe(this.erdContainer.nativeElement);
|
|
134
|
+
}
|
|
135
|
+
clearVisualization() {
|
|
136
|
+
if (this.simulation) {
|
|
137
|
+
this.simulation.stop();
|
|
138
|
+
}
|
|
139
|
+
d3.select(this.erdContainer.nativeElement).selectAll('*').remove();
|
|
140
|
+
}
|
|
141
|
+
createNodes() {
|
|
142
|
+
this.nodes = this.filteredEntities.map(entity => {
|
|
143
|
+
const entityFields = this.allEntityFields.filter(f => f.EntityID === entity.ID);
|
|
144
|
+
const primaryKeys = entityFields.filter(f => f.IsPrimaryKey);
|
|
145
|
+
const foreignKeys = entityFields.filter(f => f.RelatedEntityID && !f.IsPrimaryKey);
|
|
146
|
+
const fieldCount = Math.max(1, primaryKeys.length + foreignKeys.length);
|
|
147
|
+
const baseHeight = 60;
|
|
148
|
+
const fieldHeight = 20;
|
|
149
|
+
const maxHeight = 300;
|
|
150
|
+
const calculatedHeight = Math.min(baseHeight + (fieldCount * fieldHeight), maxHeight);
|
|
151
|
+
return {
|
|
152
|
+
id: entity.ID,
|
|
153
|
+
name: entity.Name || entity.SchemaName || 'Unknown',
|
|
154
|
+
entity: entity,
|
|
155
|
+
width: 180,
|
|
156
|
+
height: calculatedHeight,
|
|
157
|
+
primaryKeys: primaryKeys,
|
|
158
|
+
foreignKeys: foreignKeys
|
|
159
|
+
};
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
createLinks() {
|
|
163
|
+
this.links = [];
|
|
164
|
+
const entityMap = new Map(this.nodes.map(node => [node.id, node]));
|
|
165
|
+
this.allEntityFields.forEach(field => {
|
|
166
|
+
if (field.RelatedEntityID && !field.IsPrimaryKey) {
|
|
167
|
+
const sourceNode = entityMap.get(field.EntityID);
|
|
168
|
+
const targetNode = entityMap.get(field.RelatedEntityID);
|
|
169
|
+
if (sourceNode && targetNode) {
|
|
170
|
+
const isSelfReference = field.EntityID === field.RelatedEntityID;
|
|
171
|
+
// Find target field (primary key in target entity)
|
|
172
|
+
const targetField = targetNode.primaryKeys.find(pk => pk.Name === field.RelatedEntityFieldName);
|
|
173
|
+
this.links.push({
|
|
174
|
+
source: sourceNode,
|
|
175
|
+
target: targetNode,
|
|
176
|
+
field: field,
|
|
177
|
+
sourceField: field,
|
|
178
|
+
targetField: targetField,
|
|
179
|
+
isSelfReference: isSelfReference
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
createVisualization() {
|
|
186
|
+
const container = this.erdContainer.nativeElement;
|
|
187
|
+
const width = container.clientWidth || 800;
|
|
188
|
+
const height = container.clientHeight || 600;
|
|
189
|
+
// Set initial known size
|
|
190
|
+
this.lastKnownSize = { width, height };
|
|
191
|
+
// Create zoom behavior
|
|
192
|
+
this.zoom = d3.zoom().on('zoom', (event) => {
|
|
193
|
+
this.svg.select('.chart-area').attr('transform', event.transform);
|
|
194
|
+
});
|
|
195
|
+
// Create SVG with CSS-based sizing
|
|
196
|
+
this.svg = d3
|
|
197
|
+
.select(container)
|
|
198
|
+
.append('svg')
|
|
199
|
+
.attr('viewBox', `0 0 ${width} ${height}`)
|
|
200
|
+
.style('width', '100%')
|
|
201
|
+
.style('height', '100%')
|
|
202
|
+
.style('position', 'absolute')
|
|
203
|
+
.style('top', '0')
|
|
204
|
+
.style('left', '0')
|
|
205
|
+
.call(this.zoom)
|
|
206
|
+
.on('click', (event) => {
|
|
207
|
+
// Deselect entity when clicking on background
|
|
208
|
+
if (event.target === event.currentTarget) {
|
|
209
|
+
this.entityDeselected.emit();
|
|
210
|
+
this.clearAllHighlighting();
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
const chartArea = this.svg.append('g').attr('class', 'chart-area');
|
|
214
|
+
// Define arrow marker for directional links
|
|
215
|
+
this.svg
|
|
216
|
+
.append('defs')
|
|
217
|
+
.selectAll('marker')
|
|
218
|
+
.data(['end-arrow'])
|
|
219
|
+
.enter()
|
|
220
|
+
.append('marker')
|
|
221
|
+
.attr('id', 'end-arrow')
|
|
222
|
+
.attr('viewBox', '0 -5 10 10')
|
|
223
|
+
.attr('refX', 8)
|
|
224
|
+
.attr('refY', 0)
|
|
225
|
+
.attr('markerWidth', 6)
|
|
226
|
+
.attr('markerHeight', 6)
|
|
227
|
+
.attr('orient', 'auto')
|
|
228
|
+
.append('path')
|
|
229
|
+
.attr('d', 'M0,-5L10,0L0,5')
|
|
230
|
+
.attr('fill', '#666');
|
|
231
|
+
// Create force simulation with dynamic distance based on rectangle sizes
|
|
232
|
+
this.simulation = d3
|
|
233
|
+
.forceSimulation(this.nodes)
|
|
234
|
+
.force('link', d3
|
|
235
|
+
.forceLink(this.links)
|
|
236
|
+
.id((d) => d.id)
|
|
237
|
+
.distance((d) => {
|
|
238
|
+
const sourceNode = d.source;
|
|
239
|
+
const targetNode = d.target;
|
|
240
|
+
const sourceSize = Math.max(sourceNode.width, sourceNode.height);
|
|
241
|
+
const targetSize = Math.max(targetNode.width, targetNode.height);
|
|
242
|
+
return (sourceSize + targetSize) / 2 + 80;
|
|
243
|
+
}))
|
|
244
|
+
.force('charge', d3.forceManyBody().strength(-800))
|
|
245
|
+
.force('center', d3.forceCenter(width / 2, height / 2))
|
|
246
|
+
.force('collision', d3.forceCollide().radius((d) => {
|
|
247
|
+
const node = d;
|
|
248
|
+
return Math.max(node.width, node.height) / 2 + 20;
|
|
249
|
+
}));
|
|
250
|
+
// Create links with arrows
|
|
251
|
+
const link = chartArea.selectAll('.link').data(this.links).enter().append('g').attr('class', 'link-group');
|
|
252
|
+
// Add the link line
|
|
253
|
+
link
|
|
254
|
+
.append('path')
|
|
255
|
+
.attr('class', 'link')
|
|
256
|
+
.attr('stroke', '#666')
|
|
257
|
+
.attr('stroke-opacity', 0.8)
|
|
258
|
+
.attr('stroke-width', 2)
|
|
259
|
+
.attr('fill', 'none')
|
|
260
|
+
.attr('marker-end', (d) => (d.isSelfReference ? 'none' : 'url(#end-arrow)'));
|
|
261
|
+
// Add labels for foreign key relationships
|
|
262
|
+
link
|
|
263
|
+
.append('text')
|
|
264
|
+
.attr('class', 'link-label')
|
|
265
|
+
.attr('font-size', '10px')
|
|
266
|
+
.attr('fill', '#666')
|
|
267
|
+
.attr('text-anchor', 'middle')
|
|
268
|
+
.text((d) => d.sourceField.Name || '');
|
|
269
|
+
// Create entity rectangle nodes
|
|
270
|
+
const node = chartArea
|
|
271
|
+
.selectAll('.node')
|
|
272
|
+
.data(this.nodes)
|
|
273
|
+
.enter()
|
|
274
|
+
.append('g')
|
|
275
|
+
.attr('class', 'node')
|
|
276
|
+
.style('cursor', 'pointer')
|
|
277
|
+
.call(d3
|
|
278
|
+
.drag()
|
|
279
|
+
.on('start', (event, d) => this.dragstarted(event, d))
|
|
280
|
+
.on('drag', (event, d) => this.dragged(event, d))
|
|
281
|
+
.on('end', (event, d) => this.dragended(event, d)));
|
|
282
|
+
// Add main rectangle for entity
|
|
283
|
+
node
|
|
284
|
+
.append('rect')
|
|
285
|
+
.attr('class', 'entity-rect')
|
|
286
|
+
.attr('width', (d) => d.width)
|
|
287
|
+
.attr('height', (d) => d.height)
|
|
288
|
+
.attr('x', (d) => -d.width / 2)
|
|
289
|
+
.attr('y', (d) => -d.height / 2)
|
|
290
|
+
.attr('fill', '#f8f9fa')
|
|
291
|
+
.attr('stroke', '#333')
|
|
292
|
+
.attr('stroke-width', 2)
|
|
293
|
+
.attr('rx', 4);
|
|
294
|
+
// Add entity header
|
|
295
|
+
node
|
|
296
|
+
.append('rect')
|
|
297
|
+
.attr('class', 'entity-header')
|
|
298
|
+
.attr('width', (d) => d.width)
|
|
299
|
+
.attr('height', 30)
|
|
300
|
+
.attr('x', (d) => -d.width / 2)
|
|
301
|
+
.attr('y', (d) => -d.height / 2)
|
|
302
|
+
.attr('fill', '#007bff')
|
|
303
|
+
.attr('rx', 4);
|
|
304
|
+
// Add entity header bottom border (to make only top rounded)
|
|
305
|
+
node
|
|
306
|
+
.append('rect')
|
|
307
|
+
.attr('class', 'entity-header-bottom')
|
|
308
|
+
.attr('width', (d) => d.width)
|
|
309
|
+
.attr('height', 15)
|
|
310
|
+
.attr('x', (d) => -d.width / 2)
|
|
311
|
+
.attr('y', (d) => -d.height / 2 + 15)
|
|
312
|
+
.attr('fill', '#007bff');
|
|
313
|
+
// Add entity name in header
|
|
314
|
+
node
|
|
315
|
+
.append('text')
|
|
316
|
+
.attr('class', 'entity-name')
|
|
317
|
+
.attr('text-anchor', 'middle')
|
|
318
|
+
.attr('y', (d) => -d.height / 2 + 20)
|
|
319
|
+
.attr('font-size', '12px')
|
|
320
|
+
.attr('font-weight', 'bold')
|
|
321
|
+
.attr('fill', 'white')
|
|
322
|
+
.text((d) => d.name);
|
|
323
|
+
// Add primary key fields and foreign key fields
|
|
324
|
+
node.each(function (d) {
|
|
325
|
+
const group = d3.select(this);
|
|
326
|
+
let currentY = -d.height / 2 + 40;
|
|
327
|
+
// Add primary keys
|
|
328
|
+
d.primaryKeys.forEach((pk) => {
|
|
329
|
+
const fieldGroup = group.append('g').attr('class', 'field-group primary-key');
|
|
330
|
+
fieldGroup
|
|
331
|
+
.append('rect')
|
|
332
|
+
.attr('class', 'field-bg')
|
|
333
|
+
.attr('x', -d.width / 2 + 2)
|
|
334
|
+
.attr('y', currentY - 15)
|
|
335
|
+
.attr('width', d.width - 4)
|
|
336
|
+
.attr('height', 18)
|
|
337
|
+
.attr('fill', '#fff3cd');
|
|
338
|
+
fieldGroup
|
|
339
|
+
.append('text')
|
|
340
|
+
.attr('class', 'field-icon')
|
|
341
|
+
.attr('x', -d.width / 2 + 8)
|
|
342
|
+
.attr('y', currentY - 2)
|
|
343
|
+
.attr('font-size', '10px')
|
|
344
|
+
.attr('fill', '#856404')
|
|
345
|
+
.text('🔑');
|
|
346
|
+
fieldGroup
|
|
347
|
+
.append('text')
|
|
348
|
+
.attr('class', 'field-name')
|
|
349
|
+
.attr('x', -d.width / 2 + 25)
|
|
350
|
+
.attr('y', currentY - 2)
|
|
351
|
+
.attr('font-size', '11px')
|
|
352
|
+
.attr('font-weight', 'bold')
|
|
353
|
+
.attr('fill', '#856404')
|
|
354
|
+
.text(pk.Name || '');
|
|
355
|
+
currentY += 20;
|
|
356
|
+
});
|
|
357
|
+
// Add foreign key fields
|
|
358
|
+
d.foreignKeys.forEach((fk) => {
|
|
359
|
+
const fieldGroup = group.append('g').attr('class', 'field-group foreign-key');
|
|
360
|
+
fieldGroup
|
|
361
|
+
.append('rect')
|
|
362
|
+
.attr('class', 'field-bg')
|
|
363
|
+
.attr('x', -d.width / 2 + 2)
|
|
364
|
+
.attr('y', currentY - 15)
|
|
365
|
+
.attr('width', d.width - 4)
|
|
366
|
+
.attr('height', 18)
|
|
367
|
+
.attr('fill', '#cce5ff');
|
|
368
|
+
fieldGroup
|
|
369
|
+
.append('text')
|
|
370
|
+
.attr('class', 'field-icon')
|
|
371
|
+
.attr('x', -d.width / 2 + 8)
|
|
372
|
+
.attr('y', currentY - 2)
|
|
373
|
+
.attr('font-size', '10px')
|
|
374
|
+
.attr('fill', '#004085')
|
|
375
|
+
.text('🔗');
|
|
376
|
+
fieldGroup
|
|
377
|
+
.append('text')
|
|
378
|
+
.attr('class', 'field-name')
|
|
379
|
+
.attr('x', -d.width / 2 + 25)
|
|
380
|
+
.attr('y', currentY - 2)
|
|
381
|
+
.attr('font-size', '11px')
|
|
382
|
+
.attr('fill', '#004085')
|
|
383
|
+
.text(fk.Name || '');
|
|
384
|
+
currentY += 20;
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
// Add click handler for entity selection
|
|
388
|
+
node
|
|
389
|
+
.on('click', (event, d) => {
|
|
390
|
+
event.stopPropagation();
|
|
391
|
+
// Check if this entity is already selected
|
|
392
|
+
if (this.selectedEntityId === d.entity.ID) {
|
|
393
|
+
// If clicking on the selected entity, deselect it
|
|
394
|
+
this.entityDeselected.emit();
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
// Select the new entity
|
|
398
|
+
this.entitySelected.emit(d.entity);
|
|
399
|
+
// Check if entity is too small and needs auto-zoom
|
|
400
|
+
const currentTransform = d3.zoomTransform(this.svg.node());
|
|
401
|
+
const currentRenderedWidth = d.width * currentTransform.k;
|
|
402
|
+
const shouldAutoZoom = currentRenderedWidth < 20;
|
|
403
|
+
if (shouldAutoZoom) {
|
|
404
|
+
this.zoomToEntity(d.entity.ID);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
// Update visual selection state
|
|
408
|
+
this.updateSelectionHighlighting();
|
|
409
|
+
})
|
|
410
|
+
.append('title')
|
|
411
|
+
.text((d) => `${d.name}\nPrimary Keys: ${d.primaryKeys.length}\nForeign Keys: ${d.foreignKeys.length}`);
|
|
412
|
+
// Update positions on simulation tick
|
|
413
|
+
this.simulation.on('tick', () => {
|
|
414
|
+
// Update link positions with proper rectangle edge connections
|
|
415
|
+
link.select('path').attr('d', (d) => {
|
|
416
|
+
const source = d.source;
|
|
417
|
+
const target = d.target;
|
|
418
|
+
if (d.isSelfReference) {
|
|
419
|
+
// Handle self-referencing relationships with a loop
|
|
420
|
+
const loopSize = 30;
|
|
421
|
+
return `M ${source.x + source.width / 2} ${source.y}
|
|
422
|
+
Q ${source.x + source.width / 2 + loopSize} ${source.y - loopSize}
|
|
423
|
+
${source.x} ${source.y - source.height / 2}`;
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
// Calculate connection points with 90-degree routing
|
|
427
|
+
const sourceConnectPoint = this.getSourceConnectionPoint(source, target, d.sourceField);
|
|
428
|
+
const targetConnectPoint = this.getTargetConnectionPoint(target, d.targetField);
|
|
429
|
+
return this.createOrthogonalPath(sourceConnectPoint, targetConnectPoint);
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
// Update link labels
|
|
433
|
+
link.select('text').attr('transform', (d) => {
|
|
434
|
+
if (d.isSelfReference) {
|
|
435
|
+
const source = d.source;
|
|
436
|
+
return `translate(${source.x + source.width / 2 + 15}, ${source.y - source.height / 2 - 10})`;
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
const source = d.source;
|
|
440
|
+
const target = d.target;
|
|
441
|
+
const midX = (source.x + target.x) / 2;
|
|
442
|
+
const midY = (source.y + target.y) / 2;
|
|
443
|
+
return `translate(${midX}, ${midY - 8})`;
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
node.attr('transform', (d) => `translate(${d.x},${d.y})`);
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
getSourceConnectionPoint(sourceNode, _targetNode, field) {
|
|
450
|
+
// Always connect from the right edge for source connections
|
|
451
|
+
let connectY = sourceNode.y;
|
|
452
|
+
// If we have field information, connect near the foreign key field
|
|
453
|
+
if (field) {
|
|
454
|
+
const fkIndex = sourceNode.foreignKeys.findIndex((fk) => fk.ID === field.ID);
|
|
455
|
+
if (fkIndex >= 0) {
|
|
456
|
+
const fieldY = -sourceNode.height / 2 + 40 + sourceNode.primaryKeys.length * 20 + fkIndex * 20;
|
|
457
|
+
connectY = sourceNode.y + fieldY + 10;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return {
|
|
461
|
+
x: sourceNode.x + sourceNode.width / 2,
|
|
462
|
+
y: connectY,
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
getTargetConnectionPoint(targetNode, targetField) {
|
|
466
|
+
// Always connect to the left edge of target
|
|
467
|
+
let connectY = targetNode.y - targetNode.height / 2 + 40; // Default to primary key area
|
|
468
|
+
// If we have target field information, connect near the specific primary key field
|
|
469
|
+
if (targetField && targetField.IsPrimaryKey) {
|
|
470
|
+
const pkIndex = targetNode.primaryKeys.findIndex((pk) => pk.ID === targetField.ID);
|
|
471
|
+
if (pkIndex >= 0) {
|
|
472
|
+
const fieldY = -targetNode.height / 2 + 40 + pkIndex * 20;
|
|
473
|
+
connectY = targetNode.y + fieldY + 10;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return {
|
|
477
|
+
x: targetNode.x - targetNode.width / 2,
|
|
478
|
+
y: connectY,
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
createOrthogonalPath(source, target) {
|
|
482
|
+
// Ensure right-to-left connection with proper orthogonal routing
|
|
483
|
+
const dx = target.x - source.x;
|
|
484
|
+
// Calculate midpoint for orthogonal routing
|
|
485
|
+
let midX;
|
|
486
|
+
if (dx > 0) {
|
|
487
|
+
// Target is to the right of source - use 70% of distance for turn point
|
|
488
|
+
midX = source.x + dx * 0.7;
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
// Target is to the left of source - create wider arc for better visibility
|
|
492
|
+
midX = source.x + Math.max(dx * 0.3, -50); // Don't go too far left
|
|
493
|
+
}
|
|
494
|
+
// Create clean orthogonal path
|
|
495
|
+
return `M ${source.x} ${source.y}
|
|
496
|
+
L ${midX} ${source.y}
|
|
497
|
+
L ${midX} ${target.y}
|
|
498
|
+
L ${target.x} ${target.y}`;
|
|
499
|
+
}
|
|
500
|
+
clearAllHighlighting() {
|
|
501
|
+
if (!this.svg)
|
|
502
|
+
return;
|
|
503
|
+
this.svg.selectAll('.node')
|
|
504
|
+
.classed('selected', false)
|
|
505
|
+
.classed('relationship-connected', false)
|
|
506
|
+
.classed('entity-connections-highlighted', false);
|
|
507
|
+
this.svg.selectAll('.entity-rect')
|
|
508
|
+
.classed('highlighted', false)
|
|
509
|
+
.classed('relationship-highlighted', false)
|
|
510
|
+
.classed('connection-highlighted', false)
|
|
511
|
+
.style('stroke', '#333')
|
|
512
|
+
.style('stroke-width', '2px')
|
|
513
|
+
.style('filter', null);
|
|
514
|
+
this.svg.selectAll('.link-group')
|
|
515
|
+
.classed('highlighted', false);
|
|
516
|
+
this.svg.selectAll('.link')
|
|
517
|
+
.classed('highlighted', false);
|
|
518
|
+
this.svg.selectAll('.link-label')
|
|
519
|
+
.classed('highlighted', false);
|
|
520
|
+
}
|
|
521
|
+
updateSelectionHighlighting() {
|
|
522
|
+
if (!this.svg)
|
|
523
|
+
return;
|
|
524
|
+
// Clear existing selections
|
|
525
|
+
this.clearAllHighlighting();
|
|
526
|
+
// Highlight selected entity if any
|
|
527
|
+
if (this.selectedEntityId) {
|
|
528
|
+
this.svg.selectAll('.node')
|
|
529
|
+
.filter((d) => d.id === this.selectedEntityId)
|
|
530
|
+
.classed('selected', true)
|
|
531
|
+
.select('.entity-rect')
|
|
532
|
+
.style('stroke', '#4CAF50')
|
|
533
|
+
.style('stroke-width', '4px')
|
|
534
|
+
.style('filter', 'drop-shadow(0 0 8px rgba(76, 175, 80, 0.6))');
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
zoomToEntity(entityId) {
|
|
538
|
+
const node = this.nodes.find(n => n.id === entityId);
|
|
539
|
+
if (!node || !this.zoom)
|
|
540
|
+
return;
|
|
541
|
+
const container = this.erdContainer.nativeElement;
|
|
542
|
+
const width = container.clientWidth;
|
|
543
|
+
const height = container.clientHeight;
|
|
544
|
+
const scale = 1.5;
|
|
545
|
+
const x = width / 2 - (node.x || 0) * scale;
|
|
546
|
+
const y = height / 2 - (node.y || 0) * scale;
|
|
547
|
+
this.svg.transition().duration(750).call(this.zoom.transform, d3.zoomIdentity.translate(x, y).scale(scale));
|
|
548
|
+
}
|
|
549
|
+
dragstarted(event, d) {
|
|
550
|
+
if (!event.active)
|
|
551
|
+
this.simulation.alphaTarget(0.3).restart();
|
|
552
|
+
d.fx = d.x;
|
|
553
|
+
d.fy = d.y;
|
|
554
|
+
}
|
|
555
|
+
dragged(event, d) {
|
|
556
|
+
d.fx = event.x;
|
|
557
|
+
d.fy = event.y;
|
|
558
|
+
}
|
|
559
|
+
dragended(event, d) {
|
|
560
|
+
if (!event.active)
|
|
561
|
+
this.simulation.alphaTarget(0);
|
|
562
|
+
d.fx = null;
|
|
563
|
+
d.fy = null;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
ERDDiagramComponent.ɵfac = function ERDDiagramComponent_Factory(t) { return new (t || ERDDiagramComponent)(); };
|
|
567
|
+
ERDDiagramComponent.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: ERDDiagramComponent, selectors: [["mj-erd-diagram"]], viewQuery: function ERDDiagramComponent_Query(rf, ctx) { if (rf & 1) {
|
|
568
|
+
i0.ɵɵviewQuery(_c0, 5);
|
|
569
|
+
} if (rf & 2) {
|
|
570
|
+
let _t;
|
|
571
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.erdContainer = _t.first);
|
|
572
|
+
} }, inputs: { filteredEntities: "filteredEntities", allEntityFields: "allEntityFields", isRefreshingERD: "isRefreshingERD", selectedEntityId: "selectedEntityId" }, outputs: { entitySelected: "entitySelected", entityDeselected: "entityDeselected", refreshERD: "refreshERD" }, features: [i0.ɵɵNgOnChangesFeature], decls: 15, vars: 0, consts: [["erdContainer", ""], [1, "erd-section"], [1, "section-header"], [1, "erd-controls"], ["title", "Zoom In", 1, "control-btn", 3, "click"], [1, "fa-solid", "fa-search-plus"], ["title", "Zoom Out", 1, "control-btn", 3, "click"], [1, "fa-solid", "fa-search-minus"], ["title", "Reset Zoom", 1, "control-btn", 3, "click"], [1, "fa-solid", "fa-expand"], ["title", "Refresh Diagram", 1, "control-btn", 3, "click"], [1, "fa-solid", "fa-refresh"], [1, "erd-container"]], template: function ERDDiagramComponent_Template(rf, ctx) { if (rf & 1) {
|
|
573
|
+
const _r1 = i0.ɵɵgetCurrentView();
|
|
574
|
+
i0.ɵɵelementStart(0, "div", 1)(1, "div", 2)(2, "h3");
|
|
575
|
+
i0.ɵɵtext(3, "Entity Relationship Diagram");
|
|
576
|
+
i0.ɵɵelementEnd();
|
|
577
|
+
i0.ɵɵelementStart(4, "div", 3)(5, "button", 4);
|
|
578
|
+
i0.ɵɵlistener("click", function ERDDiagramComponent_Template_button_click_5_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.zoomIn()); });
|
|
579
|
+
i0.ɵɵelement(6, "span", 5);
|
|
580
|
+
i0.ɵɵelementEnd();
|
|
581
|
+
i0.ɵɵelementStart(7, "button", 6);
|
|
582
|
+
i0.ɵɵlistener("click", function ERDDiagramComponent_Template_button_click_7_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.zoomOut()); });
|
|
583
|
+
i0.ɵɵelement(8, "span", 7);
|
|
584
|
+
i0.ɵɵelementEnd();
|
|
585
|
+
i0.ɵɵelementStart(9, "button", 8);
|
|
586
|
+
i0.ɵɵlistener("click", function ERDDiagramComponent_Template_button_click_9_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.resetZoom()); });
|
|
587
|
+
i0.ɵɵelement(10, "span", 9);
|
|
588
|
+
i0.ɵɵelementEnd();
|
|
589
|
+
i0.ɵɵelementStart(11, "button", 10);
|
|
590
|
+
i0.ɵɵlistener("click", function ERDDiagramComponent_Template_button_click_11_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.setupERD()); });
|
|
591
|
+
i0.ɵɵelement(12, "span", 11);
|
|
592
|
+
i0.ɵɵelementEnd()()();
|
|
593
|
+
i0.ɵɵelement(13, "div", 12, 0);
|
|
594
|
+
i0.ɵɵelementEnd();
|
|
595
|
+
} }, styles: [".erd-section[_ngcontent-%COMP%] {\n background: white;\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.section-header[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 12px 16px;\n background-color: #f5f5f5;\n border-bottom: 1px solid #e0e0e0;\n \n h3 {\n margin: 0;\n font-size: 14px;\n font-weight: 600;\n color: #333;\n }\n}\n\n.erd-controls[_ngcontent-%COMP%] {\n display: flex;\n gap: 8px;\n}\n\n.control-btn[_ngcontent-%COMP%] {\n padding: 4px 8px;\n border: 1px solid #ccc;\n border-radius: 4px;\n background: white;\n cursor: pointer;\n font-size: 12px;\n \n &:hover {\n background-color: #f0f0f0;\n }\n}\n\n.erd-container[_ngcontent-%COMP%] {\n flex: 1;\n position: relative;\n background-color: #fafafa;\n overflow: hidden;\n min-height: 400px;\n width: 100%;\n max-width: 100%;\n box-sizing: border-box;\n \n svg {\n position: absolute;\n top: 0;\n left: 0;\n width: 100% !important;\n height: 100% !important;\n max-width: 100%;\n max-height: 100%;\n box-sizing: border-box;\n }\n}\n\n.loading-overlay[_ngcontent-%COMP%] {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(250, 250, 250, 0.95);\n backdrop-filter: blur(2px);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n animation: fadeIn 0.3s ease-in-out;\n}\n\n.loading-content[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 16px;\n}\n\n.loading-spinner[_ngcontent-%COMP%] {\n position: relative;\n width: 60px;\n height: 60px;\n}\n\n.spinner-ring[_ngcontent-%COMP%] {\n position: absolute;\n width: 100%;\n height: 100%;\n border: 3px solid transparent;\n border-radius: 50%;\n animation: _ngcontent-%COMP%_spin 1.5s linear infinite;\n \n &:nth-child(1) {\n border-top-color: #2196f3;\n animation-delay: 0s;\n }\n \n &:nth-child(2) {\n border-top-color: #9c27b0;\n animation-delay: 0.3s;\n transform: scale(0.8);\n }\n \n &:nth-child(3) {\n border-top-color: #ff6b35;\n animation-delay: 0.6s;\n transform: scale(0.6);\n }\n}\n\n.loading-text[_ngcontent-%COMP%] {\n font-size: 14px;\n color: #666;\n font-weight: 500;\n text-align: center;\n}\n\n.control-btn[_ngcontent-%COMP%]:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n@keyframes _ngcontent-%COMP%_spin {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n}\n\n// SVG visualization styles\n[_nghost-%COMP%] {\n .erd-container svg {\n .node .entity-rect.highlighted {\n stroke: #ff9800 !important;\n stroke-width: 4px !important;\n filter: drop-shadow(0 0 8px rgba(255, 152, 0, 0.6));\n animation: pulse 1s ease-in-out infinite alternate;\n }\n \n .node.selected {\n .entity-rect {\n stroke: #2196f3 !important;\n stroke-width: 4px !important;\n filter: drop-shadow(0 0 12px rgba(33, 150, 243, 0.8));\n fill: #f3f8ff !important;\n animation: selectedPulse 2s ease-in-out infinite alternate;\n }\n \n .entity-header {\n fill: #2196f3 !important;\n }\n \n .entity-header-bottom {\n fill: #2196f3 !important;\n }\n \n .entity-name {\n fill: white !important;\n font-weight: bold !important;\n }\n }\n \n @keyframes selectedPulse {\n 0% { \n stroke-width: 4px;\n filter: drop-shadow(0 0 12px rgba(33, 150, 243, 0.8));\n }\n 100% { \n stroke-width: 5px;\n filter: drop-shadow(0 0 16px rgba(33, 150, 243, 1));\n }\n }\n \n .node.relationship-connected {\n .entity-rect.relationship-highlighted {\n stroke: #ff6b35 !important;\n stroke-width: 3px !important;\n filter: drop-shadow(0 0 6px rgba(255, 107, 53, 0.5));\n animation: entityPulse 1.5s ease-in-out infinite alternate;\n }\n \n .entity-header {\n fill: #ff6b35 !important;\n }\n \n .entity-header-bottom {\n fill: #ff6b35 !important;\n }\n }\n \n .node.entity-connections-highlighted {\n .entity-rect.connection-highlighted {\n stroke: #9b59b6 !important;\n stroke-width: 3px !important;\n filter: drop-shadow(0 0 6px rgba(155, 89, 182, 0.5));\n opacity: 0.9;\n }\n \n .entity-header {\n fill: #9b59b6 !important;\n opacity: 0.8;\n }\n \n .entity-header-bottom {\n fill: #9b59b6 !important;\n opacity: 0.8;\n }\n }\n \n .link-group {\n .link {\n stroke-width: 2px;\n transition: all 0.2s ease;\n cursor: pointer;\n \n &:hover {\n stroke-width: 3px;\n stroke: #ff6b35;\n opacity: 1;\n }\n \n &.highlighted {\n stroke: #ff6b35 !important;\n stroke-width: 4px !important;\n opacity: 1 !important;\n filter: drop-shadow(0 0 6px rgba(255, 107, 53, 0.6));\n animation: linkPulse 1.5s ease-in-out infinite alternate;\n }\n }\n \n .link-label {\n font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n opacity: 0.8;\n pointer-events: none;\n transition: opacity 0.2s ease;\n \n &.highlighted {\n opacity: 1;\n font-weight: bold;\n fill: #ff6b35;\n }\n }\n \n &.highlighted {\n z-index: 10;\n }\n }\n \n .field-group {\n .field-bg {\n transition: fill 0.2s;\n }\n \n &:hover .field-bg {\n fill: #e3f2fd !important;\n }\n }\n \n @keyframes pulse {\n 0% { stroke-width: 4px; }\n 100% { stroke-width: 6px; }\n }\n \n @keyframes linkPulse {\n 0% { \n stroke-width: 4px; \n filter: drop-shadow(0 0 6px rgba(255, 107, 53, 0.6));\n }\n 100% { \n stroke-width: 5px; \n filter: drop-shadow(0 0 10px rgba(255, 107, 53, 0.8));\n }\n }\n \n @keyframes entityPulse {\n 0% { \n stroke-width: 3px; \n filter: drop-shadow(0 0 6px rgba(255, 107, 53, 0.5));\n }\n 100% { \n stroke-width: 4px; \n filter: drop-shadow(0 0 8px rgba(255, 107, 53, 0.7));\n }\n }\n }\n}"] });
|
|
596
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ERDDiagramComponent, [{
|
|
597
|
+
type: Component,
|
|
598
|
+
args: [{ selector: 'mj-erd-diagram', template: "<div class=\"erd-section\">\n <div class=\"section-header\">\n <h3>Entity Relationship Diagram</h3>\n <div class=\"erd-controls\">\n <button class=\"control-btn\" (click)=\"zoomIn()\" title=\"Zoom In\">\n <span class=\"fa-solid fa-search-plus\"></span>\n </button>\n <button class=\"control-btn\" (click)=\"zoomOut()\" title=\"Zoom Out\">\n <span class=\"fa-solid fa-search-minus\"></span>\n </button>\n <button class=\"control-btn\" (click)=\"resetZoom()\" title=\"Reset Zoom\">\n <span class=\"fa-solid fa-expand\"></span>\n </button>\n <button class=\"control-btn\" (click)=\"setupERD()\" title=\"Refresh Diagram\">\n <span class=\"fa-solid fa-refresh\"></span>\n </button>\n </div>\n </div>\n <div class=\"erd-container\" #erdContainer></div>\n</div>", styles: [".erd-section {\n background: white;\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.section-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 12px 16px;\n background-color: #f5f5f5;\n border-bottom: 1px solid #e0e0e0;\n \n h3 {\n margin: 0;\n font-size: 14px;\n font-weight: 600;\n color: #333;\n }\n}\n\n.erd-controls {\n display: flex;\n gap: 8px;\n}\n\n.control-btn {\n padding: 4px 8px;\n border: 1px solid #ccc;\n border-radius: 4px;\n background: white;\n cursor: pointer;\n font-size: 12px;\n \n &:hover {\n background-color: #f0f0f0;\n }\n}\n\n.erd-container {\n flex: 1;\n position: relative;\n background-color: #fafafa;\n overflow: hidden;\n min-height: 400px;\n width: 100%;\n max-width: 100%;\n box-sizing: border-box;\n \n svg {\n position: absolute;\n top: 0;\n left: 0;\n width: 100% !important;\n height: 100% !important;\n max-width: 100%;\n max-height: 100%;\n box-sizing: border-box;\n }\n}\n\n.loading-overlay {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(250, 250, 250, 0.95);\n backdrop-filter: blur(2px);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n animation: fadeIn 0.3s ease-in-out;\n}\n\n.loading-content {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 16px;\n}\n\n.loading-spinner {\n position: relative;\n width: 60px;\n height: 60px;\n}\n\n.spinner-ring {\n position: absolute;\n width: 100%;\n height: 100%;\n border: 3px solid transparent;\n border-radius: 50%;\n animation: spin 1.5s linear infinite;\n \n &:nth-child(1) {\n border-top-color: #2196f3;\n animation-delay: 0s;\n }\n \n &:nth-child(2) {\n border-top-color: #9c27b0;\n animation-delay: 0.3s;\n transform: scale(0.8);\n }\n \n &:nth-child(3) {\n border-top-color: #ff6b35;\n animation-delay: 0.6s;\n transform: scale(0.6);\n }\n}\n\n.loading-text {\n font-size: 14px;\n color: #666;\n font-weight: 500;\n text-align: center;\n}\n\n.control-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n@keyframes spin {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n}\n\n// SVG visualization styles\n:host ::ng-deep {\n .erd-container svg {\n .node .entity-rect.highlighted {\n stroke: #ff9800 !important;\n stroke-width: 4px !important;\n filter: drop-shadow(0 0 8px rgba(255, 152, 0, 0.6));\n animation: pulse 1s ease-in-out infinite alternate;\n }\n \n .node.selected {\n .entity-rect {\n stroke: #2196f3 !important;\n stroke-width: 4px !important;\n filter: drop-shadow(0 0 12px rgba(33, 150, 243, 0.8));\n fill: #f3f8ff !important;\n animation: selectedPulse 2s ease-in-out infinite alternate;\n }\n \n .entity-header {\n fill: #2196f3 !important;\n }\n \n .entity-header-bottom {\n fill: #2196f3 !important;\n }\n \n .entity-name {\n fill: white !important;\n font-weight: bold !important;\n }\n }\n \n @keyframes selectedPulse {\n 0% { \n stroke-width: 4px;\n filter: drop-shadow(0 0 12px rgba(33, 150, 243, 0.8));\n }\n 100% { \n stroke-width: 5px;\n filter: drop-shadow(0 0 16px rgba(33, 150, 243, 1));\n }\n }\n \n .node.relationship-connected {\n .entity-rect.relationship-highlighted {\n stroke: #ff6b35 !important;\n stroke-width: 3px !important;\n filter: drop-shadow(0 0 6px rgba(255, 107, 53, 0.5));\n animation: entityPulse 1.5s ease-in-out infinite alternate;\n }\n \n .entity-header {\n fill: #ff6b35 !important;\n }\n \n .entity-header-bottom {\n fill: #ff6b35 !important;\n }\n }\n \n .node.entity-connections-highlighted {\n .entity-rect.connection-highlighted {\n stroke: #9b59b6 !important;\n stroke-width: 3px !important;\n filter: drop-shadow(0 0 6px rgba(155, 89, 182, 0.5));\n opacity: 0.9;\n }\n \n .entity-header {\n fill: #9b59b6 !important;\n opacity: 0.8;\n }\n \n .entity-header-bottom {\n fill: #9b59b6 !important;\n opacity: 0.8;\n }\n }\n \n .link-group {\n .link {\n stroke-width: 2px;\n transition: all 0.2s ease;\n cursor: pointer;\n \n &:hover {\n stroke-width: 3px;\n stroke: #ff6b35;\n opacity: 1;\n }\n \n &.highlighted {\n stroke: #ff6b35 !important;\n stroke-width: 4px !important;\n opacity: 1 !important;\n filter: drop-shadow(0 0 6px rgba(255, 107, 53, 0.6));\n animation: linkPulse 1.5s ease-in-out infinite alternate;\n }\n }\n \n .link-label {\n font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n opacity: 0.8;\n pointer-events: none;\n transition: opacity 0.2s ease;\n \n &.highlighted {\n opacity: 1;\n font-weight: bold;\n fill: #ff6b35;\n }\n }\n \n &.highlighted {\n z-index: 10;\n }\n }\n \n .field-group {\n .field-bg {\n transition: fill 0.2s;\n }\n \n &:hover .field-bg {\n fill: #e3f2fd !important;\n }\n }\n \n @keyframes pulse {\n 0% { stroke-width: 4px; }\n 100% { stroke-width: 6px; }\n }\n \n @keyframes linkPulse {\n 0% { \n stroke-width: 4px; \n filter: drop-shadow(0 0 6px rgba(255, 107, 53, 0.6));\n }\n 100% { \n stroke-width: 5px; \n filter: drop-shadow(0 0 10px rgba(255, 107, 53, 0.8));\n }\n }\n \n @keyframes entityPulse {\n 0% { \n stroke-width: 3px; \n filter: drop-shadow(0 0 6px rgba(255, 107, 53, 0.5));\n }\n 100% { \n stroke-width: 4px; \n filter: drop-shadow(0 0 8px rgba(255, 107, 53, 0.7));\n }\n }\n }\n}"] }]
|
|
599
|
+
}], null, { erdContainer: [{
|
|
600
|
+
type: ViewChild,
|
|
601
|
+
args: ['erdContainer', { static: false }]
|
|
602
|
+
}], filteredEntities: [{
|
|
603
|
+
type: Input
|
|
604
|
+
}], allEntityFields: [{
|
|
605
|
+
type: Input
|
|
606
|
+
}], isRefreshingERD: [{
|
|
607
|
+
type: Input
|
|
608
|
+
}], selectedEntityId: [{
|
|
609
|
+
type: Input
|
|
610
|
+
}], entitySelected: [{
|
|
611
|
+
type: Output
|
|
612
|
+
}], entityDeselected: [{
|
|
613
|
+
type: Output
|
|
614
|
+
}], refreshERD: [{
|
|
615
|
+
type: Output
|
|
616
|
+
}] }); })();
|
|
617
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(ERDDiagramComponent, { className: "ERDDiagramComponent", filePath: "src/EntityAdmin/components/erd-diagram.component.ts", lineNumber: 33 }); })();
|
|
618
|
+
//# sourceMappingURL=erd-diagram.component.js.map
|