mr4 commited on
Commit
8ff6981
Β·
verified Β·
1 Parent(s): 9bd422a

Upload 65 files

Browse files
Files changed (4) hide show
  1. css/styles.css +114 -0
  2. index.html +10 -1
  3. js/app.js +7 -0
  4. js/ui/printHandler.js +320 -0
css/styles.css CHANGED
@@ -31,6 +31,12 @@ body {
31
  header {
32
  box-shadow: var(--shadow);
33
  margin-bottom: 2rem;
 
 
 
 
 
 
34
  }
35
 
36
  header .navbar-brand {
@@ -188,3 +194,111 @@ header .navbar-brand {
188
  ::-webkit-scrollbar-thumb:hover {
189
  background: #555;
190
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  header {
32
  box-shadow: var(--shadow);
33
  margin-bottom: 2rem;
34
+ z-index: 1030;
35
+ }
36
+
37
+ /* Offset body content below the fixed header */
38
+ body {
39
+ padding-top: 56px;
40
  }
41
 
42
  header .navbar-brand {
 
194
  ::-webkit-scrollbar-thumb:hover {
195
  background: #555;
196
  }
197
+
198
+ /* Print Header - hidden in normal view */
199
+ .print-header {
200
+ display: none;
201
+ }
202
+
203
+ /* Print Styles */
204
+ @media print {
205
+ /* Hide navbar header */
206
+ header.navbar {
207
+ display: none !important;
208
+ }
209
+
210
+ /* Hide left sidebar */
211
+ .col-lg-3.col-md-4 {
212
+ display: none !important;
213
+ }
214
+
215
+ /* Main content takes full width */
216
+ .col-lg-9.col-md-8 {
217
+ flex: 0 0 100%;
218
+ max-width: 100%;
219
+ }
220
+
221
+ /* Hide hidden panels */
222
+ .hidden {
223
+ display: none !important;
224
+ }
225
+
226
+ /* Remove box-shadow and hover effects */
227
+ .card,
228
+ .card:hover,
229
+ .btn,
230
+ .btn:hover,
231
+ .list-group-item,
232
+ .list-group-item:hover {
233
+ box-shadow: none !important;
234
+ transform: none !important;
235
+ }
236
+
237
+ /* White background, black text */
238
+ body {
239
+ background-color: #fff;
240
+ color: #000;
241
+ }
242
+
243
+ /* Page-break for cards */
244
+ .card {
245
+ page-break-inside: avoid;
246
+ break-inside: avoid;
247
+ }
248
+
249
+ /* Show print header */
250
+ .print-header {
251
+ display: block;
252
+ font-size: 1.5rem;
253
+ font-weight: 700;
254
+ text-align: center;
255
+ margin-bottom: 1rem;
256
+ }
257
+
258
+ /* Hide interactive buttons not needed in print */
259
+ #graphExportContainer,
260
+ .fullscreen-btn,
261
+ #fullscreenBtn,
262
+ #sidebarToggleBtn,
263
+ #copyLinkBtn,
264
+ #uploadBtn,
265
+ #exportBtn,
266
+ #printBtn,
267
+ .graph-controls,
268
+ .graph-search-container,
269
+ .graph-layout-switcher,
270
+ .node-grouping-controls,
271
+ .graph-annotation-controls,
272
+ .minimap-container,
273
+ .guided-tour-btn,
274
+ #tourBtn {
275
+ display: none !important;
276
+ }
277
+
278
+ /* Graph container: allow auto height for print image */
279
+ #graphContainer {
280
+ height: auto !important;
281
+ overflow: visible !important;
282
+ border: none !important;
283
+ }
284
+
285
+ /* Static graph image */
286
+ #graph-print-image {
287
+ max-width: 100%;
288
+ height: auto;
289
+ display: block !important;
290
+ page-break-inside: avoid;
291
+ break-inside: avoid;
292
+ }
293
+
294
+ /* Graph card: allow page break and full width for graph column */
295
+ .col-md-8:has(#graphContainer) {
296
+ flex: 0 0 100%;
297
+ max-width: 100%;
298
+ }
299
+
300
+ /* Hide node detail panel in print (it's beside the graph) */
301
+ .col-md-4:has(#nodeDetailContainer) {
302
+ display: none !important;
303
+ }
304
+ }
index.html CHANGED
@@ -22,7 +22,7 @@
22
  <body>
23
  <div id="app" class="container-fluid">
24
  <!-- Header -->
25
- <header class="navbar navbar-expand-lg navbar-dark bg-dark mb-4">
26
  <div class="container-fluid">
27
  <a class="navbar-brand" href="#">
28
  <i class="fas fa-cube"></i> ONNX Model Explorer
@@ -37,10 +37,16 @@
37
  <button id="exportBtn" class="btn btn-success btn-sm" disabled>
38
  <i class="fas fa-download"></i> Export
39
  </button>
 
 
 
40
  </div>
41
  </div>
42
  </header>
43
 
 
 
 
44
  <!-- Main Content -->
45
  <div class="row">
46
  <!-- Left Sidebar -->
@@ -231,6 +237,9 @@
231
  <script src="js/ui/tfliteViewer.js"></script>
232
  <script src="js/ui/conversionGuideManager.js"></script>
233
 
 
 
 
234
  <!-- 8. Application integration hub (depends on everything above) -->
235
  <script src="js/app.js"></script>
236
  </body>
 
22
  <body>
23
  <div id="app" class="container-fluid">
24
  <!-- Header -->
25
+ <header class="navbar navbar-expand-lg navbar-dark bg-dark mb-4 fixed-top">
26
  <div class="container-fluid">
27
  <a class="navbar-brand" href="#">
28
  <i class="fas fa-cube"></i> ONNX Model Explorer
 
37
  <button id="exportBtn" class="btn btn-success btn-sm" disabled>
38
  <i class="fas fa-download"></i> Export
39
  </button>
40
+ <button id="printBtn" class="btn btn-outline-light btn-sm ms-2" disabled>
41
+ <i class="fas fa-print"></i> Print PDF
42
+ </button>
43
  </div>
44
  </div>
45
  </header>
46
 
47
+ <!-- Print Header - only visible in @media print -->
48
+ <div id="print-header" class="print-header" aria-hidden="true"></div>
49
+
50
  <!-- Main Content -->
51
  <div class="row">
52
  <!-- Left Sidebar -->
 
237
  <script src="js/ui/tfliteViewer.js"></script>
238
  <script src="js/ui/conversionGuideManager.js"></script>
239
 
240
+ <!-- 7e. Print Handler -->
241
+ <script src="js/ui/printHandler.js"></script>
242
+
243
  <!-- 8. Application integration hub (depends on everything above) -->
244
  <script src="js/app.js"></script>
245
  </body>
js/app.js CHANGED
@@ -44,6 +44,7 @@
44
  let safeTensorsViewer = null;
45
  let tfliteParser = null;
46
  let tfliteViewer = null;
 
47
 
48
  // ─── Performance: In-Memory Parsed Model Cache ────────────────────────────
49
  // Keyed by model path; avoids re-parsing the same model on repeated selection.
@@ -301,6 +302,11 @@
301
  tfliteViewer = new window.TFLiteViewer('modelDetailsContainer');
302
  }
303
 
 
 
 
 
 
304
  // Subscribe ModelListDisplay to filteredModelList changes
305
  if (modelListDisplay && window.StateManager) {
306
  window.StateManager.subscribe('filteredModelList', function (filteredList) {
@@ -927,6 +933,7 @@
927
  getSafeTensorsViewer: () => safeTensorsViewer,
928
  getTFLiteParser: () => tfliteParser,
929
  getTFLiteViewer: () => tfliteViewer,
 
930
  getParsedModelCache: () => _parsedModelCache,
931
  clearParsedModelCache:() => _parsedModelCache.clear(),
932
  _pendingZoom: null,
 
44
  let safeTensorsViewer = null;
45
  let tfliteParser = null;
46
  let tfliteViewer = null;
47
+ let printHandler = null;
48
 
49
  // ─── Performance: In-Memory Parsed Model Cache ────────────────────────────
50
  // Keyed by model path; avoids re-parsing the same model on repeated selection.
 
302
  tfliteViewer = new window.TFLiteViewer('modelDetailsContainer');
303
  }
304
 
305
+ // PrintHandler
306
+ if (window.PrintHandler) {
307
+ printHandler = new window.PrintHandler('printBtn', 'print-header');
308
+ }
309
+
310
  // Subscribe ModelListDisplay to filteredModelList changes
311
  if (modelListDisplay && window.StateManager) {
312
  window.StateManager.subscribe('filteredModelList', function (filteredList) {
 
933
  getSafeTensorsViewer: () => safeTensorsViewer,
934
  getTFLiteParser: () => tfliteParser,
935
  getTFLiteViewer: () => tfliteViewer,
936
+ getPrintHandler: () => printHandler,
937
  getParsedModelCache: () => _parsedModelCache,
938
  clearParsedModelCache:() => _parsedModelCache.clear(),
939
  _pendingZoom: null,
js/ui/printHandler.js ADDED
@@ -0,0 +1,320 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * PrintHandler - Manages Print to PDF functionality
3
+ * Converts Cytoscape canvas to static image for printing,
4
+ * prepares layout for @media print, and restores after printing.
5
+ * Requirements: 61.1, 61.5, 62.1-62.7, 63.1-63.5, 64.1, 64.2, 65.1
6
+ */
7
+
8
+ window.PrintHandler = (function () {
9
+ 'use strict';
10
+
11
+ /**
12
+ * @param {string} printBtnId - ID of the Print PDF button
13
+ * @param {string} printHeaderId - ID of the print header element
14
+ */
15
+ function PrintHandler(printBtnId, printHeaderId) {
16
+ /** @type {HTMLButtonElement|null} */
17
+ this._printBtn = document.getElementById(printBtnId) || null;
18
+
19
+ /** @type {HTMLElement|null} */
20
+ this._printHeader = document.getElementById(printHeaderId) || null;
21
+
22
+ /** @type {Function|null} Unsubscribe from StateManager */
23
+ this._unsubscribeModel = null;
24
+
25
+ /** @type {Function|null} Bound beforeprint handler */
26
+ this._boundBeforePrint = null;
27
+
28
+ /** @type {Function|null} Bound afterprint handler */
29
+ this._boundAfterPrint = null;
30
+
31
+ /** @type {Function|null} Bound click handler */
32
+ this._boundClickHandler = null;
33
+
34
+ this._init();
35
+ }
36
+
37
+ // ─── Initialization ───────────────────────────────────────────────────────
38
+
39
+ /**
40
+ * Bind click handler, subscribe to state changes, register print events.
41
+ * @private
42
+ */
43
+ PrintHandler.prototype._init = function () {
44
+ try {
45
+ // Bind click handler for Print PDF button
46
+ if (this._printBtn) {
47
+ this._boundClickHandler = this.print.bind(this);
48
+ this._printBtn.addEventListener('click', this._boundClickHandler);
49
+ }
50
+
51
+ // Subscribe to StateManager.currentModel to enable/disable button
52
+ if (window.StateManager && typeof window.StateManager.subscribe === 'function') {
53
+ this._unsubscribeModel = window.StateManager.subscribe('currentModel', function (model) {
54
+ try {
55
+ this._setButtonEnabled(!!model);
56
+ } catch (e) {
57
+ console.error('[PrintHandler] Error in currentModel subscriber:', e);
58
+ }
59
+ }.bind(this));
60
+ }
61
+
62
+ // Register beforeprint / afterprint window events
63
+ this._boundBeforePrint = this._prepareForPrint.bind(this);
64
+ this._boundAfterPrint = this._restoreAfterPrint.bind(this);
65
+ window.addEventListener('beforeprint', this._boundBeforePrint);
66
+ window.addEventListener('afterprint', this._boundAfterPrint);
67
+ } catch (err) {
68
+ console.error('[PrintHandler] Initialization error:', err);
69
+ }
70
+ };
71
+
72
+ // ─── Button State ─────────────────────────────────────────────────────────
73
+
74
+ /**
75
+ * Enable or disable the Print PDF button.
76
+ * @param {boolean} enabled
77
+ * @private
78
+ */
79
+ PrintHandler.prototype._setButtonEnabled = function (enabled) {
80
+ try {
81
+ if (this._printBtn) {
82
+ this._printBtn.disabled = !enabled;
83
+ }
84
+ } catch (err) {
85
+ console.error('[PrintHandler] _setButtonEnabled error:', err);
86
+ }
87
+ };
88
+
89
+ // ─── Print Preparation ────────────────────────────────────────────────────
90
+
91
+ /**
92
+ * Prepare the page for printing:
93
+ * - Update print header with current model file name
94
+ * - Convert Cytoscape canvas β†’ static PNG image
95
+ * - Hide original canvas, insert static image into graphContainer
96
+ * - If no graph, show placeholder
97
+ * @private
98
+ */
99
+ PrintHandler.prototype._prepareForPrint = function () {
100
+ try {
101
+ // Update print header with model file name
102
+ var modelName = this._getCurrentModelFileName();
103
+ if (this._printHeader) {
104
+ this._printHeader.textContent = modelName
105
+ ? 'ONNX Model Explorer β€” ' + modelName
106
+ : 'ONNX Model Explorer';
107
+ }
108
+
109
+ // Get Cytoscape instance
110
+ var cy = this._getCytoscapeInstance();
111
+ var graphContainer = document.getElementById('graphContainer');
112
+
113
+ if (cy && graphContainer) {
114
+ try {
115
+ // Convert canvas to PNG data URL
116
+ var pngDataUrl = cy.png({ full: true, scale: 2, bg: '#ffffff' });
117
+
118
+ // Hide ALL direct children of graphContainer (Cytoscape wrapper, canvases, etc.)
119
+ var children = graphContainer.children;
120
+ for (var i = 0; i < children.length; i++) {
121
+ children[i].setAttribute('data-print-hidden', 'true');
122
+ children[i].style.display = 'none';
123
+ }
124
+
125
+ // Override the fixed height so the image can size naturally
126
+ this._origHeight = graphContainer.style.height;
127
+ graphContainer.style.height = 'auto';
128
+ graphContainer.style.overflow = 'visible';
129
+
130
+ // Create and insert static image
131
+ var img = document.createElement('img');
132
+ img.id = 'graph-print-image';
133
+ img.src = pngDataUrl;
134
+ img.alt = 'Model Graph';
135
+ img.style.maxWidth = '100%';
136
+ img.style.height = 'auto';
137
+ img.style.display = 'block';
138
+ graphContainer.appendChild(img);
139
+ } catch (canvasErr) {
140
+ console.error('[PrintHandler] Canvas to PNG conversion error:', canvasErr);
141
+ this._insertPlaceholder(graphContainer);
142
+ }
143
+ } else if (graphContainer) {
144
+ // No Cytoscape instance β€” show placeholder
145
+ this._insertPlaceholder(graphContainer);
146
+ }
147
+ } catch (err) {
148
+ console.error('[PrintHandler] _prepareForPrint error:', err);
149
+ }
150
+ };
151
+
152
+ /**
153
+ * Insert a "no graph" placeholder into the graph container.
154
+ * @param {HTMLElement} container
155
+ * @private
156
+ */
157
+ PrintHandler.prototype._insertPlaceholder = function (container) {
158
+ try {
159
+ var placeholder = document.createElement('p');
160
+ placeholder.id = 'graph-print-placeholder';
161
+ placeholder.textContent = 'KhΓ΄ng cΓ³ Δ‘α»“ thα»‹ để hiển thα»‹';
162
+ placeholder.style.textAlign = 'center';
163
+ placeholder.style.color = '#6c757d';
164
+ placeholder.style.padding = '2rem 0';
165
+ container.appendChild(placeholder);
166
+ } catch (err) {
167
+ console.error('[PrintHandler] _insertPlaceholder error:', err);
168
+ }
169
+ };
170
+
171
+ // ─── Print Restoration ────────────────────────────────────────────────────
172
+
173
+ /**
174
+ * Restore the page after printing:
175
+ * - Remove static print image
176
+ * - Remove placeholder
177
+ * - Restore Cytoscape canvas visibility
178
+ * @private
179
+ */
180
+ PrintHandler.prototype._restoreAfterPrint = function () {
181
+ try {
182
+ // Remove static print image
183
+ var printImage = document.getElementById('graph-print-image');
184
+ if (printImage && printImage.parentNode) {
185
+ printImage.parentNode.removeChild(printImage);
186
+ }
187
+
188
+ // Remove placeholder
189
+ var placeholder = document.getElementById('graph-print-placeholder');
190
+ if (placeholder && placeholder.parentNode) {
191
+ placeholder.parentNode.removeChild(placeholder);
192
+ }
193
+
194
+ // Restore all hidden children of graphContainer
195
+ var graphContainer = document.getElementById('graphContainer');
196
+ if (graphContainer) {
197
+ var children = graphContainer.querySelectorAll('[data-print-hidden]');
198
+ for (var i = 0; i < children.length; i++) {
199
+ children[i].style.display = '';
200
+ children[i].removeAttribute('data-print-hidden');
201
+ }
202
+
203
+ // Restore original fixed height
204
+ if (this._origHeight !== undefined) {
205
+ graphContainer.style.height = this._origHeight;
206
+ graphContainer.style.overflow = '';
207
+ this._origHeight = undefined;
208
+ }
209
+ }
210
+ } catch (err) {
211
+ console.error('[PrintHandler] _restoreAfterPrint error:', err);
212
+ }
213
+ };
214
+
215
+ // ─── Helpers ──────────────────────────────────────────────────────────────
216
+
217
+ /**
218
+ * Get the current model file name from StateManager, SafeTensorsViewer, or TFLiteViewer.
219
+ * @returns {string|null}
220
+ * @private
221
+ */
222
+ PrintHandler.prototype._getCurrentModelFileName = function () {
223
+ try {
224
+ // 1. Try StateManager (ONNX models)
225
+ if (window.StateManager && typeof window.StateManager.getCurrentModel === 'function') {
226
+ var model = window.StateManager.getCurrentModel();
227
+ if (model && model.metadata && model.metadata.fileName) {
228
+ return model.metadata.fileName;
229
+ }
230
+ }
231
+
232
+ // 2. Try SafeTensorsViewer
233
+ if (window._onnxApp && typeof window._onnxApp.getSafeTensorsViewer === 'function') {
234
+ var stViewer = window._onnxApp.getSafeTensorsViewer();
235
+ if (stViewer && stViewer._fileName) {
236
+ return stViewer._fileName;
237
+ }
238
+ }
239
+
240
+ // 3. Try TFLiteViewer
241
+ if (window._onnxApp && typeof window._onnxApp.getTFLiteViewer === 'function') {
242
+ var tflViewer = window._onnxApp.getTFLiteViewer();
243
+ if (tflViewer && tflViewer._fileName) {
244
+ return tflViewer._fileName;
245
+ }
246
+ }
247
+
248
+ return null;
249
+ } catch (err) {
250
+ console.error('[PrintHandler] _getCurrentModelFileName error:', err);
251
+ return null;
252
+ }
253
+ };
254
+
255
+ /**
256
+ * Get the Cytoscape instance from GraphVisualizer.
257
+ * @returns {cytoscape.Core|null}
258
+ * @private
259
+ */
260
+ PrintHandler.prototype._getCytoscapeInstance = function () {
261
+ try {
262
+ if (window._onnxApp && typeof window._onnxApp.getGraphVisualizer === 'function') {
263
+ var gv = window._onnxApp.getGraphVisualizer();
264
+ if (gv && gv._cy) {
265
+ return gv._cy;
266
+ }
267
+ }
268
+ return null;
269
+ } catch (err) {
270
+ console.error('[PrintHandler] _getCytoscapeInstance error:', err);
271
+ return null;
272
+ }
273
+ };
274
+
275
+ // ─── Public API ───────────────────────────────────────────────────────────
276
+
277
+ /**
278
+ * Trigger the browser print dialog.
279
+ */
280
+ PrintHandler.prototype.print = function () {
281
+ try {
282
+ window.print();
283
+ } catch (err) {
284
+ console.error('[PrintHandler] print() error:', err);
285
+ }
286
+ };
287
+
288
+ /**
289
+ * Destroy the PrintHandler: unsubscribe, remove event listeners.
290
+ */
291
+ PrintHandler.prototype.destroy = function () {
292
+ try {
293
+ // Unsubscribe from StateManager
294
+ if (this._unsubscribeModel) {
295
+ this._unsubscribeModel();
296
+ this._unsubscribeModel = null;
297
+ }
298
+
299
+ // Remove window event listeners
300
+ if (this._boundBeforePrint) {
301
+ window.removeEventListener('beforeprint', this._boundBeforePrint);
302
+ this._boundBeforePrint = null;
303
+ }
304
+ if (this._boundAfterPrint) {
305
+ window.removeEventListener('afterprint', this._boundAfterPrint);
306
+ this._boundAfterPrint = null;
307
+ }
308
+
309
+ // Remove click handler
310
+ if (this._printBtn && this._boundClickHandler) {
311
+ this._printBtn.removeEventListener('click', this._boundClickHandler);
312
+ this._boundClickHandler = null;
313
+ }
314
+ } catch (err) {
315
+ console.error('[PrintHandler] destroy() error:', err);
316
+ }
317
+ };
318
+
319
+ return PrintHandler;
320
+ })();