Spaces:
Running
Running
| /** | |
| * PrintHandler - Manages Print to PDF functionality | |
| * Converts Cytoscape canvas to static image for printing, | |
| * prepares layout for @media print, and restores after printing. | |
| * Requirements: 61.1, 61.5, 62.1-62.7, 63.1-63.5, 64.1, 64.2, 65.1 | |
| */ | |
| window.PrintHandler = (function () { | |
| 'use strict'; | |
| /** | |
| * @param {string} printBtnId - ID of the Print PDF button | |
| * @param {string} printHeaderId - ID of the print header element | |
| */ | |
| function PrintHandler(printBtnId, printHeaderId) { | |
| /** @type {HTMLButtonElement|null} */ | |
| this._printBtn = document.getElementById(printBtnId) || null; | |
| /** @type {HTMLElement|null} */ | |
| this._printHeader = document.getElementById(printHeaderId) || null; | |
| /** @type {Function|null} Unsubscribe from StateManager */ | |
| this._unsubscribeModel = null; | |
| /** @type {Function|null} Bound beforeprint handler */ | |
| this._boundBeforePrint = null; | |
| /** @type {Function|null} Bound afterprint handler */ | |
| this._boundAfterPrint = null; | |
| /** @type {Function|null} Bound click handler */ | |
| this._boundClickHandler = null; | |
| this._init(); | |
| } | |
| // βββ Initialization βββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| /** | |
| * Bind click handler, subscribe to state changes, register print events. | |
| * @private | |
| */ | |
| PrintHandler.prototype._init = function () { | |
| try { | |
| // Bind click handler for Print PDF button | |
| if (this._printBtn) { | |
| this._boundClickHandler = this.print.bind(this); | |
| this._printBtn.addEventListener('click', this._boundClickHandler); | |
| } | |
| // Subscribe to StateManager.currentModel to enable/disable button | |
| if (window.StateManager && typeof window.StateManager.subscribe === 'function') { | |
| this._unsubscribeModel = window.StateManager.subscribe('currentModel', function (model) { | |
| try { | |
| this._setButtonEnabled(!!model); | |
| } catch (e) { | |
| console.error('[PrintHandler] Error in currentModel subscriber:', e); | |
| } | |
| }.bind(this)); | |
| } | |
| // Register beforeprint / afterprint window events | |
| this._boundBeforePrint = this._prepareForPrint.bind(this); | |
| this._boundAfterPrint = this._restoreAfterPrint.bind(this); | |
| window.addEventListener('beforeprint', this._boundBeforePrint); | |
| window.addEventListener('afterprint', this._boundAfterPrint); | |
| } catch (err) { | |
| console.error('[PrintHandler] Initialization error:', err); | |
| } | |
| }; | |
| // βββ Button State βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| /** | |
| * Enable or disable the Print PDF button. | |
| * @param {boolean} enabled | |
| * @private | |
| */ | |
| PrintHandler.prototype._setButtonEnabled = function (enabled) { | |
| try { | |
| if (this._printBtn) { | |
| this._printBtn.disabled = !enabled; | |
| } | |
| } catch (err) { | |
| console.error('[PrintHandler] _setButtonEnabled error:', err); | |
| } | |
| }; | |
| // βββ Print Preparation ββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| /** | |
| * Prepare the page for printing: | |
| * - Update print header with current model file name | |
| * - Convert Cytoscape canvas β static PNG image | |
| * - Hide original canvas, insert static image into graphContainer | |
| * - If no graph, show placeholder | |
| * @private | |
| */ | |
| PrintHandler.prototype._prepareForPrint = function () { | |
| try { | |
| // Update print header with model file name | |
| var modelName = this._getCurrentModelFileName(); | |
| if (this._printHeader) { | |
| this._printHeader.textContent = modelName | |
| ? 'ONNX Model Explorer β ' + modelName | |
| : 'ONNX Model Explorer'; | |
| } | |
| // Get Cytoscape instance | |
| var cy = this._getCytoscapeInstance(); | |
| var graphContainer = document.getElementById('graphContainer'); | |
| if (cy && graphContainer) { | |
| try { | |
| // Convert canvas to PNG data URL | |
| var pngDataUrl = cy.png({ full: true, scale: 2, bg: '#ffffff' }); | |
| // Hide ALL direct children of graphContainer (Cytoscape wrapper, canvases, etc.) | |
| var children = graphContainer.children; | |
| for (var i = 0; i < children.length; i++) { | |
| children[i].setAttribute('data-print-hidden', 'true'); | |
| children[i].style.display = 'none'; | |
| } | |
| // Override the fixed height so the image can size naturally | |
| this._origHeight = graphContainer.style.height; | |
| graphContainer.style.height = 'auto'; | |
| graphContainer.style.overflow = 'visible'; | |
| // Create and insert static image | |
| var img = document.createElement('img'); | |
| img.id = 'graph-print-image'; | |
| img.src = pngDataUrl; | |
| img.alt = 'Model Graph'; | |
| img.style.maxWidth = '100%'; | |
| img.style.height = 'auto'; | |
| img.style.display = 'block'; | |
| graphContainer.appendChild(img); | |
| } catch (canvasErr) { | |
| console.error('[PrintHandler] Canvas to PNG conversion error:', canvasErr); | |
| this._insertPlaceholder(graphContainer); | |
| } | |
| } else if (graphContainer) { | |
| // No Cytoscape instance β show placeholder | |
| this._insertPlaceholder(graphContainer); | |
| } | |
| } catch (err) { | |
| console.error('[PrintHandler] _prepareForPrint error:', err); | |
| } | |
| }; | |
| /** | |
| * Insert a "no graph" placeholder into the graph container. | |
| * @param {HTMLElement} container | |
| * @private | |
| */ | |
| PrintHandler.prototype._insertPlaceholder = function (container) { | |
| try { | |
| var placeholder = document.createElement('p'); | |
| placeholder.id = 'graph-print-placeholder'; | |
| placeholder.textContent = 'KhΓ΄ng cΓ³ Δα» thα» Δα» hiα»n thα»'; | |
| placeholder.style.textAlign = 'center'; | |
| placeholder.style.color = '#6c757d'; | |
| placeholder.style.padding = '2rem 0'; | |
| container.appendChild(placeholder); | |
| } catch (err) { | |
| console.error('[PrintHandler] _insertPlaceholder error:', err); | |
| } | |
| }; | |
| // βββ Print Restoration ββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| /** | |
| * Restore the page after printing: | |
| * - Remove static print image | |
| * - Remove placeholder | |
| * - Restore Cytoscape canvas visibility | |
| * @private | |
| */ | |
| PrintHandler.prototype._restoreAfterPrint = function () { | |
| try { | |
| // Remove static print image | |
| var printImage = document.getElementById('graph-print-image'); | |
| if (printImage && printImage.parentNode) { | |
| printImage.parentNode.removeChild(printImage); | |
| } | |
| // Remove placeholder | |
| var placeholder = document.getElementById('graph-print-placeholder'); | |
| if (placeholder && placeholder.parentNode) { | |
| placeholder.parentNode.removeChild(placeholder); | |
| } | |
| // Restore all hidden children of graphContainer | |
| var graphContainer = document.getElementById('graphContainer'); | |
| if (graphContainer) { | |
| var children = graphContainer.querySelectorAll('[data-print-hidden]'); | |
| for (var i = 0; i < children.length; i++) { | |
| children[i].style.display = ''; | |
| children[i].removeAttribute('data-print-hidden'); | |
| } | |
| // Restore original fixed height | |
| if (this._origHeight !== undefined) { | |
| graphContainer.style.height = this._origHeight; | |
| graphContainer.style.overflow = ''; | |
| this._origHeight = undefined; | |
| } | |
| } | |
| } catch (err) { | |
| console.error('[PrintHandler] _restoreAfterPrint error:', err); | |
| } | |
| }; | |
| // βββ Helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| /** | |
| * Get the current model file name from StateManager, SafeTensorsViewer, or TFLiteViewer. | |
| * @returns {string|null} | |
| * @private | |
| */ | |
| PrintHandler.prototype._getCurrentModelFileName = function () { | |
| try { | |
| // 1. Try StateManager (ONNX models) | |
| if (window.StateManager && typeof window.StateManager.getCurrentModel === 'function') { | |
| var model = window.StateManager.getCurrentModel(); | |
| if (model && model.metadata && model.metadata.fileName) { | |
| return model.metadata.fileName; | |
| } | |
| } | |
| // 2. Try SafeTensorsViewer | |
| if (window._onnxApp && typeof window._onnxApp.getSafeTensorsViewer === 'function') { | |
| var stViewer = window._onnxApp.getSafeTensorsViewer(); | |
| if (stViewer && stViewer._fileName) { | |
| return stViewer._fileName; | |
| } | |
| } | |
| // 3. Try TFLiteViewer | |
| if (window._onnxApp && typeof window._onnxApp.getTFLiteViewer === 'function') { | |
| var tflViewer = window._onnxApp.getTFLiteViewer(); | |
| if (tflViewer && tflViewer._fileName) { | |
| return tflViewer._fileName; | |
| } | |
| } | |
| return null; | |
| } catch (err) { | |
| console.error('[PrintHandler] _getCurrentModelFileName error:', err); | |
| return null; | |
| } | |
| }; | |
| /** | |
| * Get the Cytoscape instance from GraphVisualizer. | |
| * @returns {cytoscape.Core|null} | |
| * @private | |
| */ | |
| PrintHandler.prototype._getCytoscapeInstance = function () { | |
| try { | |
| if (window._onnxApp && typeof window._onnxApp.getGraphVisualizer === 'function') { | |
| var gv = window._onnxApp.getGraphVisualizer(); | |
| if (gv && gv._cy) { | |
| return gv._cy; | |
| } | |
| } | |
| return null; | |
| } catch (err) { | |
| console.error('[PrintHandler] _getCytoscapeInstance error:', err); | |
| return null; | |
| } | |
| }; | |
| // βββ Public API βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| /** | |
| * Trigger the browser print dialog. | |
| */ | |
| PrintHandler.prototype.print = function () { | |
| try { | |
| window.print(); | |
| } catch (err) { | |
| console.error('[PrintHandler] print() error:', err); | |
| } | |
| }; | |
| /** | |
| * Destroy the PrintHandler: unsubscribe, remove event listeners. | |
| */ | |
| PrintHandler.prototype.destroy = function () { | |
| try { | |
| // Unsubscribe from StateManager | |
| if (this._unsubscribeModel) { | |
| this._unsubscribeModel(); | |
| this._unsubscribeModel = null; | |
| } | |
| // Remove window event listeners | |
| if (this._boundBeforePrint) { | |
| window.removeEventListener('beforeprint', this._boundBeforePrint); | |
| this._boundBeforePrint = null; | |
| } | |
| if (this._boundAfterPrint) { | |
| window.removeEventListener('afterprint', this._boundAfterPrint); | |
| this._boundAfterPrint = null; | |
| } | |
| // Remove click handler | |
| if (this._printBtn && this._boundClickHandler) { | |
| this._printBtn.removeEventListener('click', this._boundClickHandler); | |
| this._boundClickHandler = null; | |
| } | |
| } catch (err) { | |
| console.error('[PrintHandler] destroy() error:', err); | |
| } | |
| }; | |
| return PrintHandler; | |
| })(); | |