dbviewer 0.6.7 → 0.6.8
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.
- checksums.yaml +4 -4
- data/app/assets/javascripts/dbviewer/entity_relationship_diagram.js +553 -0
- data/app/assets/javascripts/dbviewer/home.js +287 -0
- data/app/assets/javascripts/dbviewer/layout.js +194 -0
- data/app/assets/javascripts/dbviewer/query.js +277 -0
- data/app/assets/javascripts/dbviewer/table.js +1563 -0
- data/app/assets/stylesheets/dbviewer/application.css +1460 -21
- data/app/assets/stylesheets/dbviewer/entity_relationship_diagram.css +181 -0
- data/app/assets/stylesheets/dbviewer/home.css +229 -0
- data/app/assets/stylesheets/dbviewer/logs.css +64 -0
- data/app/assets/stylesheets/dbviewer/query.css +171 -0
- data/app/assets/stylesheets/dbviewer/table.css +1144 -0
- data/app/views/dbviewer/connections/index.html.erb +0 -30
- data/app/views/dbviewer/entity_relationship_diagrams/index.html.erb +14 -713
- data/app/views/dbviewer/home/index.html.erb +9 -499
- data/app/views/dbviewer/logs/index.html.erb +5 -220
- data/app/views/dbviewer/tables/index.html.erb +0 -65
- data/app/views/dbviewer/tables/query.html.erb +129 -565
- data/app/views/dbviewer/tables/show.html.erb +4 -2429
- data/app/views/layouts/dbviewer/application.html.erb +13 -1544
- data/lib/dbviewer/version.rb +1 -1
- metadata +12 -7
- data/app/assets/javascripts/dbviewer/connections.js +0 -70
- data/app/assets/stylesheets/dbviewer/dbviewer.css +0 -0
- data/app/assets/stylesheets/dbviewer/enhanced.css +0 -0
- data/app/views/dbviewer/connections/new.html.erb +0 -79
- data/app/views/dbviewer/tables/mini_erd.html.erb +0 -517
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b63b072de9449e22dadd1dd7ec47dcdb4a1952e90c4461823783a844e147f026
|
4
|
+
data.tar.gz: ea463968ddf5b69a22a82b9d4213e1d8e60856878be5d8bbe72f0345b1417794
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 47ea6ee4e345f6ac05c5c475918aa84fabbe28469409fc62755997991c3700a1797a394183a75db91721c49fcde0c493680621f6be93e6625ab7f7055b66379e
|
7
|
+
data.tar.gz: 7cffe71b1bcfd22583c0796c3816eb10a0d164baf1a304e62a4b541a3db75c13b671c61aa0b94450a1ee5cd33bcf365c43e27320b0c9ff970c13c715f57953df
|
@@ -0,0 +1,553 @@
|
|
1
|
+
document.addEventListener("DOMContentLoaded", function () {
|
2
|
+
// Check if mermaid is loaded first
|
3
|
+
if (typeof mermaid === "undefined") {
|
4
|
+
console.error("Mermaid library not loaded!");
|
5
|
+
showError(
|
6
|
+
"Mermaid library not loaded",
|
7
|
+
"The diagram library could not be loaded. Please check your internet connection and try again."
|
8
|
+
);
|
9
|
+
return;
|
10
|
+
}
|
11
|
+
|
12
|
+
// Initialize mermaid with theme detection like mini ERD
|
13
|
+
mermaid.initialize({
|
14
|
+
startOnLoad: true,
|
15
|
+
theme:
|
16
|
+
document.documentElement.getAttribute("data-bs-theme") === "dark"
|
17
|
+
? "dark"
|
18
|
+
: "default",
|
19
|
+
securityLevel: "loose",
|
20
|
+
er: {
|
21
|
+
diagramPadding: 20,
|
22
|
+
layoutDirection: "TB",
|
23
|
+
minEntityWidth: 100,
|
24
|
+
minEntityHeight: 75,
|
25
|
+
entityPadding: 15,
|
26
|
+
stroke: "gray",
|
27
|
+
fill: "honeydew",
|
28
|
+
fontSize: 20,
|
29
|
+
},
|
30
|
+
});
|
31
|
+
|
32
|
+
// Function to show error messages
|
33
|
+
function showError(title, message, details = "") {
|
34
|
+
const errorContainer = document.getElementById("erd-error");
|
35
|
+
const errorMessage = document.getElementById("erd-error-message");
|
36
|
+
const errorDetails = document.getElementById("erd-error-details");
|
37
|
+
const loadingIndicator = document.getElementById("erd-loading");
|
38
|
+
|
39
|
+
if (loadingIndicator) {
|
40
|
+
loadingIndicator.style.display = "none";
|
41
|
+
}
|
42
|
+
|
43
|
+
if (errorContainer && errorMessage) {
|
44
|
+
// Set error message
|
45
|
+
errorMessage.textContent = message;
|
46
|
+
|
47
|
+
// Set error details if provided
|
48
|
+
if (details && errorDetails) {
|
49
|
+
errorDetails.textContent = details;
|
50
|
+
errorDetails.classList.remove("d-none");
|
51
|
+
} else if (errorDetails) {
|
52
|
+
errorDetails.classList.add("d-none");
|
53
|
+
}
|
54
|
+
|
55
|
+
// Show the error container
|
56
|
+
errorContainer.classList.remove("d-none");
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
// ER Diagram download functionality
|
61
|
+
let diagramReady = false;
|
62
|
+
|
63
|
+
// Function to show a temporary downloading indicator
|
64
|
+
function showDownloadingIndicator(format) {
|
65
|
+
// Create toast element
|
66
|
+
const toastEl = document.createElement("div");
|
67
|
+
toastEl.className = "position-fixed bottom-0 end-0 p-3";
|
68
|
+
toastEl.style.zIndex = "5000";
|
69
|
+
toastEl.innerHTML = `
|
70
|
+
<div class="toast show" role="alert" aria-live="assertive" aria-atomic="true">
|
71
|
+
<div class="toast-header">
|
72
|
+
<strong class="me-auto"><i class="bi bi-download"></i> Downloading ERD</strong>
|
73
|
+
<small>just now</small>
|
74
|
+
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
75
|
+
</div>
|
76
|
+
<div class="toast-body">
|
77
|
+
<div class="d-flex align-items-center">
|
78
|
+
<div class="spinner-border spinner-border-sm me-2" role="status">
|
79
|
+
<span class="visually-hidden">Loading...</span>
|
80
|
+
</div>
|
81
|
+
Preparing ${format} file for download...
|
82
|
+
</div>
|
83
|
+
</div>
|
84
|
+
</div>
|
85
|
+
`;
|
86
|
+
|
87
|
+
document.body.appendChild(toastEl);
|
88
|
+
|
89
|
+
// Automatically remove after a delay
|
90
|
+
setTimeout(() => {
|
91
|
+
toastEl.remove();
|
92
|
+
}, 3000);
|
93
|
+
}
|
94
|
+
|
95
|
+
// Generate the ERD diagram
|
96
|
+
const tables = JSON.parse(document.getElementById("tables").value);
|
97
|
+
|
98
|
+
// Initialize empty relationships - will be loaded asynchronously
|
99
|
+
let relationships = [];
|
100
|
+
let relationshipsLoaded = false;
|
101
|
+
|
102
|
+
// Function to fetch relationships asynchronously
|
103
|
+
function fetchRelationships() {
|
104
|
+
const apiPath = document.getElementById("relationships_api_path").value;
|
105
|
+
return fetch(apiPath, {
|
106
|
+
headers: {
|
107
|
+
Accept: "application/json",
|
108
|
+
"X-Requested-With": "XMLHttpRequest",
|
109
|
+
},
|
110
|
+
})
|
111
|
+
.then((response) => {
|
112
|
+
if (!response.ok) {
|
113
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
114
|
+
}
|
115
|
+
return response.json();
|
116
|
+
})
|
117
|
+
.then((data) => {
|
118
|
+
relationships = data.relationships || [];
|
119
|
+
relationshipsLoaded = true;
|
120
|
+
updateRelationshipsStatus(true);
|
121
|
+
return relationships;
|
122
|
+
})
|
123
|
+
.catch((error) => {
|
124
|
+
console.error("Error fetching relationships:", error);
|
125
|
+
relationshipsLoaded = true; // Mark as loaded even on error to prevent infinite loading
|
126
|
+
updateRelationshipsStatus(true);
|
127
|
+
return [];
|
128
|
+
});
|
129
|
+
}
|
130
|
+
|
131
|
+
// Function to update loading status
|
132
|
+
function updateLoadingStatus(message) {
|
133
|
+
const loadingElement = document.getElementById("erd-loading");
|
134
|
+
const loadingPhase = document.getElementById("loading-phase");
|
135
|
+
if (loadingPhase) {
|
136
|
+
loadingPhase.textContent = message;
|
137
|
+
}
|
138
|
+
}
|
139
|
+
|
140
|
+
// Function to update table loading progress
|
141
|
+
function updateTableProgress(loaded, total) {
|
142
|
+
const progressBar = document.getElementById("table-progress-bar");
|
143
|
+
const progressText = document.getElementById("table-progress-text");
|
144
|
+
|
145
|
+
if (progressBar && progressText) {
|
146
|
+
const percentage = total > 0 ? Math.round((loaded / total) * 100) : 0;
|
147
|
+
progressBar.style.width = percentage + "%";
|
148
|
+
progressBar.setAttribute("aria-valuenow", percentage);
|
149
|
+
progressText.textContent = `${loaded} / ${total}`;
|
150
|
+
|
151
|
+
// Update progress bar color based on completion
|
152
|
+
if (percentage === 100) {
|
153
|
+
progressBar.classList.remove("bg-primary");
|
154
|
+
progressBar.classList.add("bg-success");
|
155
|
+
}
|
156
|
+
}
|
157
|
+
}
|
158
|
+
|
159
|
+
// Function to update relationships status
|
160
|
+
function updateRelationshipsStatus(loaded) {
|
161
|
+
const relationshipsStatus = document.getElementById("relationships-status");
|
162
|
+
if (relationshipsStatus) {
|
163
|
+
if (loaded) {
|
164
|
+
relationshipsStatus.innerHTML = `
|
165
|
+
<i class="bi bi-check-circle text-success me-2"></i>
|
166
|
+
<small class="text-success">Relationships loaded</small>
|
167
|
+
`;
|
168
|
+
} else {
|
169
|
+
relationshipsStatus.innerHTML = `
|
170
|
+
<div class="spinner-border spinner-border-sm text-secondary me-2" role="status">
|
171
|
+
<span class="visually-hidden">Loading...</span>
|
172
|
+
</div>
|
173
|
+
<small class="text-muted">Loading relationships...</small>
|
174
|
+
`;
|
175
|
+
}
|
176
|
+
}
|
177
|
+
}
|
178
|
+
|
179
|
+
// Create the ER diagram definition in Mermaid syntax
|
180
|
+
let mermaidDefinition = "erDiagram\n";
|
181
|
+
|
182
|
+
// We'll store table column data here as we fetch it
|
183
|
+
const tableColumns = {};
|
184
|
+
|
185
|
+
// Track loading progress
|
186
|
+
let columnsLoadedCount = 0;
|
187
|
+
const totalTables = tables.length;
|
188
|
+
|
189
|
+
// Initialize progress bar
|
190
|
+
updateTableProgress(0, totalTables);
|
191
|
+
updateLoadingStatus("Loading table details...");
|
192
|
+
|
193
|
+
// Start fetching relationships immediately
|
194
|
+
updateRelationshipsStatus(false);
|
195
|
+
const relationshipsPromise = fetchRelationships();
|
196
|
+
const tablePath = document.getElementById("tables_path").value;
|
197
|
+
|
198
|
+
// First pass: add all tables with minimal info and start loading columns
|
199
|
+
tables.forEach(function (table) {
|
200
|
+
const tableName = table.name;
|
201
|
+
mermaidDefinition += ` ${tableName} {\n`;
|
202
|
+
mermaidDefinition += ` string id\n`;
|
203
|
+
mermaidDefinition += " }\n";
|
204
|
+
|
205
|
+
// Start loading column data asynchronously
|
206
|
+
fetch(`${tablePath}/${tableName}?format=json`, {
|
207
|
+
headers: {
|
208
|
+
Accept: "application/json",
|
209
|
+
"X-Requested-With": "XMLHttpRequest",
|
210
|
+
},
|
211
|
+
})
|
212
|
+
.then((response) => response.json())
|
213
|
+
.then((data) => {
|
214
|
+
if (data && data.columns) {
|
215
|
+
tableColumns[tableName] = data.columns;
|
216
|
+
columnsLoadedCount++;
|
217
|
+
|
218
|
+
// Update progress bar
|
219
|
+
updateTableProgress(columnsLoadedCount, totalTables);
|
220
|
+
|
221
|
+
checkIfReadyToUpdate();
|
222
|
+
}
|
223
|
+
})
|
224
|
+
.catch((error) => {
|
225
|
+
console.error(`Error fetching columns for table ${tableName}:`, error);
|
226
|
+
columnsLoadedCount++;
|
227
|
+
updateTableProgress(columnsLoadedCount, totalTables);
|
228
|
+
checkIfReadyToUpdate();
|
229
|
+
});
|
230
|
+
});
|
231
|
+
|
232
|
+
// Function to check if we're ready to update the diagram with full data
|
233
|
+
function checkIfReadyToUpdate() {
|
234
|
+
if (columnsLoadedCount === totalTables && relationshipsLoaded) {
|
235
|
+
updateDiagramWithFullData();
|
236
|
+
}
|
237
|
+
}
|
238
|
+
|
239
|
+
// Wait for relationships to load and check if ready
|
240
|
+
relationshipsPromise.finally(() => {
|
241
|
+
checkIfReadyToUpdate();
|
242
|
+
});
|
243
|
+
|
244
|
+
// Track if we're currently updating the diagram
|
245
|
+
let isUpdatingDiagram = false;
|
246
|
+
|
247
|
+
// Function to update the diagram once we have all data
|
248
|
+
function updateDiagramWithFullData() {
|
249
|
+
// Prevent multiple simultaneous updates
|
250
|
+
if (isUpdatingDiagram) return;
|
251
|
+
|
252
|
+
isUpdatingDiagram = true;
|
253
|
+
|
254
|
+
updateLoadingStatus("Generating final diagram...");
|
255
|
+
|
256
|
+
// Regenerate the diagram with complete data
|
257
|
+
let updatedDefinition = "erDiagram\n";
|
258
|
+
|
259
|
+
tables.forEach(function (table) {
|
260
|
+
const tableName = table.name;
|
261
|
+
updatedDefinition += ` ${tableName} {\n`;
|
262
|
+
|
263
|
+
const columns = tableColumns[tableName] || [];
|
264
|
+
columns.forEach((column) => {
|
265
|
+
updatedDefinition += ` ${column.type || "string"} ${column.name}\n`;
|
266
|
+
});
|
267
|
+
|
268
|
+
updatedDefinition += " }\n";
|
269
|
+
});
|
270
|
+
|
271
|
+
// Add relationships
|
272
|
+
if (relationships && relationships.length > 0) {
|
273
|
+
relationships.forEach(function (rel) {
|
274
|
+
updatedDefinition += ` ${rel.from_table} }|--|| ${rel.to_table} : "${rel.from_column} → ${rel.to_column}"\n`;
|
275
|
+
});
|
276
|
+
} else {
|
277
|
+
updatedDefinition +=
|
278
|
+
" %% No relationships found in the database schema\n";
|
279
|
+
}
|
280
|
+
|
281
|
+
// Create the diagram element
|
282
|
+
const erdDiv = document.createElement("div");
|
283
|
+
erdDiv.className = "mermaid";
|
284
|
+
erdDiv.innerHTML = updatedDefinition;
|
285
|
+
|
286
|
+
// Create a temporary container for rendering
|
287
|
+
const tempContainer = document.createElement("div");
|
288
|
+
tempContainer.style.visibility = "hidden";
|
289
|
+
tempContainer.style.position = "absolute";
|
290
|
+
tempContainer.style.width = "100%";
|
291
|
+
tempContainer.appendChild(erdDiv);
|
292
|
+
document.body.appendChild(tempContainer);
|
293
|
+
|
294
|
+
// Render the diagram in the temporary container
|
295
|
+
mermaid
|
296
|
+
.init(undefined, erdDiv)
|
297
|
+
.then(function () {
|
298
|
+
console.log("Diagram fully rendered with all data");
|
299
|
+
|
300
|
+
try {
|
301
|
+
// Remove from temp container without destroying
|
302
|
+
tempContainer.removeChild(erdDiv);
|
303
|
+
|
304
|
+
// Hide loading indicator
|
305
|
+
document.getElementById("erd-loading").style.display = "none";
|
306
|
+
|
307
|
+
// Clear main container and add the diagram
|
308
|
+
container.innerHTML = "";
|
309
|
+
container.appendChild(erdDiv);
|
310
|
+
|
311
|
+
// Remove temp container
|
312
|
+
document.body.removeChild(tempContainer);
|
313
|
+
|
314
|
+
// Wait a bit for the DOM to stabilize before initializing pan-zoom
|
315
|
+
setTimeout(() => {
|
316
|
+
setupZoomControls();
|
317
|
+
// Mark diagram as ready for download
|
318
|
+
diagramReady = true;
|
319
|
+
isUpdatingDiagram = false;
|
320
|
+
}, 100);
|
321
|
+
} catch (err) {
|
322
|
+
console.error("Error moving diagram to container:", err);
|
323
|
+
isUpdatingDiagram = false;
|
324
|
+
}
|
325
|
+
})
|
326
|
+
.catch(function (error) {
|
327
|
+
console.error("Error rendering diagram:", error);
|
328
|
+
document.body.removeChild(tempContainer);
|
329
|
+
isUpdatingDiagram = false;
|
330
|
+
showError(
|
331
|
+
"Error rendering diagram",
|
332
|
+
"There was an error rendering the entity relationship diagram.",
|
333
|
+
error.message
|
334
|
+
);
|
335
|
+
});
|
336
|
+
}
|
337
|
+
|
338
|
+
// Get the container reference for later use
|
339
|
+
const container = document.getElementById("erd-container");
|
340
|
+
|
341
|
+
// SVG Pan Zoom instance
|
342
|
+
let panZoomInstance = null;
|
343
|
+
|
344
|
+
// Setup zoom controls using svg-pan-zoom library
|
345
|
+
function setupZoomControls() {
|
346
|
+
const diagramContainer = document.getElementById("erd-container");
|
347
|
+
const svgElement = diagramContainer.querySelector("svg");
|
348
|
+
|
349
|
+
if (!svgElement) {
|
350
|
+
console.warn("SVG element not found for zoom controls");
|
351
|
+
return;
|
352
|
+
}
|
353
|
+
|
354
|
+
// Make sure SVG has proper attributes for zooming
|
355
|
+
svgElement.setAttribute("width", "100%");
|
356
|
+
svgElement.setAttribute("height", "100%");
|
357
|
+
|
358
|
+
// Initialize svg-pan-zoom
|
359
|
+
panZoomInstance = svgPanZoom(svgElement, {
|
360
|
+
zoomEnabled: true,
|
361
|
+
controlIconsEnabled: false,
|
362
|
+
fit: true,
|
363
|
+
center: true,
|
364
|
+
minZoom: 0.1,
|
365
|
+
maxZoom: 20,
|
366
|
+
zoomScaleSensitivity: 0.3,
|
367
|
+
onZoom: function (newZoom) {
|
368
|
+
// Update zoom percentage display
|
369
|
+
const zoomDisplay = document.getElementById("zoomPercentage");
|
370
|
+
if (zoomDisplay) {
|
371
|
+
zoomDisplay.textContent = `${Math.round(newZoom * 100)}%`;
|
372
|
+
}
|
373
|
+
},
|
374
|
+
});
|
375
|
+
|
376
|
+
// Set initial zoom to 100%
|
377
|
+
panZoomInstance.zoom(1);
|
378
|
+
|
379
|
+
// Add event listeners for zoom controls
|
380
|
+
document.getElementById("zoomIn").addEventListener("click", function () {
|
381
|
+
panZoomInstance.zoomIn();
|
382
|
+
});
|
383
|
+
|
384
|
+
document.getElementById("zoomOut").addEventListener("click", function () {
|
385
|
+
panZoomInstance.zoomOut();
|
386
|
+
});
|
387
|
+
|
388
|
+
document.getElementById("resetView").addEventListener("click", function () {
|
389
|
+
panZoomInstance.reset();
|
390
|
+
});
|
391
|
+
|
392
|
+
// Update initial percentage display
|
393
|
+
const zoomDisplay = document.getElementById("zoomPercentage");
|
394
|
+
if (zoomDisplay) {
|
395
|
+
zoomDisplay.textContent = "100%";
|
396
|
+
}
|
397
|
+
|
398
|
+
// Mark diagram as ready for download
|
399
|
+
diagramReady = true;
|
400
|
+
}
|
401
|
+
|
402
|
+
// Function to download the ERD as SVG
|
403
|
+
function downloadAsSVG() {
|
404
|
+
if (!diagramReady) {
|
405
|
+
alert("Please wait for the diagram to finish loading.");
|
406
|
+
return;
|
407
|
+
}
|
408
|
+
|
409
|
+
// Show loading indicator
|
410
|
+
showDownloadingIndicator("SVG");
|
411
|
+
|
412
|
+
try {
|
413
|
+
// Get the SVG element
|
414
|
+
const svgElement = document.querySelector("#erd-container svg");
|
415
|
+
if (!svgElement) {
|
416
|
+
alert("SVG diagram not found.");
|
417
|
+
return;
|
418
|
+
}
|
419
|
+
|
420
|
+
// Create a clone of the SVG to modify for download
|
421
|
+
const clonedSvg = svgElement.cloneNode(true);
|
422
|
+
|
423
|
+
// Set explicit dimensions to ensure proper rendering
|
424
|
+
clonedSvg.setAttribute("width", svgElement.getBoundingClientRect().width);
|
425
|
+
clonedSvg.setAttribute(
|
426
|
+
"height",
|
427
|
+
svgElement.getBoundingClientRect().height
|
428
|
+
);
|
429
|
+
|
430
|
+
// Convert SVG to a string
|
431
|
+
const serializer = new XMLSerializer();
|
432
|
+
let svgString = serializer.serializeToString(clonedSvg);
|
433
|
+
|
434
|
+
// Add XML declaration and doctype
|
435
|
+
svgString = '<?xml version="1.0" standalone="no"?>\n' + svgString;
|
436
|
+
|
437
|
+
// Create a Blob with the SVG data
|
438
|
+
const blob = new Blob([svgString], {
|
439
|
+
type: "image/svg+xml;charset=utf-8",
|
440
|
+
});
|
441
|
+
|
442
|
+
// Create a timestamp for filename
|
443
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
444
|
+
|
445
|
+
// Create download link and trigger download
|
446
|
+
const downloadLink = document.createElement("a");
|
447
|
+
downloadLink.href = URL.createObjectURL(blob);
|
448
|
+
downloadLink.download = `database_erd_${timestamp}.svg`;
|
449
|
+
document.body.appendChild(downloadLink);
|
450
|
+
downloadLink.click();
|
451
|
+
document.body.removeChild(downloadLink);
|
452
|
+
} catch (error) {
|
453
|
+
console.error("Error downloading SVG:", error);
|
454
|
+
alert("Error downloading SVG. Please check console for details.");
|
455
|
+
}
|
456
|
+
}
|
457
|
+
|
458
|
+
// Function to download the ERD as PNG
|
459
|
+
function downloadAsPNG() {
|
460
|
+
if (!diagramReady) {
|
461
|
+
alert("Please wait for the diagram to finish loading.");
|
462
|
+
return;
|
463
|
+
}
|
464
|
+
|
465
|
+
// Show loading indicator
|
466
|
+
showDownloadingIndicator("PNG");
|
467
|
+
|
468
|
+
try {
|
469
|
+
// Get the SVG element
|
470
|
+
const svgElement = document.querySelector("#erd-container svg");
|
471
|
+
if (!svgElement) {
|
472
|
+
alert("SVG diagram not found.");
|
473
|
+
return;
|
474
|
+
}
|
475
|
+
|
476
|
+
// Create a clone of the SVG to modify for download
|
477
|
+
const clonedSvg = svgElement.cloneNode(true);
|
478
|
+
|
479
|
+
// Set explicit dimensions to ensure proper rendering
|
480
|
+
const width = svgElement.getBoundingClientRect().width;
|
481
|
+
const height = svgElement.getBoundingClientRect().height;
|
482
|
+
clonedSvg.setAttribute("width", width);
|
483
|
+
clonedSvg.setAttribute("height", height);
|
484
|
+
|
485
|
+
// Convert SVG to a string
|
486
|
+
const serializer = new XMLSerializer();
|
487
|
+
const svgString = serializer.serializeToString(clonedSvg);
|
488
|
+
|
489
|
+
// Create a Blob with the SVG data
|
490
|
+
const svgBlob = new Blob([svgString], {
|
491
|
+
type: "image/svg+xml;charset=utf-8",
|
492
|
+
});
|
493
|
+
const svgUrl = URL.createObjectURL(svgBlob);
|
494
|
+
|
495
|
+
// Create an Image object to draw to canvas
|
496
|
+
const img = new Image();
|
497
|
+
img.onload = function () {
|
498
|
+
// Create canvas with appropriate dimensions
|
499
|
+
const canvas = document.createElement("canvas");
|
500
|
+
canvas.width = width * 2; // Scale up for better quality
|
501
|
+
canvas.height = height * 2;
|
502
|
+
|
503
|
+
// Get drawing context and scale it
|
504
|
+
const ctx = canvas.getContext("2d");
|
505
|
+
ctx.scale(2, 2); // Scale up for better quality
|
506
|
+
|
507
|
+
// Draw white background (SVG may have transparency)
|
508
|
+
ctx.fillStyle = "white";
|
509
|
+
ctx.fillRect(0, 0, width, height);
|
510
|
+
|
511
|
+
// Draw the image onto the canvas
|
512
|
+
ctx.drawImage(img, 0, 0, width, height);
|
513
|
+
|
514
|
+
// Create timestamp for filename
|
515
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
516
|
+
|
517
|
+
// Convert canvas to PNG and trigger download
|
518
|
+
canvas.toBlob(function (blob) {
|
519
|
+
const downloadLink = document.createElement("a");
|
520
|
+
downloadLink.href = URL.createObjectURL(blob);
|
521
|
+
downloadLink.download = `database_erd_${timestamp}.png`;
|
522
|
+
document.body.appendChild(downloadLink);
|
523
|
+
downloadLink.click();
|
524
|
+
document.body.removeChild(downloadLink);
|
525
|
+
}, "image/png");
|
526
|
+
|
527
|
+
// Clean up
|
528
|
+
URL.revokeObjectURL(svgUrl);
|
529
|
+
};
|
530
|
+
|
531
|
+
// Set the image source to the SVG URL
|
532
|
+
img.src = svgUrl;
|
533
|
+
} catch (error) {
|
534
|
+
console.error("Error downloading PNG:", error);
|
535
|
+
alert("Error downloading PNG. Please check console for details.");
|
536
|
+
}
|
537
|
+
}
|
538
|
+
|
539
|
+
// Set up event listeners for download buttons
|
540
|
+
document
|
541
|
+
.getElementById("downloadSvg")
|
542
|
+
.addEventListener("click", function (e) {
|
543
|
+
e.preventDefault();
|
544
|
+
downloadAsSVG();
|
545
|
+
});
|
546
|
+
|
547
|
+
document
|
548
|
+
.getElementById("downloadPng")
|
549
|
+
.addEventListener("click", function (e) {
|
550
|
+
e.preventDefault();
|
551
|
+
downloadAsPNG();
|
552
|
+
});
|
553
|
+
});
|