/** * Helpers - General utility functions * Provides common helper functions used across the application. * Requirements: 1.1, 2.1, 3.1, 4.1, 5.1 */ const Helpers = (function () { // ─── Debounce ───────────────────────────────────────────────────────────── /** * Returns a debounced version of fn that delays invocation by `wait` ms. * @param {Function} fn * @param {number} wait - milliseconds * @returns {Function} */ function debounce(fn, wait) { let timer = null; return function (...args) { clearTimeout(timer); timer = setTimeout(() => fn.apply(this, args), wait); }; } // ─── String Utilities ───────────────────────────────────────────────────── /** * Escape HTML special characters to prevent XSS. * @param {string} str * @returns {string} */ function escapeHtml(str) { if (str == null) return ''; return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } /** * Truncate a string to maxLength, appending ellipsis if needed. * @param {string} str * @param {number} maxLength * @returns {string} */ function truncate(str, maxLength) { if (!str) return ''; if (str.length <= maxLength) return str; return str.slice(0, maxLength - 3) + '...'; } // ─── Object Utilities ───────────────────────────────────────────────────── /** * Deep clone a plain object / array using JSON serialisation. * @param {*} obj * @returns {*} */ function deepClone(obj) { if (obj === null || typeof obj !== 'object') return obj; try { return JSON.parse(JSON.stringify(obj)); } catch (_) { return obj; } } /** * Check whether a value is a plain object (not null, not array). * @param {*} value * @returns {boolean} */ function isPlainObject(value) { return value !== null && typeof value === 'object' && !Array.isArray(value); } // ─── ID Generation ──────────────────────────────────────────────────────── /** * Generate a short unique ID string. * @returns {string} */ function generateId() { return Math.random().toString(36).slice(2, 10) + Date.now().toString(36); } // ─── File Utilities ─────────────────────────────────────────────────────── /** * Extract the file extension (including dot) from a file name or path. * @param {string} filename * @returns {string} e.g. ".onnx" */ function getFileExtension(filename) { if (!filename) return ''; const idx = filename.lastIndexOf('.'); return idx >= 0 ? filename.slice(idx).toLowerCase() : ''; } /** * Extract the base file name (without directory path) from a full path. * @param {string} filePath * @returns {string} */ function getBaseName(filePath) { if (!filePath) return ''; return filePath.replace(/\\/g, '/').split('/').pop() || ''; } /** * Check whether a file name has an allowed extension. * @param {string} filename * @param {string[]} allowedExtensions - e.g. ['.onnx'] * @returns {boolean} */ function hasAllowedExtension(filename, allowedExtensions) { const ext = getFileExtension(filename); return allowedExtensions.map(e => e.toLowerCase()).includes(ext); } // ─── DOM Utilities ──────────────────────────────────────────────────────── /** * Safely query a DOM element; returns null if not found. * @param {string} selector * @param {Element} [root=document] * @returns {Element|null} */ function $(selector, root) { return (root || document).querySelector(selector); } /** * Create a DOM element with optional attributes and text content. * @param {string} tag * @param {Object} [attrs] * @param {string} [text] * @returns {Element} */ function createElement(tag, attrs, text) { const el = document.createElement(tag); if (attrs) { Object.entries(attrs).forEach(([k, v]) => { if (k === 'className') { el.className = v; } else if (k === 'dataset') { Object.entries(v).forEach(([dk, dv]) => { el.dataset[dk] = dv; }); } else { el.setAttribute(k, v); } }); } if (text != null) el.textContent = text; return el; } // ─── Async Utilities ────────────────────────────────────────────────────── /** * Wait for a given number of milliseconds. * @param {number} ms * @returns {Promise} */ function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Wrap a promise with a timeout; rejects with an error if it exceeds `ms`. * @param {Promise} promise * @param {number} ms * @returns {Promise} */ function withTimeout(promise, ms) { const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error(`Operation timed out after ${ms}ms`)), ms) ); return Promise.race([promise, timeout]); } // ─── Array Utilities ────────────────────────────────────────────────────── /** * Return unique items from an array (shallow equality). * @param {Array} arr * @returns {Array} */ function unique(arr) { return [...new Set(arr)]; } /** * Chunk an array into sub-arrays of size `n`. * @param {Array} arr * @param {number} n * @returns {Array[]} */ function chunk(arr, n) { const result = []; for (let i = 0; i < arr.length; i += n) { result.push(arr.slice(i, i + n)); } return result; } // ─── Public API ─────────────────────────────────────────────────────────── return { debounce, escapeHtml, truncate, deepClone, isPlainObject, generateId, getFileExtension, getBaseName, hasAllowedExtension, $, createElement, sleep, withTimeout, unique, chunk, }; })(); // Export for global access in vanilla JS context window.Helpers = Helpers;