let footer; // Footer containing all icons and statuses let windowIndex = 0; // Index of generated window const statuses = []; // Window open statuses (true / false) const selectors = []; // Window elements for sliding // Displayed windows slideToggle control ensuring only one window at a time is shown. // Faster sliding transition when closing previous window when new window is being opened. function windowControl(index) { if(statuses[index] === true) { // Window currently open, close it statuses[index] = !statuses[index]; // Open -> closed selectors[index].slideToggle(400); } else { // Window currently closed // Close any windows currently open (should be one max) for(const ind in statuses) { if(statuses[ind] === true) { statuses[ind] = !statuses[ind];; // Open -> closed selectors[ind].slideToggle(200); } } statuses[index] = !statuses[index]; // Closed -> open selectors[index].slideToggle(400); } }; // Generate red-orange header with text, pretty simple. // @param {string} text // @returns {DOMElement} function generateHeader(text) { // Generate header wrapper element const header = document.createElement("div"); header.classList.add("akito-header"); // Generate div containing actual text const headerText = document.createElement("p"); headerText.classList.add("akito-headerTitle"); header.appendChild(headerText); headerText.innerText = text; return header; } // Generates and adds footer icon and window (todo: add compatibility mode for Best Buy...) // Window is added to slide controller, and global icon and window offset are incremented per initialization. // @param {string} iconURL // @param {string} title // @param {number} width // @param {number} height // @param {boolean} compatibility function generateWindow(iconURL, title, width, height, compatibility = false) { // Check whether footer has even been initialized if(footer === undefined) { throw 'Footer has not been initialized yet!'; } // Increment all flexbox orders in advance const placeIndex = footer.children.length - 2; for(const element of footer.children) { if(element.tagName !== "a") { // script info or donation element.style.order++; } } // Initialize a element for window toggle and add to footer const iconClick = document.createElement("a"); const index = windowIndex++; // Copy constant for onclick iconClick.classList.add("akito-iconClick"); iconClick.classList.add(`akito-icon${placeIndex}`); footer.insertBefore(iconClick, footer.children[footer.children.length - 2]); // doesn't matter because order iconClick.href = "#"; iconClick.onclick = function() { windowControl(index); return false; }; // Toggle window with given index // Initialize icon for window toggle "button" const iconImage = document.createElement("img"); iconImage.classList.add("akito-iconImage"); iconClick.appendChild(iconImage); iconImage.src = iconURL; // Initialize actual window with given width, height, and left offset const thisWindow = document.createElement("div"); thisWindow.classList.add("akito-window"); const contentDiv = document.createElement("div"); // Initialize in advance for compatibility? thisWindow.appendChild(contentDiv); if(compatibility === true) { contentDiv.classList.add("akito-windowContentCompat"); } else { thisWindow.style.width = width; thisWindow.style.height = height; contentDiv.classList.add("akito-windowContent"); } // Best Buy doesn't let me set the left property that's so stupid statuses[index] = false; selectors[index] = $(thisWindow); // Initialize window header with title const header = generateHeader(title); thisWindow.appendChild(header); // Add to document body and retrieve selector when loaded window.addEventListener("DOMContentLoaded", function(_) { document.body.appendChild(thisWindow); $(thisWindow).hide(); // Best Buy forcing me to initially hide? if(compatibility === false) { new SimpleBar(contentDiv); } }) return contentDiv; } // Generates page footer for script user interface (script info and other elements) // @param {string} scriptText // @param {string} messageText function generateInterface(scriptText, messageText) { // Full-width footer containing script controls and output footer = document.createElement("div"); footer.classList.add("akito-footer"); // Script name/version/author information const scriptInfo = document.createElement("p"); scriptInfo.classList.add("akito-scriptInfo"); footer.appendChild(scriptInfo); scriptInfo.style.order = 0; scriptInfo.innerText = scriptText; // Miscellaneous message info (donation, link, etc.) const messageInfo = document.createElement("p"); messageInfo.classList.add("akito-messageInfo"); footer.appendChild(messageInfo); messageInfo.style.order = 1; messageInfo.innerHTML = messageText; // Append elements and process selectors on document load $(document).ready(function() { document.body.appendChild(footer); }); } // Small function for "weighing" the settings type for sorting // @param {Object} value function settingsWeight(value) { switch(value.type) { case "boolean": return 0; case "number": return 1; case "array": return 2; } } // Designates div for settings with onchange modification to passed settings // number: current (number), valid (array of numbers) // @param {DOMElement} contentDiv // @param {Object} settings function designateSettings(contentDiv, settings) { // Generate wrapper table element const settingsTable = document.createElement("table"); contentDiv.appendChild(settingsTable); settingsTable.classList.add("akito-table"); // Transform and sort settings by type and alphabetical order const settingsArray = []; for(const property in settings) { settingsArray.push(settings[property]); } settingsArray.sort(function(value, value2) { const valueWeight = settingsWeight(value); const value2Weight = settingsWeight(value2); if(valueWeight < value2Weight) { return -1; } else if(valueWeight === value2Weight) { if(value.description < value2.description) { return -1; } else if(value.description === value2.description) { return 0; // Should never happen } else { return 1; } } else { return 1; } }); // Add each setting as new row within table and attach onchange for(const setting of settingsArray) { // Generate row for specific setting const row = document.createElement("tr"); settingsTable.appendChild(row); // Generate cell showing setting description (onhover?) const descriptionCell = document.createElement("td"); row.appendChild(descriptionCell); descriptionCell.innerHTML = `${setting.description}`; // Generate cell with actual switcher (checkbox, slider, etc.) // Currently no support for arrays because they're complicated const settingCell = document.createElement("td"); row.appendChild(settingCell); settingCell.style.align = "center"; switch(setting.type) { case "boolean": // Checkbox const checkbox = document.createElement("input"); settingCell.appendChild(checkbox); checkbox.setAttribute("type", "checkbox"); checkbox.checked = setting.value; checkbox.onclick = function() { setting.value = checkbox.checked; }; break; case "number": // Numerical slider input + display const numberInput = document.createElement("input"); settingCell.appendChild(numberInput); numberInput.setAttribute("type", "number"); numberInput.value = setting.value; $(numberInput).change(function() { setting.value = numberInput.value; }); break; case "array": break; // Currently not implemented } } } // Designates div for logging and generates table appending function // @param {DOMElement} contentDiv // @returns {function} function designateLogging(contentDiv) { // Generate wrapper table element const loggingTable = document.createElement("table"); contentDiv.appendChild(loggingTable); loggingTable.classList.add("akito-table"); // Generates timestamp and appends to logging table // @param {string} message return function(message) { const row = document.createElement("tr"); // Generate timestamp cell const timestamp = "[" + (new Date()).toTimeString().split(' ')[0] + "]"; const loggingCell = document.createElement("td"); row.appendChild(loggingCell); loggingCell.innerHTML= `${timestamp} ${message}`; loggingTable.insertBefore(row, loggingTable.firstChild); } }