diff --git a/main/background.ts b/main/background.ts index 391ed1e..ae3d2b9 100644 --- a/main/background.ts +++ b/main/background.ts @@ -36,7 +36,9 @@ let tray = null; height: 800, titleBarStyle: "hidden", webPreferences: { + // nodeIntegration: true, contextIsolation: false, + // nodeIntegrationInWorker: true, enableRemoteModule: true, webSecurity: false, backgroundThrottling: false @@ -67,6 +69,20 @@ let tray = null; await socket.stop() }) + // ipcMain.on('UDPSR-start', () => { + // socket = DgramAsPromised.createSocket("udp4") + // PORT = 11988 + // }) + + // ipcMain.on('UDPSR', async (event, arg) => { + // console.log(arg[1]) + // message = Buffer.from(arg[1]) + // await socket.send(message, 0, message.length, PORT, '239.0.0.1') + // }) + // ipcMain.on('UDPSR-stop', async () => { + // await socket.stop() + // }) + // tray = new Tray(nativeImage.createFromDataURL('data:image/x-icon;base64,AAABAAEAEBAAAAEAGACGAAAAFgAAAIlQTkcNChoKAAAADUlIRFIAAAAQAAAAEAgGAAAAH/P/YQAAAE1JREFUOI1j/P//PwOxgNGeAUMxE9G6cQCKDWAhpADZ2f8PMjBS3QW08QK20KaZC2gfC9hCnqouoNgARgY7zMxAyNlUdQHlXiAlO2MDAD63EVqNHAe0AAAAAElFTkSuQmCC')) diff --git a/main/helpers/create-window.ts b/main/helpers/create-window.ts index acc444c..9c9abe0 100644 --- a/main/helpers/create-window.ts +++ b/main/helpers/create-window.ts @@ -77,6 +77,7 @@ export default (windowName: string, options: BrowserWindowConstructorOptions): B titleBarStyle: "hidden", webPreferences: { nodeIntegration: true, + // nodeIntegrationInWorker: true, contextIsolation: false, enableRemoteModule: true, ...options.webPreferences, diff --git a/package.json b/package.json index ab6cf14..49d277d 100644 --- a/package.json +++ b/package.json @@ -2,12 +2,13 @@ "private": true, "name": "wled-manager", "description": "WLED Manager by Blade", - "version": "0.0.9", + "version": "0.1.0", "author": "YeonV aka Blade ", "main": "app/background.js", "scripts": { "dev": "nextron", "build": "nextron build", + "dist": "electron-builder -w zip -w portable -w nsis", "postinstall": "electron-builder install-app-deps" }, "dependencies": { @@ -18,6 +19,7 @@ "electron-serve": "^1.1.0", "electron-store": "^8.0.0", "react-colorful": "^5.5.0", + "react-gcolor-picker": "^1.2.4", "zustand": "^3.5.11" }, "devDependencies": { diff --git a/renderer/components/AddSegment.jsx b/renderer/components/AddSegment.jsx new file mode 100644 index 0000000..3c02e72 --- /dev/null +++ b/renderer/components/AddSegment.jsx @@ -0,0 +1,158 @@ +import * as React from 'react'; +import { + Card, + Box, + FormControl, + InputLabel, + Select, + Typography, + Button, + MenuItem, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, +} from '@material-ui/core'; +import useStore from '../store/store'; + +export default function AddSegment({ devices, handleSegments }) { + const [open, setOpen] = React.useState(false); + const [device, setDevice] = React.useState(''); + const [segment, setSegment] = React.useState(''); + + const handleChange = (event) => { + setDevice(event.target.value); + }; + const handleSegmentChange = (event) => { + setSegment(event.target.value); + }; + const handleClickOpen = () => { + setOpen(true); + }; + + const handleClose = () => { + setOpen(false); + }; + const handleAdd = () => { + handleSegments(JSON.parse(segment)); + setSegment(''); + setDevice(''); + setOpen(false); + }; + + // React.useEffect(() => { + // if (devices && devices.length && devices[0] && devices[0].name) { + // setDevice(devices[0].name) + // } + // }, [devices]) + return ( +
+ +
+ + Add + + +
+
+ + Add Segment to virtual + + + Merge several segments of different devices into one virtual strip + + + + Select a Device + + + {device && device !== '' && ( + + + Select a Segment + + + + )} + + + + + + + +
+ ); +} diff --git a/renderer/components/AddVirtual.jsx b/renderer/components/AddVirtual.jsx new file mode 100644 index 0000000..e806fcc --- /dev/null +++ b/renderer/components/AddVirtual.jsx @@ -0,0 +1,93 @@ +import * as React from 'react'; +import { + Card, + TextField, + Typography, + Button, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, +} from '@material-ui/core'; +import useStore from '../store/store'; + +export default function AddVirtual() { + const [open, setOpen] = React.useState(false); + const [virtual, setVirtual] = React.useState(''); + const addVirtual = useStore((state) => state.addVirtual); + const virtuals = useStore((state) => state.virtuals); + const handleChange = (event) => { + setVirtual(event.target.value); + }; + const handleClickOpen = () => { + setOpen(true); + }; + + const handleClose = () => { + setOpen(false); + }; + const handleAdd = () => { + addVirtual({ + name: virtual, + type: 'span', + pixel_count: 0, + }); + setOpen(false); + }; + + return ( +
+ +
+ + Add + + +
+
+ + Add Virtual Device + + + Merge several segments of different devices into one virtual strip + + + + + + + + +
+ ); +} diff --git a/renderer/components/AudioContainer.jsx b/renderer/components/AudioContainer.jsx index 93d92a9..e7b8a73 100644 --- a/renderer/components/AudioContainer.jsx +++ b/renderer/components/AudioContainer.jsx @@ -1,6 +1,15 @@ import React, { useState, useRef, useEffect } from 'react'; import Visualizer from './Visualizer'; -// import aubio from 'aubiojs'; +// const aubio = require('../../../aubiojs') +// import Essentia from './essentia.js-core.es.js'; +// // import essentia-wasm-module +// import { EssentiaWASM } from './essentia-wasm.es.js'; + +// create essentia object with all the methods to run various algorithms +// by loading the wasm back-end. +// here, `EssentiaModule` is an emscripten module object imported to the global namespace + + const AudioDataContainer = ({ audioDeviceId, fft, bandCount, drawerBottomHeight }) => { const [frequencyBandArray] = useState([...Array(bandCount).keys()]); @@ -8,6 +17,7 @@ const AudioDataContainer = ({ audioDeviceId, fft, bandCount, drawerBottomHeight const audioContext = useRef(new AudioContext()); const theStream = useRef(null); const theGain = useRef(null); + // const theTempo = useRef(null); // const theAubio = useRef(null); @@ -20,6 +30,17 @@ const AudioDataContainer = ({ audioDeviceId, fft, bandCount, drawerBottomHeight const source = audioContext.current.createMediaStreamSource(stream); const analyser = audioContext.current.createAnalyser(); + // const audioBuffer = audioContext.current.createBuffer(1, 1024, audioContext.current.sampleRate); + // let esPkg = require('essentia.js'); + + // let essentia = new esPkg.Essentia(esPkg.EssentiaWASM); + // const inputSignalVector = essentia.arrayToVector(audioBuffer.getChannelData(0)); + // let outputRG = essentia.ReplayGain(inputSignalVector, // input + // 44100); // sampleRate (parameter optional) + + + // console.log(outputRG.replayGain); + // const scriptProcessor = audioContext.current.createScriptProcessor( // 1024, // 1, @@ -68,6 +89,12 @@ const AudioDataContainer = ({ audioDeviceId, fft, bandCount, drawerBottomHeight if (!audioData.current) { return; } + // const worker = new Worker('./audioWorker.js', { type: 'module' }); + // await worker.postMessage(audioData.current); + + // worker.onmessage = ({ data }) => { + // console.log(`page got message: ${data}`); + // }; const bufferLength = audioData.current.frequencyBinCount; const amplitudeArray = new Uint8Array(bufferLength); @@ -112,10 +139,17 @@ const AudioDataContainer = ({ audioDeviceId, fft, bandCount, drawerBottomHeight // const { Tempo } = await aubio(); // const tempo = new Tempo(4096, 1024, audioContext.current.sampleRate); // theTempo.current = tempo; - // console.log("YZ2", theTempo.current) // }; // init(); // }, []); + + // useEffect(() => { + // console.log("YZ2", theTempo.current) + // const audioBuffer = audioContext.current.createBuffer(1, 1024, audioContext.current.sampleRate); + // theTempo.current && theTempo.current.do(audioBuffer); + // const bpm = theTempo.current && theTempo.current.getBpm(); + // console.log(bpm) + // }, [theTempo.current, audioContext.current]); return (
{ +const ColorPicker = ({ color, onChange, disabled, label, gradient,setDrawerBottomHeight }) => { const popover = useRef(); const classes = useStyles() const [isOpen, toggle] = useState(false); @@ -46,7 +46,10 @@ const ColorPicker = ({ color, onChange, disabled, label, gradient }) => { cursor: 'pointer', background: gradient ? color : `rgb(${color.r}, ${color.g}, ${color.b})`, }} - onClick={() => toggle(true)}> + onClick={() => { + setDrawerBottomHeight(800) + toggle(true) + }}> state.device) + const devices = useStore(state => state.devices) const audioDevice = useStore(state => state.audioDevice) const setAudioDevice = useStore(state => state.setAudioDevice) const audioDevices = useStore(state => state.audioDevices) @@ -102,7 +103,11 @@ export default function Visualizer({ const [flipped, setFlipped] = useState(false) const [effect, setEffect] = useState("Power (Left FB)") const [volume, setVolume] = useState(0) - const [innerVolume, setInnerVolume] = useState(0) + const [innerVolume, setInnerVolume] = useState(0) + const virtualView = useStore(state => state.virtualView) + const virtual = useStore(state => state.virtual) + const setDrawerBottomHeight = useStore(state => state.setDrawerBottomHeight) + const settingColor = (clr) => { setColor(clr) @@ -173,7 +178,7 @@ export default function Visualizer({ type: effect, config: { ampValues: amplitudeValues.current, - pixel_count: device.pixel_count, + pixel_count: virtualView ? virtual.pixel_count : device.pixel_count, color, bgColor, gcolor, @@ -199,9 +204,17 @@ export default function Visualizer({ // ledData && ledData.length > 1 && ipcRenderer.send('UDPSR', [{ ip: device.ip }, // `${header}${myVals}${sampleAvc}${sample}${sampleAvg}${samplePeak}${fftResult}${FFT_Magnitude}${FFT_MajorPeak}`]) - ledData && ledData.length > 1 && ipcRenderer.send('UDP', [{ ip: device.ip }, flipped - ? [...ledDataPrefix, ...ledData.reverse().flat()] - : [...ledDataPrefix, ...ledData.flat()]]) + if (virtualView) { + virtual.seg && virtual.seg.length && virtual.seg.map(s=>{ + ledData && ledData.length > 1 && ipcRenderer.send('UDP', [{ ip: devices.find(d=>d.name === s.device).ip }, flipped + ? [...ledDataPrefix, ...ledData.reverse().flat()].splice(s.seg[0]*3, s.seg[1]*3) + : [...ledDataPrefix, ...ledData.flat()].splice(s.seg[0]*3, s.seg[1]*3)]) + }) + } else { + ledData && ledData.length > 1 && ipcRenderer.send('UDP', [{ ip: device.ip }, flipped + ? [...ledDataPrefix, ...ledData.reverse().flat()] + : [...ledDataPrefix, ...ledData.flat()]]) + } // ledData && ledData.length > 1 && ipcRenderer.send('UDP', [{ ip: device.ip }, (amplitudeValues.current[activeFb] - volume * 2.55) > 0 // ? [...ledDataPrefix, ...tmp.reverse().flat()] @@ -230,7 +243,7 @@ export default function Visualizer({ function handleStopButtonClick() { setPlaying(false) ipcRenderer.send('UDP-stop') - console.log(performance.now() - timeStarted.current) + // console.log(performance.now() - timeStarted.current) // ipcRenderer.send('UDPSR-stop') if (frequencyBandArray.length > 0) { let domElements = frequencyBandArray.map((num) => @@ -373,11 +386,11 @@ export default function Visualizer({
{effect.indexOf("radient") === -1 && - } + } {effect !== "BladeWave (Range)" && effect.indexOf("radient") === -1 && - + } - {effect.indexOf("radient") > -1 && } + {effect.indexOf("radient") > -1 && }
@@ -386,7 +399,6 @@ export default function Visualizer({ setVolume(v)} onChangeCommitted={settingVolume} min={0} diff --git a/renderer/components/audioWorker.js b/renderer/components/audioWorker.js new file mode 100644 index 0000000..c15d0f1 --- /dev/null +++ b/renderer/components/audioWorker.js @@ -0,0 +1,19 @@ +const essentia = require('essentia.js'); + +addEventListener('message', e => { + console.log(e.data); + + let audio = e.data.channelData[0]; + let inputSignalVector = essentia.arrayToVector(audio); + let key = essentia.KeyExtractor(inputSignalVector); + let bpm = essentia.RhythmExtractor(inputSignalVector); + + let data = { + bpm : bpm.bpm, + key : key.key, + scale : key.scale + } + + postMessage(data); + +}); \ No newline at end of file diff --git a/renderer/effects/effects.js b/renderer/effects/effects.js index 7829d8c..b385566 100644 --- a/renderer/effects/effects.js +++ b/renderer/effects/effects.js @@ -1,5 +1,6 @@ import GradientRolling from "./gradientRolling"; import GradientAudio from "./gradientsAudio"; +import GradientsAudioInv from "./gradientsAudioInv"; import GradientStatic from "./gradientStatic"; import Power from "./power"; import Wavelength from "./wavelength"; @@ -12,6 +13,7 @@ export const effects = [ 'GradientStatic', 'GradientRolling', 'GradientAudio', + 'GradientsAudioInv', ] const Effect = ({ type, config }) => { @@ -33,6 +35,9 @@ const Effect = ({ type, config }) => { case 'GradientAudio': return GradientAudio(config) + + case 'GradientsAudioInv': + return GradientsAudioInv(config) default: return Power(config) diff --git a/renderer/effects/gradientsAudio.js b/renderer/effects/gradientsAudio.js index 13480c8..477eccc 100644 --- a/renderer/effects/gradientsAudio.js +++ b/renderer/effects/gradientsAudio.js @@ -1,30 +1,28 @@ import { getMultipleGradientSteps } from "./utils" -const GradientAudio = ({ ampValues, pixel_count, color, bgColor, activeFb, volume, timeStarted, gcolor, lastShift, lastAudio }) => { - let tmp = getMultipleGradientSteps(gcolor.match(/rgb\([^()]*\)|#\w+/g).map(c=>c.match(/\d+/g)), pixel_count) - let audio = (ampValues[activeFb] - volume * 2.55) > 0 - let speed = audio ? 64 : 512 - - // ToDo: Fix: speedChange produces jump in shift - const shift = parseInt(((performance.now() - timeStarted.current)/speed) )% pixel_count +let shift = 0; - if (lastShift.current && lastShift.current !== shift) { - const slicePreA = tmp.slice(0,lastShift.current) - const slicePreB = tmp.slice(lastShift.current) - tmp = [...slicePreB, ...slicePreA] +export const shifting = (pixel_count) => { + if (shift >= pixel_count) { + shift = 0; + } else { + shift++ } - - if (audio !== lastAudio.current) { +} + +const GradientAudio = ({ ampValues, pixel_count, color, bgColor, activeFb, volume, timeStarted, gcolor, lastShift, lastAudio }) => { + let tmp = getMultipleGradientSteps(gcolor.match(/rgb\([^()]*\)|#\w+/g).map(c=>c.match(/\d+/g)), pixel_count) + let audio = (ampValues[activeFb] - volume * 2.55) > 0 + let speed = audio ? 0 : 5 + + if (performance.now() - timeStarted.current >= 16+speed*9.84) { + shifting(pixel_count) timeStarted.current = performance.now() - lastAudio.current = audio - lastShift.current = shift - } else { - lastShift.current = 0 - } - console.log(shift) + } + const sliceA = tmp.slice(0,shift) const sliceB = tmp.slice(shift) - + return [...sliceB, ...sliceA] } diff --git a/renderer/effects/gradientsAudioInv.js b/renderer/effects/gradientsAudioInv.js new file mode 100644 index 0000000..d1ece0b --- /dev/null +++ b/renderer/effects/gradientsAudioInv.js @@ -0,0 +1,29 @@ +import { getMultipleGradientStepsInverted } from "./utils" + +let shift = 0; + +export const shifting = (pixel_count) => { + if (shift >= pixel_count) { + shift = 0; + } else { + shift++ + } +} + +const GradientAudioInv = ({ ampValues, pixel_count, color, bgColor, activeFb, volume, timeStarted, gcolor, lastShift, lastAudio }) => { + let tmp = getMultipleGradientStepsInverted(gcolor.match(/rgb\([^()]*\)|#\w+/g).map(c=>c.match(/\d+/g)), pixel_count) + let audio = (ampValues[activeFb] - volume * 2.55) > 0 + let speed = audio ? 0 : 5 + + if (performance.now() - timeStarted.current >= 16+speed*9.84) { + shifting(pixel_count) + timeStarted.current = performance.now() + } + + const sliceA = tmp.slice(0,shift) + const sliceB = tmp.slice(shift) + + return [...sliceB, ...sliceA] +} + +export default GradientAudioInv \ No newline at end of file diff --git a/renderer/effects/utils.js b/renderer/effects/utils.js index 8180fa4..0d1c85d 100644 --- a/renderer/effects/utils.js +++ b/renderer/effects/utils.js @@ -14,10 +14,26 @@ export const getGradientSteps = (colorStart,colorEnd,colorCount) => { export const getMultipleGradientSteps = (colors, count) => { const output = [] - for (let i = 0; i < colors.length - 1; i++) { + for (let i = 0; i < colors.length - 2; i++) { const gradient = getGradientSteps( + colors[i+1], colors[i], - colors[i+1], i === colors.length - 1 + i === colors.length - 1 + ? count - ((colors.length - 2) * Math.floor(count / (colors.length - 1))) + : Math.floor(count / (colors.length - 1)) + ) + output.push(gradient) + } + return output.flat() +} + +export const getMultipleGradientStepsInverted = (colors, count) => { + const output = [] + for (let i = 0; i < colors.length - 2; i++) { + const gradient = getGradientSteps( + colors[i], + colors[i+1], + i === colors.length - 1 ? count - ((colors.length - 2) * Math.floor(count / (colors.length - 1))) : Math.floor(count / (colors.length - 1)) ) diff --git a/renderer/pages/home.jsx b/renderer/pages/home.jsx index 69eeb5f..e3f2e41 100644 --- a/renderer/pages/home.jsx +++ b/renderer/pages/home.jsx @@ -63,18 +63,19 @@ function Home() { setSuccess(false) setError(false) setLoading(true) - fetch(`http://${newIp}/json/info`) + fetch(`http://${newIp}/json`) .then(r => r.json()) .then((res) => { - if (res.name) { + if (res.info.name) { setSuccess(true) window && window.localStorage.setItem("wled-manager-ip", newIp) setDevice({ - "name": res.name, - "type": res.arch === "esp8266" ? 82 : 32, + "name": res.info.name, + "type": res.info.arch === "esp8266" ? 82 : 32, "ip": newIp, - "vid": res.vid, - "pixel_count": res.leds.count + "vid": res.info.vid, + "pixel_count": res.info.leds.count, + "seg": res.state.seg }) if (zeroconf) { diff --git a/renderer/pages/yz.jsx b/renderer/pages/yz.jsx index 512994c..8a6d0dd 100644 --- a/renderer/pages/yz.jsx +++ b/renderer/pages/yz.jsx @@ -10,6 +10,8 @@ import useLeftBarStyles from '../styles/yz.styles'; import { template } from '../components/MenuTemplate'; import AudioDataContainer from '../components/AudioContainer'; import useStore from '../store/store'; +import AddVirtual from '../components/AddVirtual'; +import AddSegment from '../components/AddSegment'; const LeftBar = () => { if (typeof window === 'undefined') { @@ -28,6 +30,10 @@ const LeftBar = () => { const setDevices = useStore(state => state.setDevices) const device = useStore(state => state.device) const setDevice = useStore(state => state.setDevice) + const virtuals = useStore(state => state.virtuals) + const setVirtuals = useStore(state => state.setVirtuals) + const virtual = useStore(state => state.virtual) + const setVirtual = useStore(state => state.setVirtual) const audioDevice = useStore(state => state.audioDevice) const setDrawerBottomHeight = useStore(state => state.setDrawerBottomHeight) const drawerWidth = useStore(state => state.drawerWidth) @@ -45,6 +51,41 @@ const LeftBar = () => { const [singleMode, setSingleMode] = useState(router.query.singlemode || false) const [error, setError] = useState("") + const virtualView = useStore(state => state.virtualView) + const setVirtualView = useStore(state => state.setVirtualView) + const removeVirtual = useStore(state => state.removeVirtual) + const addVirtual = useStore(state => state.addVirtual) + + const openVirtual = (virtual) => { + setVirtualView(virtual.name) + } + + const handleRemoveVirtual = (v) => { + if (v.name === virtualView) { + setVirtualView(false) + } + removeVirtual(v) + }; + const handleSegments = (segs) => { + addVirtual({ + name: virtual.name, + type: 'span', + pixel_count: 0, + seg: [...virtual.seg || [], segs] + }); + }; + const removeSeg = (i) => { + const segs = [ ...virtual.seg ] + segs.splice(i, 1) + + addVirtual({ + name: virtual.name, + type: 'span', + pixel_count: 0, + seg: [...segs ] + }); + }; + useEffect(() => { const { Menu } = remote; const customTitleBar = require('custom-electron-titlebar'); @@ -61,10 +102,31 @@ const LeftBar = () => { }; }, []); + useEffect(() => { + setVirtual(virtuals.find(v=>v.name === virtualView)) + }, [virtualView, virtuals]) + + + useEffect(() => { + virtuals.map(v=>{ + if (v.seg && v.seg.length) { + v.pixel_count = v.seg.map(s=>(s.seg && s.seg.length) ? s.seg[1] - s.seg[0] : 0).reduce((a,b)=>a+b) + } + return v + }) + }, [virtuals]) + useEffect(() => { ipcRenderer.send('resize-me-please', [1024, 1080]) }, []) + // useEffect(() => { + // const virt = virtuals.find(v => v.name === virtualView) + // if (virt) { + // setVirtual(virt) + // } + // }, [virtuals, virtualView]) + useEffect(() => { if (router.query && router.query.zeroconf) { setIsZeroConf(true) @@ -109,25 +171,27 @@ const LeftBar = () => { if (service.referer && service.referer.address) { if ((!combNodes.filter(n => n.ip === service.referer.address).length > 0) || (!devices.filter(n => n.ip === service.referer.address).length)) { console.log("wled found:", service.name) - await fetch(`http://${service.referer.address}/json/info`) + await fetch(`http://${service.referer.address}/json`) .then(r => r.json()) .then((re) => { if (!combNodes.filter(n => n.ip === service.referer.address).length > 0) { setCombNodes((comb) => [...comb, { "name": service.name, - "type": re.arch === "esp8266" ? 82 : 32, + "type": re.info.arch === "esp8266" ? 82 : 32, "ip": service.referer.address, - "vid": re.vid, - "pixel_count": re.leds.count + "vid": re.info.vid, + "pixel_count": re.info.leds.count, + "seg": re.state.seg }]) } if (!devices.filter(n => n.ip === service.referer.address).length) { setDevices([...devices, { "name": service.name, - "type": re.arch === "esp8266" ? 82 : 32, + "type": re.info.arch === "esp8266" ? 82 : 32, "ip": service.referer.address, - "vid": re.vid, - "pixel_count": re.leds.count + "vid": re.info.vid, + "pixel_count": re.info.leds.count, + "seg": re.state.seg }]) } }) @@ -147,25 +211,27 @@ const LeftBar = () => { res.nodes.forEach((node) => { if ((!combNodes.filter(n => n.ip === node.ip).length > 0) || (!devices.filter(n => n.ip === node.ip).length)) { console.log("wled found:", node) - fetch(`http://${node.ip}/json/info`) + fetch(`http://${node.ip}/json`) .then(r => r.json()) .then((re) => { if (!combNodes.filter(n => n.ip === node.ip).length > 0) { setCombNodes((comb) => [...comb, { "name": node.name, - "type": re.arch === "esp8266" ? 82 : 32, + "type": re.info.arch === "esp8266" ? 82 : 32, "ip": node.ip, - "vid": re.vid, - "pixel_count": re.leds.count + "vid": re.info.vid, + "pixel_count": re.info.leds.count, + "seg": re.state.seg }]) } if (!devices.filter(n => n.ip === node.ip).length) { setDevices([...devices, { "name": node.name, - "type": re.arch === "esp8266" ? 82 : 32, + "type": re.info.arch === "esp8266" ? 82 : 32, "ip": node.ip, - "vid": re.vid, - "pixel_count": re.leds.count + "vid": re.info.vid, + "pixel_count": re.info.leds.count, + "seg": re.state.seg }]) } }) @@ -188,7 +254,8 @@ const LeftBar = () => { } } }, []) - + + return (<> WLED Manager - by Blade @@ -226,7 +293,8 @@ const LeftBar = () => { { setIframe(combNodes[i].ip) setDevice(combNodes[i]) - }} style={{ cursor: 'pointer', margin: '0.5rem', padding: '0.5rem 0.25rem 0.5rem 0.5rem', background: combNodes[i].ip === iframe ? '#404040' : '#202020' }}> + setVirtualView(false) + }} style={{ cursor: 'pointer', margin: '0.5rem', padding: '0.5rem 0.25rem 0.5rem 0.5rem', background: (combNodes[i].ip === iframe && !virtualView) ? '#404040' : '#202020' }}>
{combNodes[i].name} @@ -249,26 +317,27 @@ const LeftBar = () => {
- -
- - Dummy Virtual 1 - - -
-
- + {virtuals.length > 0 && virtuals.map((v, i) => ( + + handleRemoveVirtual(v)} onClick={() => { + openVirtual(v) + }} style={{ cursor: 'pointer', margin: '0.5rem', padding: '0.5rem 0.25rem 0.5rem 0.5rem', background: virtualView === v.name ? '#404040' : '#202020' }}>
- - Dummy Virtual 2 + + {virtuals[i].name} - +
+ + +
+ ))} + {/*