/* Q Light Controller Plus devtool.js Copyright (c) Heikki Junnila Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.txt Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ var devtool = Object; devtool.gridwidth = null; devtool.gridheight = null; devtool.gridsize = null; devtool.currentStep = 0; devtool.testTimer = null; devtool.alternate = false; devtool.onLoad = function() { devtool.loadAlgoFile(); // Initialize drag-and-drop support const onDragStart = function(e) { const dt = e.dataTransfer; if (dt.types && (dt.types.indexOf ? dt.types.indexOf('Files') != -1 : dt.types.contains('Files'))) { e.preventDefault(); e.stopPropagation(); document.dragAndDropTarget = e.target; document.getElementById('dropContainer').className = 'dragging'; } }; const onDragEnd = function(e) { const dt = e.dataTransfer; if (dt.types && (dt.types.indexOf ? dt.types.indexOf('Files') != -1 : dt.types.contains('Files')) && document.dragAndDropTarget == e.target) { e.preventDefault(); e.stopPropagation(); document.getElementById('dropContainer').className = ''; } }; const onDragDrop = function(e) { const dt = e.dataTransfer; if (dt.types && (dt.types.indexOf ? dt.types.indexOf('Files') != -1 : dt.types.contains('Files'))) { e.preventDefault(); e.stopPropagation(); document.getElementById('dropContainer').className = ''; devtool.onFileDropped(e.dataTransfer.files[0]); } } document.body.addEventListener('dragstart', onDragStart); document.body.addEventListener('dragover', onDragStart); document.body.addEventListener('dragenter', onDragStart); document.body.addEventListener('dragend', onDragEnd); document.body.addEventListener('dragleave', onDragEnd); document.body.addEventListener('drop', onDragDrop); } devtool.initDefinitions = function() { document.getElementById("apiversion").value = testAlgo.apiVersion; document.getElementById("name").value = testAlgo.name; document.getElementById("author").value = testAlgo.author; if (typeof testAlgo.acceptColors !== "undefined") { document.getElementById("acceptColors").value = testAlgo.acceptColors; } else { document.getElementById("acceptColors").value = "2 (Default)"; } } devtool.writeSelectOptions = function(item) { var opt = document.createElement("option"); var t = document.createTextNode(item); if (this.currentValue === item) { opt.selected = "selected"; } opt.setAttribute("value", item); opt.appendChild(t); this.inputElement.appendChild(opt); } devtool.addPropertyTableEntry = function(property) { var table = document.getElementById("properties"); var row = table.insertRow(-1); var i = 0; var keys = new Array(); var input; for (i = 0; i < property.length; i++) { keys.push(property[i][0]); } var name = ""; if (keys.indexOf("name") >= 0) { name = property[keys.indexOf("name")][1]; } var displayName = name; if (keys.indexOf("display") >= 0) { displayName = property[keys.indexOf("display")][1]; } var typeProperty = "string"; if (keys.indexOf("type") >= 0) { // list: defines a list of strings that will be displayed by the QLC+ RGB Matrix Editor // range: defined a range of integer values that this property can handle // integer: an integer value that QLC+ can exchange with the script // string: a string that QLC+ can exchange with the script typeProperty = property[keys.indexOf("type")][1]; } var values = Array(0, 0); if (keys.indexOf("values") >= 0) { values = property[keys.indexOf("values")][1].split(","); } var writeFunction = property[keys.indexOf("write")][1]; if (name !== "") { var storedValue = localStorage.getItem(name); if (storedValue !== null) { window.testAlgo[writeFunction](storedValue); } } var readFunction = property[keys.indexOf("read")][1]; var nameCell = row.insertCell(-1); var label = document.createElement('label'); label.textContent = displayName; label.htmlFor = name; nameCell.appendChild(label); var currentValue = window.testAlgo[readFunction](); var formCell = row.insertCell(-1); if (typeProperty === "list") { input = document.createElement("select"); input.name = name; input.id = name; input.setAttribute("onChange", "devtool.writeFunction('" + writeFunction + "', '" + name + "', this.value); devtool.setStep(0); devtool.writeCurrentStep()"); var selectOption = new Object(); selectOption.currentValue = currentValue; selectOption.readFunction = readFunction; selectOption.inputElement = input; values.forEach(devtool.writeSelectOptions, selectOption); formCell.appendChild(input); } else if (typeProperty === "range") { input = document.createElement("input"); input.type = "number"; input.required = "required"; input.name = name; input.setAttribute("value", currentValue); input.id = name; input.min = values[0]; input.max = values[1]; input.setAttribute("onChange", "devtool.writeFunction('" + writeFunction + "', '" + name + "', this.value); devtool.setStep(0); devtool.writeCurrentStep()"); formCell.appendChild(input); } else if (typeProperty === "float") { input = document.createElement("input"); input.type = "number"; input.required = "required"; input.name = name; input.setAttribute("value", currentValue); input.id = name; input.min = -1000000; input.max = 1000000; input.step = 0.001; input.setAttribute("onChange", "devtool.writeFunction('" + writeFunction + "', '" + name + "', this.value); devtool.setStep(0); devtool.writeCurrentStep()"); formCell.appendChild(input); } else { // string input = document.createElement("input"); input.type = "text"; input.name = name; input.setAttribute("value", currentValue); input.id = name; input.setAttribute("onChange", "devtool.writeFunction('" + writeFunction + "', '" + name + "', this.value); devtool.setStep(0); devtool.writeCurrentStep()"); formCell.appendChild(input); } } devtool.initProperties = function() { var table = document.getElementById("properties"); var properties = Array(); var property = Array(); var i = 0; var entry = 0; // Cleanup the table before updating its contents for (i = table.rows.length - 1; i >= 0; i--) { table.deleteRow(i); } // Algo properties not supported by versions prior to 2. if (testAlgo.apiVersion < 2) { return; } // Get the properties for (i = 0; i < testAlgo.properties.length; i++) { var propDef = testAlgo.properties[i]; var propKeyValue = propDef.split("|"); property = Array(); for (entry = 0; entry < propKeyValue.length; entry++) { var keyValue = propKeyValue[entry].split(":"); var key = keyValue[0]; keyValue.shift(); property.push(Array(key, keyValue.join(":"))); } properties.push(property); } // Write the properties properties.forEach(devtool.addPropertyTableEntry); } devtool.getAcceptColors = function() { var acceptColors = 2 // Default if (typeof testAlgo.acceptColors !== "undefined") { acceptColors = parseInt(testAlgo.acceptColors, 10); } return acceptColors; } devtool.initPixelColors = function() { var acceptColors = devtool.getAcceptColors(); var colorTable = document.getElementById("colorChooserTable"); if (colorTable === null) return; while (colorTable.rows.length > acceptColors) colorTable.deleteRow(-1); while (colorTable.rows.length < acceptColors) { var colorId = colorTable.rows.length + 1; var row = colorTable.insertRow(); row.id = "color" + colorId + "Chooser"; var titleCell = row.insertCell(); var label = document.createElement('label'); label.htmlFor = "color" + colorId + "Picker"; if (colorId === 1) label.textContent = "Color " + colorId + " (rrggbb)"; else label.textContent = "Color " + colorId + " (rrggbb, leave empty to disable)"; titleCell.appendChild(label); var colorInput = document.createElement("input"); colorInput.setAttribute("type", "text"); colorInput.setAttribute("id", "color" + colorId + "Text"); if (colorId === 1) colorInput.value = "FF0000"; else colorInput.value = ""; colorInput.setAttribute("pattern", "[0-9A-Fa-f]{6}"); colorInput.setAttribute("size", "6"); colorInput.setAttribute("onInput", "devtool.onColorTextChange()"); colorInput.required = true; var colorCell = row.insertCell(); colorCell.appendChild(colorInput); var colorPicker = document.createElement("input"); colorPicker.setAttribute("type", "color"); colorPicker.setAttribute("id", "color" + colorId + "Picker"); if (colorId === 1) colorPicker.value = "#FF0000"; else colorPicker.value = "#000000"; colorPicker.setAttribute("onInput", "devtool.onColorPickerChange(" + colorId + ")"); colorPicker.required = true; var pickerCell = row.insertCell(); pickerCell.appendChild(colorPicker); } } devtool.initSpeedValue = function() { var speed = localStorage.getItem("devtool.speed"); if (speed === null) { speed = 500; } document.getElementById("speed").value = speed; } devtool.initAlternateValue = function() { var alternate = localStorage.getItem("devtool.alternate"); if (alternate === true) { alternate = true; } else { alternate = false; } document.getElementById("alternate").checked = alternate; } devtool.initColorValues = function() { var rawColors = []; var acceptColors = devtool.getAcceptColors(); for (var i = 0; i < acceptColors; i++) { var idx = i + 1; var color = localStorage.getItem("devtool.color" + idx); if (color === null || Number.isNaN(parseInt("0x" + color, 16))) { if (idx === 1) color = "ff0000"; else color = "000000"; } color = color.padStart(6,"0"); rawColors.push(parseInt("0x" + color, 16)); document.getElementById("color" + idx + "Text").value = color; document.getElementById("color" + idx + "Picker").value = "#" + color; } if (testAlgo.apiVersion >= 3) { testAlgo.rgbMapSetColors(rawColors); } } devtool.initGridSize = function() { devtool.gridwidth = localStorage.getItem("devtool.gridwidth"); if (devtool.gridwidth === null) { devtool.gridwidth = 15; } document.getElementById("gridwidth").value = devtool.gridwidth; devtool.gridheight = localStorage.getItem("devtool.gridheight"); if (devtool.gridheight === null) { devtool.gridheight = 15; } document.getElementById("gridheight").value = devtool.gridheight; devtool.gridsize = localStorage.getItem("devtool.gridsize"); if (devtool.gridsize === null) { devtool.gridsize = 20; } document.getElementById("gridsize").value = devtool.gridsize; } devtool.getRgbFromColorInt = function(color) { var red = color >> 16; var green = (color >> 8) - red * 256; var blue = color - red * 256 * 256 - green * 256; return [red, green, blue]; } devtool.getColorInt = function(index) { var colorInput = document.getElementById("color" + index + "Text"); if (colorInput === null) return 0; return parseInt("0x" + colorInput.value, 16); } devtool.getCurrentColorInt = function() { var primaryColor = devtool.getColorInt(1); var secondaryColor = devtool.getColorInt(2); var acceptColors = devtool.getAcceptColors(); if (acceptColors === 0 || Number.isNaN(primaryColor)) { return null; } if (acceptColors === 1 || Number.isNaN(secondaryColor) || devtool.stepCount() <= 1) { return primaryColor; } var primaryColorRgb = devtool.getRgbFromColorInt(primaryColor); var secondaryColorRgb = devtool.getRgbFromColorInt(secondaryColor); var primaryFactor = (devtool.stepCount() - devtool.currentStep - 1) / (devtool.stepCount() - 1); var secondaryFactor = devtool.currentStep / (devtool.stepCount() - 1); var red = Math.round(primaryColorRgb[0] * primaryFactor + secondaryColorRgb[0] * secondaryFactor); var green = Math.round(primaryColorRgb[1] * primaryFactor + secondaryColorRgb[1] * secondaryFactor); var blue = Math.round(primaryColorRgb[2] * primaryFactor + secondaryColorRgb[2] * secondaryFactor); return red * 256 * 256 + green * 256 + blue; } devtool.writeCurrentStep = function() { // currentStep may have been changed manually devtool.currentStep = parseInt(document.getElementById("currentStep").value, 10); var map = document.getElementById("map"); for (var i = map.rows.length - 1; i >= 0; i--) { map.deleteRow(i); } var rgb = testAlgo.rgbMap(devtool.gridwidth, devtool.gridheight, devtool.getCurrentColorInt(), devtool.currentStep); for (var y = 0; y < devtool.gridheight; y++) { var row = map.insertRow(y); for (var x = 0; x < devtool.gridwidth; x++) { var cell = row.insertCell(x); var rgbStr = "#" + rgb[y][x].toString(16).padStart(6,"0"); cell.style.backgroundColor = rgbStr; cell.style.height = devtool.gridsize + "px"; cell.style.width = devtool.gridsize + "px"; cell.title = "(" + x + ", " + y + "): " + rgbStr + " – " + cell.style.backgroundColor; // rgbStr will be #rrggbb whereas the cell style will be rgb(255, 255, 255) } } } devtool.setStep = function(step) { devtool.currentStep = step; document.getElementById("currentStep").value = devtool.currentStep; devtool.writeCurrentStep(); } // Do not cache this value - the program also doesn't // and there are scripts changing this number on any parameter change devtool.stepCount = function() { return testAlgo.rgbMapStepCount(devtool.gridwidth, devtool.gridheight); } devtool.updateStepCount = function() { document.getElementById("stepCount").value = devtool.stepCount(); document.getElementById("currentStep").max = devtool.stepCount() - 1; } devtool.onGridSizeUpdated = function() { devtool.gridwidth = parseInt(document.getElementById("gridwidth").value, 10); localStorage.setItem("devtool.gridwidth", devtool.gridwidth); devtool.gridheight = parseInt(document.getElementById("gridheight").value, 10); localStorage.setItem("devtool.gridheight", devtool.gridheight); devtool.gridsize = parseInt(document.getElementById("gridsize").value, 10); localStorage.setItem("devtool.gridsize", devtool.gridsize); devtool.updateStepCount(); devtool.writeCurrentStep(); } devtool.onColorTextChange = function() { var rawColors = []; var acceptColors = devtool.getAcceptColors(); for (var i = 0; i < acceptColors; i++) { var idx = i + 1; var color = parseInt("0x" + document.getElementById("color" + idx + "Text").value, 16).toString(16); if (color === "NaN" && document.getElementById("color" + idx + "Picker").value != "#000000"){ document.getElementById("color" + idx + "Picker").value = "#000000"; } else { var pickerValue = "#" + color.padStart(6,"0"); if (document.getElementById("color" + idx + "Picker").value != pickerValue) { document.getElementById("color" + idx + "Picker").value = pickerValue; } } localStorage.setItem("devtool.color" + idx, color); rawColors.push(devtool.getColorInt(idx)); } if (testAlgo.apiVersion >= 3) { testAlgo.rgbMapSetColors(rawColors); } devtool.writeCurrentStep(); } devtool.onColorPickerChange = function(i) { var textId = "color" + i + "Text"; var pickerId = "color" + i + "Picker"; var oldTextValue = document.getElementById(textId).value; var newTextValue = document.getElementById(pickerId).value.substring(1); if (oldTextValue != newTextValue) { document.getElementById(textId).value = newTextValue; devtool.onColorTextChange(); } } devtool.startTest = function(inc) { var speed = document.getElementById("speed").value; window.clearInterval(devtool.testTimer); // avoid multiple timers running simultaneously if (inc > 0) { devtool.testTimer = window.setInterval("devtool.nextStep()", speed); } else { devtool.testTimer = window.setInterval("devtool.previousStep()", speed); } localStorage.setItem("devtool.timerRunning", inc); } devtool.stopTest = function() { window.clearInterval(devtool.testTimer); localStorage.setItem("devtool.timerRunning", 0); } devtool.initTestStatus = function() { let timerStatus = localStorage.getItem("devtool.timerRunning"); if (timerStatus === null || parseInt(timerStatus, 10) !== 0) { devtool.startTest(parseInt(timerStatus, 10)); } } devtool.init = function() { if (typeof testAlgo === "undefined") { return; } devtool.initDefinitions(); devtool.initGridSize(); devtool.initProperties(); devtool.initPixelColors(); devtool.initColorValues(); devtool.setStep(0); devtool.initSpeedValue(); devtool.onGridSizeUpdated(); devtool.writeCurrentStep(); devtool.initTestStatus(); } devtool.handleLoadError = function() { return; } devtool.onFilenameUpdated = function(filename) { if (filename === "") { return; } localStorage.setItem("devtool.filename", filename); devtool.refreshAlgoFile(filename, null); document.getElementById("filebrowse").value = ""; // devtool.init(); // is called onload. } devtool.onFileBrowsed = function(event) { const file = event.target.files[0]; if (!file) { return; } const reader = new FileReader(); reader.onload = function(event) { const content = event.target.result; localStorage.removeItem("devtool.filename"); document.getElementById("filename").value = ""; devtool.refreshAlgoFile(null, content); devtool.init(); }; reader.readAsText(file); } devtool.onFileDropped = function(file) { const reader = new FileReader(); reader.onload = function (event) { const content = event.target.result; localStorage.removeItem("devtool.filename"); document.getElementById("filename").value = ""; document.getElementById("filebrowse").value = ""; devtool.refreshAlgoFile(null, content); devtool.init(); }; reader.readAsText(file); } devtool.refreshAlgoFile = function(src = null, content = null) { var script = document.getElementById("algoScript"); if (script !== null) { script.remove(); } script = document.createElement("script"); script.id = "algoScript"; script.addEventListener("load", () => devtool.init(), false); script.addEventListener("error", () => devtool.handleLoadError(), false); script.type = "text/javascript"; if (src !== null) { script.src = src; } else if (content !== null) { script.text = content; } script.text = content; document.head.appendChild(script); } devtool.loadAlgoFile = function() { var storedValue = localStorage.getItem("devtool.filename"); if (storedValue != null) { document.getElementById("filename").value = storedValue; devtool.onFilenameUpdated(storedValue); } } devtool.writeFunction = function(functionName, propertyName, value) { window.testAlgo[functionName](value); localStorage.setItem(propertyName, value); devtool.initPixelColors(); devtool.initColorValues(); devtool.updateStepCount(); } devtool.onAlternateChanged = function() { var alternate = document.getElementById("alternate").checked; localStorage.setItem("devtool.alternate", alternate); devtool.alternate = alternate; } devtool.onSpeedChanged = function() { var speed = document.getElementById("speed").value; localStorage.setItem("devtool.speed", speed); devtool.initTestStatus(); } devtool.nextStep = function() { if (devtool.currentStep + 1 < devtool.stepCount()) { devtool.setStep(devtool.currentStep + 1); } else { let timerStatus = localStorage.getItem("devtool.timerRunning"); let alternate = document.getElementById("alternate").checked; if (timerStatus == "1" && alternate) { devtool.startTest(-1); } else { devtool.setStep(0); } } } devtool.previousStep = function() { if (devtool.currentStep > 0 && (devtool.currentStep - 1) < devtool.stepCount()) { devtool.setStep(devtool.currentStep - 1); } else { let timerStatus = localStorage.getItem("devtool.timerRunning"); let alternate = document.getElementById("alternate").checked; if (timerStatus == "-1" && alternate) { devtool.startTest(1); } else { devtool.setStep(devtool.stepCount() - 1); // last step } } }