model-explorer / js /ui /printHandler.js
mr4's picture
Upload 65 files
8ff6981 verified
/**
* 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;
})();