-
Select Device
-
-
-
- Orbit
-
-
+
+
+
+
+
+
+
+
`;
-
- super('modesPanel', content, 'Modes and Device Controls');
+ super('modesPanel', content, 'Modes List');
this.editor = editor;
this.lightshow = editor.lightshow;
this.vortexPort = editor.vortexPort;
this.shareModal = new Modal('share');
this.exportModal = new Modal('export');
this.importModal = new Modal('import');
-
- // note changing this will not impact which device is shown, this just records
- // which device was selected or connected for reference later
- this.selectedDevice = 'None';
- this.devices = {
- 'None': { image: 'public/images/none-logo-square-512.png', icon: 'public/images/none-logo-square-64.png', label: 'None', ledCount: 1 },
- 'Orbit': { image: 'public/images/orbit.png', icon: 'public/images/orbit-logo-square-64.png', label: 'Orbit', ledCount: 28 },
- 'Handle': { image: 'public/images/handle.png', icon: 'public/images/handle-logo-square-64.png', label: 'Handle', ledCount: 3 },
- 'Gloves': { image: 'public/images/gloves.png', icon: 'public/images/gloves-logo-square-64.png', label: 'Gloves', ledCount: 10 },
- 'Chromadeck': { image: 'public/images/chromadeck.png', icon: 'public/images/chromadeck-logo-square-64.png', label: 'Chromadeck', ledCount: 20 },
- 'Spark': { image: 'public/images/spark.png', icon: 'public/images/spark-logo-square-64.png', label: 'Spark', ledCount: 6 },
- 'Duo': { image: 'public/images/duo.png', icon: 'public/images/duo-logo-square-64.png', label: 'Duo', ledCount: 2 }
- };
}
initialize() {
// Hide device connection section and leds fieldset initially
//document.getElementById('deviceConnectionSection').style.display = 'block';
- document.getElementById('ledsFieldset').style.display = 'none';
+ //document.getElementById('ledsFieldset').style.display = 'none';
// optionally initialize the chromalink now:
//this.chromalinkPanel = new ChromalinkPanel(this.editor, this);
@@ -134,170 +89,20 @@ export default class ModesPanel extends Panel {
transmitButton.addEventListener('click', () => this.editor.transmitVL());
document.addEventListener('patternChange', () => this.refresh(true));
+ document.addEventListener('deviceChange', this.handleDeviceEvent.bind(this));
- document.getElementById('connectDeviceButton').addEventListener('click', async () => {
- try {
- // TODO: check for the thing
- // // Check if WebSerial is available in the browser
- //if ('serial' in navigator) {
- //} else {
- // document.getElementById('connectDevice').style.display = 'none';
- // document.getElementById('deviceConnectMessage').style.display = 'none';
- // document.getElementById('unsupportedBrowserMessage').style.display = 'block';
- //}
-
- //
- //
- // WebSerial is not supported in your browser.
- // Please use a supported browser to connect a device.
- //
- //
-
- await this.vortexPort.requestDevice(deviceEvent => this.deviceChange(deviceEvent));
- } catch (error) {
- console.log("Error: " + error);
- Notification.failure('Failed to connect: ' + error.message);
- }
- });
-
- const ledList = document.getElementById('ledList');
- ledList.addEventListener('change', () => this.handleLedSelectionChange());
- ledList.addEventListener('click', () => this.handleLedSelectionChange());
-
- document.getElementById('selectAllLeds').addEventListener('click', () => this.selectAllLeds());
- document.getElementById('selectNoneLeds').addEventListener('click', () => this.selectNoneLeds());
- document.getElementById('invertLeds').addEventListener('click', () => this.invertLeds());
- document.getElementById('evenLeds').addEventListener('click', () => this.evenLeds());
- document.getElementById('oddLeds').addEventListener('click', () => this.oddLeds());
- document.getElementById('randomLeds').addEventListener('click', () => this.randomLeds());
-
- const deviceImageContainer = document.getElementById('deviceImageContainer');
- deviceImageContainer.addEventListener('mousedown', (event) => this.onMouseDown(event));
- document.addEventListener('mousemove', (event) => this.onMouseMove(event));
- document.addEventListener('mouseup', (event) => this.onMouseUp(event));
-
- document.addEventListener('deviceTypeChange', (event) => {
- this.renderLedIndicators(this.selectedDevice);
- this.handleLedSelectionChange();
- });
-
- // Initialize dropdown with icons
- this.addIconsToDropdown();
- // Add event listener for device type selection
- document.getElementById('deviceTypeOptions').addEventListener('click', (event) => {
- if (event.target && event.target.classList.contains('custom-dropdown-option')) {
- const selectedValue = event.target.getAttribute('data-value');
- this.updateSelectedDevice(selectedValue);
- }
- });
-
- // Add event listener for the selected device type dropdown
- document.getElementById('deviceTypeSelected').addEventListener('click', () => {
- if (event.currentTarget.classList.contains('locked')) {
- event.stopPropagation(); // Prevent further propagation of the click event
- event.preventDefault(); // Prevent the default action associated with the click event
- return;
- }
- document.getElementById('deviceTypeOptions').classList.toggle('show');
- });
- this.refreshModeList();
- }
-
- lockDeviceSelection(locked) {
- const deviceTypeSelected = document.getElementById('deviceTypeSelected');
-
- if (locked) {
- deviceTypeSelected.classList.add('locked');
- } else {
- deviceTypeSelected.classList.remove('locked');
- }
- }
-
- updateSelectedDevice(device, lock = false) {
- const deviceTypeSelected = document.getElementById('deviceTypeSelected');
- const modesListScrollContainer = document.getElementById('modesListScrollContainer');
-
- const deviceIcon = this.devices[device].icon;
-
- this.selectedDevice = device;
- document.dispatchEvent(new CustomEvent('deviceTypeChange', { detail: this.selectedDevice }));
-
- if (device === 'None') {
- deviceTypeSelected.innerHTML = 'Select Device';
- document.getElementById('deviceTypeOptions').classList.remove('show');
- this.lightshow.setLedCount(1); // Reset to default LED count
- modesListScrollContainer.style.height = '200px';
-
- // hide the spread slider
- document.getElementById('spread_div').style.display = 'none';
-
- // Hide the LED selection fieldset
- ledsFieldset.style.display = 'none';
-
- // set the lock
- this.lockDeviceSelection(lock);
-
- return;
- }
-
- // display the spread slider
- document.getElementById('spread_div').style.display = 'block';
-
- deviceTypeSelected.innerHTML = `
-
- ${device}
- `;
-
- this.lightshow.setLedCount(this.devices[device].ledCount);
- if (device === 'None') {
- document.getElementById('deviceTypeOptions').classList.add('show');
- // make the modes list long again
- if (modesListScrollContainer) {
- modesListScrollContainer.style.height = '200px';
- }
- // hide led selection
- ledsFieldset.style.display = 'none';
- // all done
- return
- }
-
- // otherwise handle all other devices with shorter modes list
- if (modesListScrollContainer) {
- modesListScrollContainer.style.height = '200px';
- }
- ledsFieldset.style.display = 'block'; // Show the fieldset for other devices
- document.getElementById('deviceTypeOptions').classList.remove('show');
- this.lockDeviceSelection(lock);
this.refreshModeList();
}
- addIconsToDropdown() {
- const deviceTypeOptions = document.getElementById('deviceTypeOptions');
- deviceTypeOptions.innerHTML = Object.keys(this.devices).map(key => {
- const device = this.devices[key];
- return `
-
- ${device.label}
-
- `;
- }).join('');
- }
-
- showDeviceConnectionSection() {
- //document.getElementById('deviceConnectionSection').style.display = 'block';
- document.getElementById('ledsFieldset').style.display = 'block';
- }
-
- async onDeviceConnect() {
+ onDeviceConnect(deviceName) {
console.log("Device connected: " + this.vortexPort.name);
- const ledCount = this.devices[this.vortexPort.name].ledCount;
+ const ledCount = this.editor.devices[this.vortexPort.name].ledCount;
if (ledCount !== undefined) {
this.lightshow.setLedCount(ledCount);
console.log(`Set LED count to ${ledCount} for ${this.vortexPort.name}`);
} else {
console.log(`Device name ${this.vortexPort.name} not recognized`);
}
- document.dispatchEvent(new CustomEvent('deviceConnected'));
this.refresh(true);
//Notification.success(this.vortexPort.name + ' Connected!');
//let statusMessage = document.getElementById('deviceStatus');
@@ -320,6 +125,8 @@ export default class ModesPanel extends Panel {
//if (!this.editor.updatePanel.isVisible && this.vortexPort.name === 'Spark') {
// this.editor.updatePanel.show();
//}
+
+ console.log("Checking version...");
// check version numbers
this.editor.checkVersion(this.vortexPort.name, this.vortexPort.version);
@@ -339,185 +146,23 @@ export default class ModesPanel extends Panel {
if (modesListScrollContainer) {
modesListScrollContainer.style.height = '200px';
}
-
- // update device selection and lock it so it can't change
- this.updateSelectedDevice(this.vortexPort.name, true);
-
- this.selectAllLeds();
- }
-
- showOutdatedFirmwareNotification(device, version, latestVersion, downloadUrl) {
- const modalId = 'outdatedFirmwareModal' + device;
-
- // Check if the modal already exists
- let modal = Modal.getExistingModal(modalId);
-
- if (!modal) {
- modal = new Modal(modalId);
- }
-
- const lowerDevice = device.toLowerCase();
- const content = `
-
-
Your device is out of date, please install the latest version
-
Device: ${device}
-
Current Version: ${version}
-
Latest Version: ${latestVersion}
-
-
- `;
-
- modal.show({ title: device + ' Firmware Update', blurb: content });
}
- // Add the rest of the unchanged methods here...
- onMouseDown(event) {
- if (event.button !== 0) return; // Only react to left mouse button
- event.preventDefault();
-
- this.isDragging = true;
- this.startX = event.clientX;
- this.startY = event.clientY;
- this.currentX = event.clientX;
- this.currentY = event.clientY;
- this.dragStartTime = Date.now(); // Record the time when dragging started
-
- this.selectionBox = document.createElement('div');
- this.selectionBox.classList.add('selection-box');
- document.body.appendChild(this.selectionBox);
-
- this.selectionBox.style.left = `${this.startX}px`;
- this.selectionBox.style.top = `${this.startY}px`;
- }
-
- onMouseMove(event) {
- if (!this.isDragging) return;
- event.preventDefault();
-
- this.currentX = event.clientX;
- this.currentY = event.clientY;
-
- this.selectionBox.style.width = `${Math.abs(this.currentX - this.startX)}px`;
- this.selectionBox.style.height = `${Math.abs(this.currentY - this.startY)}px`;
- this.selectionBox.style.left = `${Math.min(this.currentX, this.startX)}px`;
- this.selectionBox.style.top = `${Math.min(this.currentY, this.startY)}px`;
- }
-
- onMouseUp(event) {
- if (event.button !== 0) return; // Only react to left mouse button
- event.preventDefault();
-
- if (!this.selectionBox) {
- this.isDragging = false;
- return;
- }
-
- if (this.selectionBox) {
- document.body.removeChild(this.selectionBox);
- this.selectionBox = null;
- }
-
- if (!this.isDragging) {
- return;
- }
-
- this.isDragging = false;
- if (this.lightshow.vortex.engine().modes().curMode().isMultiLed()) {
- Notification.failure("To select LEDs switch to a single led pattern.", 3000);
- // Prevent selection if multi-LED pattern is applied
- return;
- }
-
- const deviceImageContainer = document.getElementById('deviceImageContainer');
- const rect = deviceImageContainer.getBoundingClientRect();
-
- let startX = Math.min(this.startX, this.currentX) - rect.left;
- let startY = Math.min(this.startY, this.currentY) - rect.top;
- let endX = Math.max(this.startX, this.currentX) - rect.left;
- let endY = Math.max(this.startY, this.currentY) - rect.top;
-
- // Ensure values are within bounds of the container
- startX = Math.max(startX, 0);
- startY = Math.max(startY, 0);
- endX = Math.min(endX, rect.width);
- endY = Math.min(endY, rect.height);
-
- const isClick = Math.abs(startX - endX) <= 3 && Math.abs(startY - endY) <= 3;
- const clickX = (startX + endX) / 2;
- const clickY = (startY + endY) / 2;
-
- document.querySelectorAll('.led-indicator').forEach(indicator => {
- const ledRect = indicator.getBoundingClientRect();
- const ledX1 = (ledRect.left - rect.left) + 1;
- const ledY1 = (ledRect.top - rect.top) + 1;
- const ledX2 = (ledRect.right - rect.left) - 1;
- const ledY2 = (ledRect.bottom - rect.top) - 1;
- const ledMidX = ledX1 + (ledRect.width / 2);
- const ledMidY = ledY1 + (ledRect.height / 2);
-
- const ledList = document.getElementById('ledList');
- let option = ledList.querySelector(`option[value='${indicator.dataset.ledIndex}']`);
-
- let withinBounds = false;
-
- if (isClick) {
- // Check if click is within the bounds of the indicator
- withinBounds = clickX >= ledX1 && clickX <= ledX2 && clickY >= ledY1 && clickY <= ledY2;
- } else {
- // Check if any part of the indicator is within the selection box
- withinBounds =
- (ledX1 >= startX && ledX1 <= endX && ledY1 >= startY && ledY1 <= endY) ||
- (ledX2 >= startX && ledX2 <= endX && ledY1 >= startY && ledY1 <= endY) ||
- (ledX1 >= startX && ledX1 <= endX && ledY2 >= startY && ledY2 <= endY) ||
- (ledX2 >= startX && ledX2 <= endX && ledY2 >= startY && ledY2 <= endY) ||
- (ledMidX >= startX && ledMidX <= endX && ledMidY >= startY && ledMidY <= endY);
- }
-
- if (withinBounds) {
- this.selectLed(indicator.dataset.ledIndex, !event.ctrlKey);
- option.selected = !event.ctrlKey;
- if (option.selected) {
- indicator.classList.add('selected');
- } else {
- indicator.classList.remove('selected');
- }
- } else if (!event.shiftKey && !event.ctrlKey) {
- this.selectLed(indicator.dataset.ledIndex, false);
- option.selected = false;
- indicator.classList.remove('selected');
- }
- });
-
- // Manually call the handler
- this.handleLedSelectionChange();
- }
-
- selectLed(index, selected = true) {
- const ledList = document.getElementById('ledList');
- let option = ledList.querySelector(`option[value='${index}']`);
- if (!option) {
- // don't add it if it's not there
- return;
- }
- option.selected = selected;
-
- this.handleLedSelectionChange();
- }
-
- deviceChange(deviceEvent) {
+ handleDeviceEvent(deviceChangeEvent) {
+ // Access the custom data from `event.detail`
+ const { deviceEvent, deviceName } = deviceChangeEvent.detail;
if (deviceEvent === 'waiting') {
- this.onDeviceWaiting();
+ this.onDeviceWaiting(deviceName);
} else if (deviceEvent === 'connect') {
- this.onDeviceConnect();
+ this.onDeviceConnect(deviceName);
} else if (deviceEvent === 'disconnect') {
- this.onDeviceDisconnect();
+ this.onDeviceDisconnect(deviceName);
+ } else if (deviceEvent === 'select') {
+ this.onDeviceSelected(deviceName);
}
}
- onDeviceWaiting() {
+ onDeviceWaiting(deviceName) {
Notification.success("Waiting for device...");
//let statusMessage = document.getElementById('deviceStatus');
//statusMessage.textContent = 'Waiting for device...';
@@ -525,46 +170,7 @@ export default class ModesPanel extends Panel {
//statusMessage.classList.remove('status-success', 'status-failure');
}
- toggleLed(index) {
- const cur = this.lightshow.vortex.engine().modes().curMode();
- if (cur.isMultiLed()) return; // Prevent toggling if multi-LED pattern is applied
-
- const ledIndicator = document.querySelector(`.led-indicator[data-led-index='${index}']`);
- if (ledIndicator) {
- ledIndicator.classList.toggle('selected');
- }
-
- // Update internal state without triggering change event
- const ledList = document.getElementById('ledList');
- const option = ledList.querySelector(`option[value='${index}']`);
- if (!option) {
- // Create option if it doesn't exist
- const newOption = document.createElement('option');
- newOption.value = index;
- newOption.textContent = `LED ${index}`;
- ledList.appendChild(newOption);
- newOption.selected = true;
- } else {
- option.selected = !option.selected;
- }
-
- // Directly call the handler to avoid cyclic event loop
- this.handleLedSelectionChange();
- }
-
- async getLedPositions(deviceName) {
- try {
- const cacheBuster = '?v=' + new Date().getTime();
- const response = await fetch(`public/data/${deviceName.toLowerCase()}-led-positions.json${cacheBuster}`);
- const data = await response.json();
- return data;
- } catch (error) {
- console.error(`Error loading LED positions for ${deviceName}:`, error);
- return { points: [], original_width: 1, original_height: 1 };
- }
- }
-
- onDeviceDisconnect() {
+ onDeviceDisconnect(deviceName) {
console.log("Device disconnected");
Notification.success(this.vortexPort.name + ' Disconnected!');
@@ -578,223 +184,19 @@ export default class ModesPanel extends Panel {
//statusMessage.textContent = this.vortexPort.name + ' Disconnected!';
//statusMessage.classList.remove('status-success', 'status-pending');
//statusMessage.classList.add('status-failure');
- this.lockDeviceSelection(false);
+ //this.lockDeviceSelection(false);
}
- refresh(fromEvent = false) {
- this.refreshModeList(fromEvent);
- this.updateLedIndicators(); // Ensure indicators are updated
+ onDeviceSelected(devicename) {
+ //
}
- refreshLedList(fromEvent = false) {
- const ledList = document.getElementById('ledList');
- const ledControls = document.getElementById('ledControls');
- const deviceImageContainer = document.getElementById('deviceImageContainer');
-
- let selectedLeds = Array.from(ledList.selectedOptions).map(option => option.value);
- const cur = this.lightshow.vortex.engine().modes().curMode();
- if (!cur) {
- ledList.innerHTML = '';
- return;
- }
- this.clearLedList();
- this.clearLedSelections();
-
- if (!cur.isMultiLed()) {
- for (let pos = 0; pos < this.lightshow.vortex.numLedsInMode(); ++pos) {
- let ledName = this.lightshow.vortex.ledToString(pos) + " (" + this.lightshow.vortex.getPatternName(pos) + ")";
- const option = document.createElement('option');
- option.value = pos;
- option.textContent = ledName;
- ledList.appendChild(option);
- }
- ledControls.style.display = 'flex';
- deviceImageContainer.querySelectorAll('.led-indicator').forEach(indicator => {
- indicator.style.backgroundColor = ''; // Reset to default
- });
- if (selectedLeds.includes("multi")) {
- this.selectAllLeds();
- selectedLeds = Array.from(ledList.selectedOptions).map(option => option.value);
- }
- } else {
- // If multi-LED pattern is applied
- let ledName = "Multi led (" + this.lightshow.vortex.getPatternName(this.lightshow.vortex.engine().leds().ledMulti()) + ")";
- const option = document.createElement('option');
- option.value = 'multi';
- option.textContent = ledName;
- ledList.appendChild(option);
- selectedLeds = [ "multi" ];
-
- // Disable LED controls
- ledControls.style.display = 'none';
-
- // Set all LED indicators to green
- deviceImageContainer.querySelectorAll('.led-indicator').forEach(indicator => {
- indicator.classList.add('selected');
- });
- }
- if (!selectedLeds.length && ledList.options.length > 0) {
- selectedLeds = [ "0" ];
- }
- this.applyLedSelections(selectedLeds);
- }
-
- async renderLedIndicators(deviceName = null) {
- const ledsFieldset = document.getElementById('ledsFieldset');
- const deviceImageContainer = document.getElementById('deviceImageContainer');
- const ledControls = document.getElementById('ledControls');
-
- if (!deviceName || deviceName === 'None') {
- ledsFieldset.style.display = 'none';
- return;
- }
-
- ledsFieldset.style.display = 'block';
- deviceImageContainer.innerHTML = '';
- ledList.style.display = 'block';
- ledControls.style.display = 'flex';
- deviceImageContainer.innerHTML = '';
-
- const overlay = document.createElement('div');
- overlay.classList.add('led-overlay');
- deviceImageContainer.appendChild(overlay);
-
- const deviceData = await this.getLedPositions(deviceName);
- const deviceImageSrc = this.devices[deviceName].image;
-
- if (deviceImageSrc) {
- const deviceImage = document.createElement('img');
- deviceImage.src = deviceImageSrc + '?v=' + new Date().getTime();
- deviceImage.style.display = 'block';
- deviceImage.style.width = '100%';
- deviceImage.style.height = 'auto';
-
- deviceImage.onload = () => {
- const scaleX = deviceImageContainer.clientWidth / deviceData.original_width;
- const scaleY = deviceImageContainer.clientHeight / deviceData.original_height;
-
- deviceData.points.forEach((point, index) => {
- const ledIndicator = document.createElement('div');
- ledIndicator.classList.add('led-indicator');
- ledIndicator.style.left = `${point.x * scaleX}px`;
- ledIndicator.style.top = `${point.y * scaleY}px`;
- ledIndicator.dataset.ledIndex = index;
-
- overlay.appendChild(ledIndicator);
- });
- };
-
- deviceImageContainer.appendChild(deviceImage);
- }
- }
-
- updateLedIndicators() {
- const selectedLeds = this.getSelectedLeds();
- const cur = this.lightshow.vortex.engine().modes().curMode();
-
- document.querySelectorAll('.led-indicator').forEach(indicator => {
- const index = indicator.dataset.ledIndex;
- if (cur && cur.isMultiLed()) {
- indicator.classList.add('selected');
- } else {
- if (selectedLeds.includes(index.toString())) {
- indicator.classList.add('selected');
- } else {
- indicator.classList.remove('selected');
- }
- indicator.style.backgroundColor = ''; // Reset to default if not multi-LED
- }
- });
+ refresh(fromEvent = false) {
+ this.refreshModeList(fromEvent);
}
refreshPatternControlPanel() {
- document.dispatchEvent(new CustomEvent('modeChange', { detail: this.getSelectedLeds() }));
- }
-
- clearLedList() {
- const ledList = document.getElementById('ledList');
- ledList.innerHTML = '';
- }
-
- clearLedSelections() {
- const ledList = document.getElementById('ledList');
- for (let option of ledList.options) {
- option.selected = false;
- }
- }
-
- selectAllLeds() {
- const ledList = document.getElementById('ledList');
- for (let option of ledList.options) {
- option.selected = true;
- }
- this.handleLedSelectionChange();
- }
-
- applyLedSelections(selectedLeds) {
- const ledList = document.getElementById('ledList');
- for (let option of ledList.options) {
- if (selectedLeds.includes(option.value)) {
- option.selected = true;
- } else {
- option.selected = false;
- }
- }
- }
-
- selectAllLeds() {
- const ledList = document.getElementById('ledList');
- for (let option of ledList.options) {
- option.selected = true;
- }
- this.handleLedSelectionChange();
- }
-
- selectNoneLeds() {
- const ledList = document.getElementById('ledList');
- for (let option of ledList.options) {
- option.selected = false;
- }
- this.handleLedSelectionChange();
- }
-
- invertLeds() {
- const ledList = document.getElementById('ledList');
- for (let option of ledList.options) {
- option.selected = !option.selected;
- }
- this.handleLedSelectionChange();
- }
-
- evenLeds() {
- const ledList = document.getElementById('ledList');
- for (let i = 0; i < ledList.options.length; i++) {
- ledList.options[i].selected = (i % 2 === 0);
- }
- this.handleLedSelectionChange();
- }
-
- oddLeds() {
- const ledList = document.getElementById('ledList');
- for (let i = 0; i < ledList.options.length; i++) {
- ledList.options[i].selected = (i % 2 !== 0);
- }
- this.handleLedSelectionChange();
- }
-
- randomLeds(probability = 0.5) {
- const ledList = document.getElementById('ledList');
- for (let option of ledList.options) {
- option.selected = Math.random() < probability;
- }
- this.handleLedSelectionChange();
- }
-
- handleLedSelectionChange() {
- const selectedOptions = this.getSelectedLeds();
- this.lightshow.targetLeds = selectedOptions;
- document.dispatchEvent(new CustomEvent('ledsChange', { detail: selectedOptions }));
- this.updateLedIndicators();
+ document.dispatchEvent(new CustomEvent('modeChange', { detail: this.editor.ledSelectPanel.getSelectedLeds() }));
}
clearModeList() {
@@ -839,7 +241,7 @@ export default class ModesPanel extends Panel {
}
this.lightshow.vortex.setCurMode(curSel, false);
this.attachModeEventListeners();
- this.refreshLedList(fromEvent);
+ this.editor.ledSelectPanel.refreshLedList(fromEvent);
}
selectMode(index) {
@@ -856,11 +258,10 @@ export default class ModesPanel extends Panel {
selectedMode.style.display = 'flex';
}
- this.refreshLedList();
+ this.editor.ledSelectPanel.refreshLedList();
this.refreshPatternControlPanel();
}
-
attachModeEventListeners() {
const modesListContainer = document.getElementById('modesListContainer');
@@ -917,36 +318,39 @@ export default class ModesPanel extends Panel {
});
}
- getLedList() {
- const ledList = document.getElementById('ledList');
- return Array.from(ledList.options).map(option => option.value);
- }
-
- getSelectedLeds() {
- const ledList = document.getElementById('ledList');
- return Array.from(ledList.selectedOptions).map(option => option.value);
- }
-
addMode() {
let modeCount = this.lightshow.vortex.numModes();
- switch (this.selectedDevice) {
- case 'Orbit':
- case 'Handle':
- case 'Gloves':
- if (modeCount >= 14) {
- Notification.failure("This device can only hold 14 modes");
- return;
- }
- break;
- case 'Duo':
- // TODO: version check?
- if (modeCount >= 5) {
- Notification.failure("This device can only hold 5 modes");
- return;
- }
- break;
- default:
- break;
+ let maxModes = 16;
+ const device = this.editor.devicePanel.selectedDevice;
+ switch (device) {
+ case 'Orbit':
+ case 'Handle':
+ case 'Gloves':
+ // these devices had 14
+ maxModes = 14;
+ break;
+ case 'Duo':
+ // default duo max is 5
+ maxModes = 5;
+ if (this.editor && this.editor.chromalinkPanel) {
+ const clPanel = this.editor.chromalinkPanel;
+ if (clPanel.duoHeader && clPanel.duoHeader.vMinor >= 4) {
+ // allow 9 modes after 1.4.x duo
+ maxModes = 9;
+ }
+ }
+ break;
+ case 'Chromadeck':
+ case 'Spark':
+ // 16 modes
+ break;
+ default:
+ break;
+ }
+ // check the mode count against max
+ if (modeCount >= maxModes) {
+ Notification.failure(`The ${device} can only hold ${maxModes} modes`);
+ return;
}
if (!this.lightshow.vortex.addNewMode(false)) {
Notification.failure("Failed to add another mode");
@@ -1140,7 +544,7 @@ export default class ModesPanel extends Panel {
if (addNew) {
curSel = this.lightshow.vortex.engine().modes().curModeIndex();
let modeCount = this.lightshow.vortex.numModes();
- switch (this.selectedDevice) {
+ switch (this.editor.devicePanel.selectedDevice) {
case 'Orbit':
case 'Handle':
case 'Gloves':
diff --git a/js/PatternPanel.js b/js/PatternPanel.js
index eff1728..b4e7622 100644
--- a/js/PatternPanel.js
+++ b/js/PatternPanel.js
@@ -30,7 +30,7 @@ export default class PatternPanel extends Panel {
this.refresh();
document.addEventListener('modeChange', this.handleModeChange.bind(this));
document.addEventListener('ledsChange', this.handleLedsChange.bind(this));
- document.addEventListener('deviceConnected', this.handleDeviceConnected.bind(this));
+ document.addEventListener('deviceChange', this.handleDeviceEvent.bind(this));
// Attach event listeners for help and randomize buttons
document.getElementById('patternRandomizeButton').addEventListener('click', () => this.randomizePattern());
@@ -70,14 +70,41 @@ export default class PatternPanel extends Panel {
this.refresh(true);
}
- handleDeviceConnected() {
+ handleDeviceEvent(deviceChangeEvent) {
+ // Access the custom data from `event.detail`
+ const { deviceEvent, deviceName } = deviceChangeEvent;
+ if (deviceEvent === 'waiting') {
+ this.onDeviceWaiting(deviceName);
+ } else if (deviceEvent === 'connect') {
+ this.onDeviceConnect(deviceName);
+ } else if (deviceEvent === 'disconnect') {
+ this.onDeviceDisconnect(deviceName);
+ } else if (deviceEvent === 'select') {
+ this.onDeviceSelected(deviceName);
+ }
+ }
+
+ onDeviceWaiting(deviceName) {
+ // nothing yet
+ }
+
+ onDeviceConnect(deviceName) {
this.multiEnabled = true;
this.populatePatternDropdown();
this.refresh(true);
+ // uh is this supposed to be here?
this.vortexPort.startReading();
this.editor.demoModeOnDevice();
}
+ onDeviceDisconnect(deviceName) {
+ // nothing yet
+ }
+
+ onDeviceSelected(deviceName) {
+ // nothing yet
+ }
+
setTargetSingles(selectedLeds = null) {
const ledCount = this.lightshow.vortex.engine().leds().ledCount();
this.targetLeds = (selectedLeds || Array.from({ length: ledCount }, (_, i) => i.toString()))
@@ -147,7 +174,7 @@ export default class PatternPanel extends Panel {
dropdown.appendChild(blendGroup);
dropdown.appendChild(solidGroup);
- if (this.editor.modesPanel.selectedDevice !== 'None') {
+ if (this.editor.devicePanel.selectedDevice !== 'None') {
dropdown.appendChild(multiGroup);
}
}
diff --git a/js/UpdatePanel.js b/js/UpdatePanel.js
index 7b4d4d6..301b488 100644
--- a/js/UpdatePanel.js
+++ b/js/UpdatePanel.js
@@ -2,7 +2,7 @@ import Panel from './Panel.js';
import Notification from './Notification.js';
export default class UpdatePanel extends Panel {
- constructor(editor, modesPanel) {
+ constructor(editor) {
const content = `
@@ -19,7 +19,6 @@ export default class UpdatePanel extends Panel {
super('updatePanel', content, 'Device Updates', { showCloseButton: true });
this.editor = editor;
this.vortexPort = editor.vortexPort;
- this.modesPanel = modesPanel;
this.espStub = null;
}
@@ -49,9 +48,9 @@ export default class UpdatePanel extends Panel {
}
});
- document.addEventListener('deviceConnected', () => {
- Notification.success('Device connected. Ready to flash firmware.');
- });
+ //document.addEventListener('deviceChange', () => {
+ // do anything if the device changes...?
+ //});
this.toggleCollapse(false);
this.hide();
@@ -63,11 +62,10 @@ export default class UpdatePanel extends Panel {
throw new Error('No serial port available.');
}
- const esploaderMod = await window.esptoolPackage;
- const esploader = new esploaderMod.ESPLoader(this.vortexPort.serialPort, console);
-
- await esploader.initialize();
- this.espStub = await esploader.runStub();
+ const esptool = await window.esptoolPackage;
+ this.espLoader = new esptool.ESPLoader(this.vortexPort.serialPort, console);
+ await this.espLoader.initialize();
+ this.espStub = await this.espLoader.runStub();
} catch (error) {
throw new Error('Failed to initialize ESP flasher: ' + error.message);
}
@@ -80,7 +78,7 @@ export default class UpdatePanel extends Panel {
async fetchAndFlashFirmware() {
let targetDevice = this.vortexPort.name.toLowerCase();
if (!targetDevice) {
- targetDevice = this.editor.modesPanel.selectedDevice.toLowerCase();
+ targetDevice = this.editor.devicePanel.selectedDevice.toLowerCase();
}
if (targetDevice === 'none') {
throw new Error(`Select a device first`);
@@ -222,9 +220,15 @@ export default class UpdatePanel extends Panel {
console.log('All files flashed successfully.');
try {
+ console.log('ESP32 reset complete.');
+ if (this.espLoader) {
+ //await this.espLoader.disconnect();
+ await this.espLoader._reader.releaseLock();
+ console.log('Disconnected ESP Loader.');
+ }
console.log('Resetting ESP32...');
await this.espStub.hardReset();
- console.log('ESP32 reset complete.');
+ await this.editor.vortexPort.restartConnecton();
} catch (resetError) {
console.error('Failed to reset ESP32:', resetError);
}
diff --git a/js/VortexEditor.js b/js/VortexEditor.js
index 6406fe9..4976ae0 100644
--- a/js/VortexEditor.js
+++ b/js/VortexEditor.js
@@ -5,7 +5,9 @@ import AnimationPanel from './AnimationPanel.js';
import PatternPanel from './PatternPanel.js';
import ColorsetPanel from './ColorsetPanel.js';
import ColorPickerPanel from './ColorPickerPanel.js';
+import DevicePanel from './DevicePanel.js';
import ModesPanel from './ModesPanel.js';
+import LedSelectPanel from './LedSelectPanel.js';
import Modal from './Modal.js';
import VortexPort from './VortexPort.js';
import WelcomePanel from './WelcomePanel.js';
@@ -36,7 +38,9 @@ export default class VortexEditor {
this.animationPanel = new AnimationPanel(this);
this.patternPanel = new PatternPanel(this);
this.colorsetPanel = new ColorsetPanel(this);
+ this.devicePanel = new DevicePanel(this);
this.modesPanel = new ModesPanel(this);
+ this.ledSelectPanel = new LedSelectPanel(this);
this.colorPickerPanel = new ColorPickerPanel(this);
this.updatePanel = new UpdatePanel(this);
this.chromalinkPanel = new ChromalinkPanel(this);
@@ -47,11 +51,57 @@ export default class VortexEditor {
this.animationPanel,
this.patternPanel,
this.colorsetPanel,
+ this.devicePanel,
this.modesPanel,
+ this.ledSelectPanel,
this.colorPickerPanel,
this.updatePanel,
this.chromalinkPanel
];
+ this.devices = {
+ 'None': {
+ image: 'public/images/none-logo-square-512.png',
+ icon: 'public/images/none-logo-square-64.png',
+ label: 'None',
+ ledCount: 1
+ },
+ 'Orbit': {
+ image: 'public/images/orbit.png',
+ icon: 'public/images/orbit-logo-square-64.png',
+ label: 'Orbit',
+ ledCount: 28
+ },
+ 'Handle': {
+ image: 'public/images/handle.png',
+ icon: 'public/images/handle-logo-square-64.png',
+ label: 'Handle',
+ ledCount: 3
+ },
+ 'Gloves': {
+ image: 'public/images/gloves.png',
+ icon: 'public/images/gloves-logo-square-64.png',
+ label: 'Gloves',
+ ledCount: 10
+ },
+ 'Chromadeck': {
+ image: 'public/images/chromadeck.png',
+ icon: 'public/images/chromadeck-logo-square-64.png',
+ label: 'Chromadeck',
+ ledCount: 20
+ },
+ 'Spark': {
+ image: 'public/images/spark.png',
+ icon: 'public/images/spark-logo-square-64.png',
+ label: 'Spark',
+ ledCount: 6
+ },
+ 'Duo': {
+ image: 'public/images/duo.png',
+ icon: 'public/images/duo-logo-square-64.png',
+ label: 'Duo',
+ ledCount: 2
+ }
+ };
}
initialize() {
@@ -73,7 +123,13 @@ export default class VortexEditor {
// Keydown event to show updatePanel
document.addEventListener('keydown', (event) => {
if (event.key === 'Insert') {
- this.checkVersion(this.vortexPort.name, this.vortexPort.version);
+ if (this.vortexPort.name.length > 0 && this.vortexPort.version.length > 0) {
+ this.checkVersion(this.vortexPort.name, this.vortexPort.version);
+ } else {
+ this.updatePanel.displayFirmwareUpdateInfo(this.devicePanel.selectedDevice,
+ '1.0.0', '1.0.1', 'https://vortex.community/downloads');
+ Notification.error("Need a device connection to use update panel");
+ }
this.updatePanel.show();
}
});
@@ -94,9 +150,11 @@ export default class VortexEditor {
async checkVersion(device, version) {
// the results are lowercased
- if (!device.length || device === 'None') {
- device = this.modesPanel.selectedDevice;
- if (!device.length || device === 'None') {
+ if (!device.length) {
+ console.log("Missing device for comparison, checking devicePanel...");
+ device = this.devicePanel.selectedDevice;
+ if (!device.length) {
+ console.log("Missing device for comparison, devicePanel and port device empty");
// not connected?
return;
}
@@ -105,6 +163,7 @@ export default class VortexEditor {
// this can happen if the update panel is forced open with Insert
if (!version) {
+ console.log("Missing version for comparison, using 1.0.0...");
version = '1.0.0';
}
@@ -120,11 +179,18 @@ export default class VortexEditor {
}
// Compare versions
- if (latestFirmwareVersions && latestFirmwareVersions[lowerDevice]) {
- const latestVersion = latestFirmwareVersions[lowerDevice].firmware.version;
- const downloadUrl = latestFirmwareVersions[lowerDevice].firmware.fileUrl;
- this.updatePanel.displayFirmwareUpdateInfo(device, version, latestVersion, downloadUrl);
+ if (!latestFirmwareVersions) {
+ console.log("Missing latest firmware version info");
+ return;
+ }
+ if (!latestFirmwareVersions[lowerDevice]) {
+ console.log(`Missing device info for device '${lowerDevice}'`);
+ return;
}
+ const latestVersion = latestFirmwareVersions[lowerDevice].firmware.version;
+ const downloadUrl = latestFirmwareVersions[lowerDevice].firmware.fileUrl;
+ console.log(`Comparing ${latestVersion} with ${downloadUrl} for ${device}...`);
+ this.updatePanel.displayFirmwareUpdateInfo(device, version, latestVersion, downloadUrl);
}
async pushToDevice() {
diff --git a/js/VortexPort.js b/js/VortexPort.js
index c214b89..0d96d4e 100644
--- a/js/VortexPort.js
+++ b/js/VortexPort.js
@@ -57,16 +57,19 @@ export default class VortexPort {
}
resetState() {
- // Reset properties to default state
- this.serialPort = null;
+ if (this.reader) {
+ try {
+ this.reader.releaseLock();
+ } catch (error) {
+ console.warn('Error releasing reader in resetState:', error);
+ } finally {
+ this.reader = null;
+ }
+ }
this.portActive = false;
this.name = '';
this.version = 0;
this.buildDate = '';
- if (this.reader) {
- this.reader.releaseLock();
- this.reader = null;
- }
this.isTransmitting = false; // Reset the transmission state on reset
this.hasUPDI = false;
// Further state reset logic if necessary
@@ -77,26 +80,32 @@ export default class VortexPort {
}
async requestDevice(callback) {
+ this.deviceCallback = callback;
try {
if (!this.serialPort) {
this.serialPort = await navigator.serial.requestPort();
if (!this.serialPort) {
throw new Error('Failed to open serial port');
}
- await this.serialPort.open({ baudRate: 9600 });
- }
- // is this necessary...? I don't remember why it's here
- await this.serialPort.setSignals({ dataTerminalReady: true });
- this.portActive = false;
- if (callback && typeof callback === 'function') {
- callback('waiting');
+ await this.serialPort.open({ baudRate: 115200 });
+ // is this necessary...? I don't remember why it's here
+ await this.serialPort.setSignals({ dataTerminalReady: true });
+ if (this.deviceCallback && typeof this.deviceCallback === 'function') {
+ this.deviceCallback('waiting');
+ }
}
- this.listenForGreeting(callback);
+ await this.beginConnection();
} catch (error) {
console.error('Error:', error);
}
}
+ async beginConnection(){
+ console.log("Beginning connection...");
+ this.portActive = false;
+ this.listenForGreeting();
+ }
+
async writeData(data) {
if (!this.serialPort || !this.serialPort.writable) {
console.error('Port is not writable.');
@@ -127,23 +136,26 @@ export default class VortexPort {
return true;
}
- listenForGreeting = async (callback) => {
+ listenForGreeting = async () => {
while (!this.portActive && !this.cancelListeningForGreeting) {
if (this.serialPort) {
try {
+ console.log("Listening for greeting...");
// Read data from the serial port
- const response = await this.readData();
+ const response = await this.readData(true);
if (!response) {
console.log("Error: Connection broken");
// broken connection
- break;
+ continue;
}
- let responseRegex = /^== Vortex Engine v(\d+\.\d+.\d+) '([\w\s]+)' \(built (.*)\) ==$/;
+ console.log("Matching: [" + response + "]...");
+
+ let responseRegex = /== Vortex Engine v(\d+\.\d+.\d+) '([\w\s]+)' \(built (.*)\) ==/;
let match = response.match(responseRegex);
if (!match) {
// TODO: removeme later! backwards compatibility for old connection string
- responseRegex = /^== Vortex Engine v(\d+\.\d+) '([\w\s]+)' \( built (.*)\) ==$/;
+ responseRegex = /== Vortex Engine v(\d+\.\d+) '([\w\s]+)' \( built (.*)\) ==/;
match = response.match(responseRegex);
}
@@ -176,10 +188,10 @@ export default class VortexPort {
this.portActive = true;
this.serialPort.addEventListener("disconnect", (event) => {
- this.disconnect(callback);
+ this.disconnect();
});
- if (callback && typeof callback === 'function') {
- callback('connect');
+ if (this.deviceCallback && typeof this.deviceCallback === 'function') {
+ this.deviceCallback('connect');
}
}
} catch (err) {
@@ -191,15 +203,22 @@ export default class VortexPort {
}
}
}
- await new Promise(resolve => setTimeout(resolve, 1000));
+ await new Promise(resolve => setTimeout(resolve, 300));
}
this.cancelListeningForGreeting = false;
}
- disconnect(callback = null) {
- this.resetState(); // Reset the state of the class
- if (callback && typeof callback === 'function') {
- callback('disconnect');
+ async restartConnecton() {
+ await this.beginConnection();
+ }
+
+ async disconnect() {
+ if (this.reader) {
+ await this.reader.cancel();
+ }
+ this.resetState();
+ if (this.deviceCallback && typeof this.deviceCallback === 'function') {
+ this.deviceCallback('disconnect');
}
}
@@ -211,10 +230,17 @@ export default class VortexPort {
// todo: implement async read cancel
}
- async readData() {
+ async readData(fullResponse) {
if (!this.serialPort || !this.serialPort.readable) {
return null;
}
+ if (this.accumulatedData.length > 0) {
+ // check the buffer first...
+ // Return any single byte
+ const singleByte = this.accumulatedData[0];
+ this.accumulatedData = this.accumulatedData.substring(1);
+ return singleByte;
+ }
if (this.reader) {
try {
this.reader.releaseLock();
@@ -238,15 +264,23 @@ export default class VortexPort {
const text = new TextDecoder().decode(value);
this.accumulatedData += text;
- // If it starts with '=' or '==', look for the end delimiter '=='
- if (this.accumulatedData.startsWith('=') || this.accumulatedData.startsWith('==')) {
- const endIndex = this.accumulatedData.indexOf('==', 2); // Search for '==' after the first one.
-
- if (endIndex >= 0) {
- const fullMessage = this.accumulatedData.substring(0, endIndex + 2).trim();
- this.accumulatedData = this.accumulatedData.substring(endIndex + 2); // Trim accumulatedData
- return fullMessage; // Return the full message
+ if (fullResponse) {
+ const responseRegex = /==.*==/;
+ const match = this.accumulatedData.match(responseRegex);
+ if (match) {
+ const result = this.accumulatedData;
+ this.accumulatedData = '';
+ return result;
}
+ // If it starts with '=' or '==', look for the end delimiter '=='
+ //if (this.accumulatedData.startsWith('=') || this.accumulatedData.startsWith('==')) {
+ // const endIndex = this.accumulatedData.indexOf('==', 2); // Search for '==' after the first one.
+
+ // if (endIndex >= 0) {
+ // const fullMessage = this.accumulatedData.substring(0, endIndex + 2).trim();
+ // this.accumulatedData = this.accumulatedData.substring(endIndex + 2); // Trim accumulatedData
+ // return fullMessage; // Return the full message
+ // }
} else {
// Return any single byte
const singleByte = this.accumulatedData[0];
@@ -564,7 +598,6 @@ export default class VortexPort {
if (headerStream.size() > 5) {
duoHeader.vBuild = headerData[5];
}
- console.log(JSON.stringify(headerData));
// construct a full version string
duoHeader.version = duoHeader.vMajor + '.' + duoHeader.vMinor + '.' + duoHeader.vBuild;
duoHeader.rawData = headerData;
@@ -770,6 +803,7 @@ export default class VortexPort {
this.reader.releaseLock();
}
this.reader = this.serialPort.readable.getReader();
+ console.log("readFromSerialPort(): Got reader");
let result = null;
try {
result = await this.reader.read();