2022-03-10 22:36:09 +01:00
//page js
2023-06-04 18:40:29 +02:00
var loc = false , locip , locproto = "http:" ;
2023-09-10 18:52:14 +02:00
var isOn = false , nlA = false , isLv = false , isInfo = false , isNodes = false , syncSend = false /*, syncTglRecv = true*/ ;
2022-03-10 22:36:09 +01:00
var hasWhite = false , hasRGB = false , hasCCT = false ;
var nlDur = 60 , nlTar = 0 ;
var nlMode = false ;
2023-02-25 09:41:15 +01:00
var segLmax = 0 ; // size (in pixels) of largest selected segment
2022-07-23 22:00:19 +02:00
var selectedFx = 0 ;
2022-03-10 22:36:09 +01:00
var selectedPal = 0 ;
var csel = 0 ; // selected color slot (0-2)
2022-07-23 22:00:19 +02:00
var currentPreset = - 1 ;
2022-03-10 22:36:09 +01:00
var lastUpdate = 0 ;
var segCount = 0 , ledCount = 0 , lowestUnused = 0 , maxSeg = 0 , lSeg = 0 ;
var pcMode = false , pcModeA = false , lastw = 0 , wW ;
var tr = 7 ;
var d = document ;
var palettesData ;
var fxdata = [ ] ;
var pJson = { } , eJson = { } , lJson = { } ;
2022-11-30 19:34:32 +01:00
var plJson = { } ; // array of playlists
2022-03-10 22:36:09 +01:00
var pN = "" , pI = 0 , pNum = 0 ;
var pmt = 1 , pmtLS = 0 , pmtLast = 0 ;
var lastinfo = { } ;
2022-05-08 10:50:48 +02:00
var isM = false , mw = 0 , mh = 0 ;
2023-06-27 01:51:24 +02:00
var ws , cpick , ranges , wsRpt = 0 ;
2022-03-10 22:36:09 +01:00
var cfg = {
2023-10-31 10:21:00 +01:00
theme : { base : "dark" , bg : { url : "" , rnd : false , rndGrayscale : false , rndBlur : false } , alpha : { bg : 0.6 , tab : 0.8 } , color : { bg : "" } } ,
2022-03-10 22:36:09 +01:00
comp : { colors : { picker : true , rgb : false , quick : true , hex : false } ,
2023-10-20 21:32:39 +02:00
labels : true , pcmbot : false , pid : true , seglen : false , segpwr : false , segexp : false ,
2023-11-04 09:39:08 +01:00
css : true , hdays : false , fxdef : true , on : 0 , off : 0 , idsort : false }
2022-03-10 22:36:09 +01:00
} ;
var hol = [
[ 0 , 11 , 24 , 4 , "https://aircoookie.github.io/xmas.png" ] , // christmas
[ 0 , 2 , 17 , 1 , "https://images.alphacoders.com/491/491123.jpg" ] , // st. Patrick's day
2022-04-19 17:16:07 +02:00
[ 2025 , 3 , 20 , 2 , "https://aircoookie.github.io/easter.png" ] ,
[ 2024 , 2 , 31 , 2 , "https://aircoookie.github.io/easter.png" ] ,
2023-10-19 10:41:49 +02:00
[ 0 , 6 , 4 , 1 , "https://images.alphacoders.com/516/516792.jpg" ] , // 4th of July
[ 0 , 0 , 1 , 1 , "https://images.alphacoders.com/119/1198800.jpg" ] // new year
2022-03-10 22:36:09 +01:00
] ;
function handleVisibilityChange ( ) { if ( ! d . hidden && new Date ( ) - lastUpdate > 3000 ) requestJson ( ) ; }
function sCol ( na , col ) { d . documentElement . style . setProperty ( na , col ) ; }
function gId ( c ) { return d . getElementById ( c ) ; }
function gEBCN ( c ) { return d . getElementsByClassName ( c ) ; }
function isEmpty ( o ) { return Object . keys ( o ) . length === 0 ; }
function isObj ( i ) { return ( i && typeof i === 'object' && ! Array . isArray ( i ) ) ; }
2022-08-01 17:32:40 +02:00
function isNumeric ( n ) { return ! isNaN ( parseFloat ( n ) ) && isFinite ( n ) ; }
2022-03-10 22:36:09 +01:00
// returns true if dataset R, G & B values are 0
function isRgbBlack ( a ) { return ( parseInt ( a . r ) == 0 && parseInt ( a . g ) == 0 && parseInt ( a . b ) == 0 ) ; }
// returns RGB color from a given dataset
function rgbStr ( a ) { return "rgb(" + a . r + "," + a . g + "," + a . b + ")" ; }
// brightness approximation for selecting white as text color if background bri < 127, and black if higher
function rgbBri ( a ) { return 0.2126 * parseInt ( a . r ) + 0.7152 * parseInt ( a . g ) + 0.0722 * parseInt ( a . b ) ; }
// sets background of color slot selectors
function setCSL ( cs )
{
let w = cs . dataset . w ? parseInt ( cs . dataset . w ) : 0 ;
let hasShadow = getComputedStyle ( cs ) . textShadow !== "none" ;
if ( hasRGB && ! isRgbBlack ( cs . dataset ) ) {
if ( ! hasShadow ) cs . style . color = rgbBri ( cs . dataset ) > 127 ? "#000" : "#fff" ; // if text has no CSS "shadow"
2023-03-02 18:21:55 +01:00
cs . style . background = ( hasWhite && w > 0 ) ? ` linear-gradient(180deg, ${ rgbStr ( cs . dataset ) } 30%, rgb( ${ w } , ${ w } , ${ w } )) ` : rgbStr ( cs . dataset ) ;
2022-03-10 22:36:09 +01:00
} else {
2023-03-02 18:21:55 +01:00
if ( hasRGB && ! hasWhite ) w = 0 ;
2022-03-10 22:36:09 +01:00
cs . style . background = ` rgb( ${ w } , ${ w } , ${ w } ) ` ;
if ( ! hasShadow ) cs . style . color = w > 127 ? "#000" : "#fff" ;
}
}
function applyCfg ( )
{
cTheme ( cfg . theme . base === "light" ) ;
2023-10-20 21:32:39 +02:00
gId ( "Colors" ) . style . paddingTop = cfg . comp . colors . picker ? "0" : "28px" ;
2022-03-10 22:36:09 +01:00
var bg = cfg . theme . color . bg ;
if ( bg ) sCol ( '--c-1' , bg ) ;
var l = cfg . comp . labels ;
2022-03-14 20:22:20 +01:00
sCol ( '--tbp' , l ? "14px 14px 10px 14px" : "10px 22px 4px 22px" ) ;
sCol ( '--bbp' , l ? "9px 0 7px 0" : "10px 0 4px 0" ) ;
sCol ( '--bhd' , l ? "block" : "none" ) ; // show/hide labels
sCol ( '--bmt' , l ? "0px" : "5px" ) ;
2022-03-10 22:36:09 +01:00
sCol ( '--t-b' , cfg . theme . alpha . tab ) ;
2022-03-14 20:22:20 +01:00
sCol ( '--sgp' , ! cfg . comp . segpwr ? "block" : "none" ) ; // show/hide segment power
2022-03-10 22:36:09 +01:00
size ( ) ;
localStorage . setItem ( 'wledUiCfg' , JSON . stringify ( cfg ) ) ;
2022-03-13 14:04:29 +01:00
if ( lastinfo . leds ) updateUI ( ) ; // update component visibility
2022-03-10 22:36:09 +01:00
}
function tglHex ( )
{
cfg . comp . colors . hex = ! cfg . comp . colors . hex ;
applyCfg ( ) ;
}
function tglTheme ( )
{
cfg . theme . base = ( cfg . theme . base === "light" ) ? "dark" : "light" ;
applyCfg ( ) ;
}
function tglLabels ( )
{
cfg . comp . labels = ! cfg . comp . labels ;
applyCfg ( ) ;
}
2022-03-13 14:04:29 +01:00
function tglRgb ( )
{
cfg . comp . colors . rgb = ! cfg . comp . colors . rgb ;
2023-10-16 18:34:51 +02:00
cfg . comp . colors . picker = ! cfg . comp . colors . picker ;
2022-03-13 14:04:29 +01:00
applyCfg ( ) ;
}
2022-03-10 22:36:09 +01:00
function cTheme ( light ) {
if ( light ) {
sCol ( '--c-1' , '#eee' ) ;
sCol ( '--c-f' , '#000' ) ;
sCol ( '--c-2' , '#ddd' ) ;
sCol ( '--c-3' , '#bbb' ) ;
sCol ( '--c-4' , '#aaa' ) ;
sCol ( '--c-5' , '#999' ) ;
sCol ( '--c-6' , '#999' ) ;
sCol ( '--c-8' , '#888' ) ;
sCol ( '--c-b' , '#444' ) ;
sCol ( '--c-c' , '#333' ) ;
sCol ( '--c-e' , '#111' ) ;
sCol ( '--c-d' , '#222' ) ;
2022-06-20 22:17:01 +02:00
sCol ( '--c-r' , '#a21' ) ;
sCol ( '--c-g' , '#2a1' ) ;
2022-03-10 22:36:09 +01:00
sCol ( '--c-l' , '#26c' ) ;
sCol ( '--c-o' , 'rgba(204, 204, 204, 0.9)' ) ;
sCol ( '--c-sb' , '#0003' ) ; sCol ( '--c-sbh' , '#0006' ) ;
sCol ( '--c-tb' , 'rgba(204, 204, 204, var(--t-b))' ) ;
sCol ( '--c-tba' , 'rgba(170, 170, 170, var(--t-b))' ) ;
sCol ( '--c-tbh' , 'rgba(204, 204, 204, var(--t-b))' ) ;
gId ( 'imgw' ) . style . filter = "invert(0.8)" ;
} else {
sCol ( '--c-1' , '#111' ) ;
sCol ( '--c-f' , '#fff' ) ;
sCol ( '--c-2' , '#222' ) ;
sCol ( '--c-3' , '#333' ) ;
sCol ( '--c-4' , '#444' ) ;
sCol ( '--c-5' , '#555' ) ;
sCol ( '--c-6' , '#666' ) ;
sCol ( '--c-8' , '#888' ) ;
sCol ( '--c-b' , '#bbb' ) ;
sCol ( '--c-c' , '#ccc' ) ;
sCol ( '--c-e' , '#eee' ) ;
sCol ( '--c-d' , '#ddd' ) ;
sCol ( '--c-r' , '#e42' ) ;
sCol ( '--c-g' , '#4e2' ) ;
sCol ( '--c-l' , '#48a' ) ;
sCol ( '--c-o' , 'rgba(34, 34, 34, 0.9)' ) ;
sCol ( '--c-sb' , '#fff3' ) ; sCol ( '--c-sbh' , '#fff5' ) ;
sCol ( '--c-tb' , 'rgba(34, 34, 34, var(--t-b))' ) ;
sCol ( '--c-tba' , 'rgba(102, 102, 102, var(--t-b))' ) ;
sCol ( '--c-tbh' , 'rgba(51, 51, 51, var(--t-b))' ) ;
gId ( 'imgw' ) . style . filter = "unset" ;
}
}
function loadBg ( iUrl )
{
let bg = gId ( 'bg' ) ;
let img = d . createElement ( "img" ) ;
img . src = iUrl ;
if ( iUrl == "" || iUrl === "https://picsum.photos/1920/1080" ) {
var today = new Date ( ) ;
2022-07-27 17:00:55 +02:00
for ( var h of ( hol || [ ] ) ) {
var yr = h [ 0 ] == 0 ? today . getFullYear ( ) : h [ 0 ] ;
var hs = new Date ( yr , h [ 1 ] , h [ 2 ] ) ;
2022-03-10 22:36:09 +01:00
var he = new Date ( hs ) ;
2022-07-27 17:00:55 +02:00
he . setDate ( he . getDate ( ) + h [ 3 ] ) ;
if ( today >= hs && today <= he ) img . src = h [ 4 ] ;
2022-03-10 22:36:09 +01:00
}
}
img . addEventListener ( 'load' , ( e ) => {
var a = parseFloat ( cfg . theme . alpha . bg ) ;
if ( isNaN ( a ) ) a = 0.6 ;
bg . style . opacity = a ;
bg . style . backgroundImage = ` url( ${ img . src } ) ` ;
img = null ;
2022-10-25 03:27:16 +02:00
gId ( 'namelabel' ) . style . color = "var(--c-c)" ; // improve namelabel legibility on background image
2022-03-10 22:36:09 +01:00
} ) ;
}
2023-04-23 21:36:19 +02:00
function loadSkinCSS ( cId )
{
2022-03-10 22:36:09 +01:00
if ( ! gId ( cId ) ) // check if element exists
{
2023-04-23 21:36:19 +02:00
var h = d . getElementsByTagName ( 'head' ) [ 0 ] ;
var l = d . createElement ( 'link' ) ;
l . id = cId ;
l . rel = 'stylesheet' ;
l . type = 'text/css' ;
2023-06-04 18:40:29 +02:00
l . href = getURL ( '/skin.css' ) ;
2023-04-23 21:36:19 +02:00
l . media = 'all' ;
h . appendChild ( l ) ;
2022-03-10 22:36:09 +01:00
}
}
2023-06-04 18:40:29 +02:00
function getURL ( path ) {
return ( loc ? locproto + "//" + locip : "" ) + path ;
}
2022-03-10 22:36:09 +01:00
function onLoad ( )
{
2023-06-07 21:37:54 +02:00
let l = window . location ;
if ( l . protocol == "file:" ) {
2022-03-10 22:36:09 +01:00
loc = true ;
locip = localStorage . getItem ( 'locIp' ) ;
if ( ! locip ) {
locip = prompt ( "File Mode. Please enter WLED IP!" ) ;
localStorage . setItem ( 'locIp' , locip ) ;
}
2023-06-04 18:40:29 +02:00
} else {
// detect reverse proxy and/or HTTPS
let pathn = l . pathname ;
let paths = pathn . slice ( 1 , pathn . endsWith ( '/' ) ? - 1 : undefined ) . split ( "/" ) ;
locproto = l . protocol ;
2023-06-07 21:37:54 +02:00
locip = l . hostname + ( l . port ? ":" + l . port : "" ) ;
if ( paths . length > 0 && paths [ 0 ] !== "" ) {
2023-06-04 18:40:29 +02:00
loc = true ;
2023-11-08 18:40:52 +01:00
locip += "/" + paths . join ( '/' ) ;
2023-06-04 18:40:29 +02:00
} else if ( locproto === "https:" ) {
loc = true ;
}
2022-03-10 22:36:09 +01:00
}
var sett = localStorage . getItem ( 'wledUiCfg' ) ;
if ( sett ) cfg = mergeDeep ( cfg , JSON . parse ( sett ) ) ;
2023-10-30 13:43:50 +01:00
tooltip ( ) ;
2022-03-10 22:36:09 +01:00
resetPUtil ( ) ;
2023-03-30 21:35:23 +02:00
if ( localStorage . getItem ( 'pcm' ) == "true" || ( ! /Mobi/ . test ( navigator . userAgent ) && localStorage . getItem ( 'pcm' ) == null ) ) togglePcMode ( true ) ;
2022-03-10 22:36:09 +01:00
applyCfg ( ) ;
if ( cfg . comp . hdays ) { //load custom holiday list
2023-06-04 18:40:29 +02:00
fetch ( getURL ( "/holidays.json" ) , { // may be loaded from external source
2022-03-10 22:36:09 +01:00
method : 'get'
} )
. then ( ( res ) => {
//if (!res.ok) showErrorToast();
return res . json ( ) ;
} )
. then ( ( json ) => {
if ( Array . isArray ( json ) ) hol = json ;
//TODO: do some parsing first
} )
. catch ( ( e ) => {
console . log ( "No array of holidays in holidays.json. Defaults loaded." ) ;
} )
. finally ( ( ) => {
loadBg ( cfg . theme . bg . url ) ;
} ) ;
} else
loadBg ( cfg . theme . bg . url ) ;
selectSlot ( 0 ) ;
updateTablinks ( 0 ) ;
pmtLS = localStorage . getItem ( 'wledPmt' ) ;
// Load initial data
loadPalettes ( ( ) => {
2022-04-11 22:18:44 +02:00
// fill effect extra data array
loadFXData ( ( ) => {
2023-10-16 18:34:51 +02:00
setTimeout ( ( ) => { // ESP8266 can't handle quick requests
// load and populate effects
loadFX ( ( ) => {
setTimeout ( ( ) => { // ESP8266 can't handle quick requests
loadPalettesData ( ( ) => {
requestJson ( ) ; // will load presets and create WS
if ( cfg . comp . css ) setTimeout ( ( ) => { loadSkinCSS ( 'skinCss' ) } , 100 ) ;
} ) ;
} , 100 ) ;
} ) ;
} , 100 ) ;
2022-03-10 22:36:09 +01:00
} ) ;
} ) ;
resetUtil ( ) ;
d . addEventListener ( "visibilitychange" , handleVisibilityChange , false ) ;
2023-03-30 21:35:23 +02:00
//size();
2022-03-10 22:36:09 +01:00
gId ( "cv" ) . style . opacity = 0 ;
2023-11-04 09:39:08 +01:00
d . querySelectorAll ( 'input[type="range"]' ) . forEach ( ( sl ) => {
2022-03-10 22:36:09 +01:00
sl . addEventListener ( 'touchstart' , toggleBubble ) ;
sl . addEventListener ( 'touchend' , toggleBubble ) ;
2023-11-04 09:39:08 +01:00
} ) ;
2022-03-10 22:36:09 +01:00
}
function updateTablinks ( tabI )
{
var tablinks = gEBCN ( "tablinks" ) ;
2022-12-23 02:59:24 +01:00
for ( var i of tablinks ) i . classList . remove ( 'active' ) ;
2022-03-10 22:36:09 +01:00
if ( pcMode ) return ;
2022-12-23 02:59:24 +01:00
tablinks [ tabI ] . classList . add ( 'active' ) ;
2022-03-10 22:36:09 +01:00
}
function openTab ( tabI , force = false )
{
if ( pcMode && ! force ) return ;
iSlide = tabI ;
_C . classList . toggle ( 'smooth' , false ) ;
_C . style . setProperty ( '--i' , iSlide ) ;
updateTablinks ( tabI ) ;
}
var timeout ;
function showToast ( text , error = false )
{
if ( error ) gId ( 'connind' ) . style . backgroundColor = "var(--c-r)" ;
2022-12-23 02:59:24 +01:00
var x = gId ( 'toast' ) ;
2022-03-12 18:03:00 +01:00
//if (error) text += '<i class="icons btn-icon" style="transform:rotate(45deg);position:absolute;top:10px;right:0px;" onclick="clearErrorToast(100);"></i>';
2022-03-10 22:36:09 +01:00
x . innerHTML = text ;
2022-12-23 02:59:24 +01:00
x . classList . add ( error ? 'error' : 'show' ) ;
2022-03-10 22:36:09 +01:00
clearTimeout ( timeout ) ;
x . style . animation = 'none' ;
2022-12-23 02:59:24 +01:00
timeout = setTimeout ( ( ) => { x . classList . remove ( 'show' ) ; } , 2900 ) ;
2022-03-10 22:36:09 +01:00
if ( error ) console . log ( text ) ;
}
function showErrorToast ( )
{
showToast ( 'Connection to light failed!' , true ) ;
}
2022-05-20 14:48:40 +02:00
function clearErrorToast ( n = 5000 )
2022-03-10 22:36:09 +01:00
{
2022-12-23 02:59:24 +01:00
var x = gId ( 'toast' ) ;
if ( x . classList . contains ( 'error' ) ) {
2022-03-10 22:36:09 +01:00
clearTimeout ( timeout ) ;
timeout = setTimeout ( ( ) => {
2022-12-23 02:59:24 +01:00
x . classList . remove ( 'show' ) ;
x . classList . remove ( 'error' ) ;
2022-03-10 22:36:09 +01:00
} , n ) ;
}
}
function getRuntimeStr ( rt )
{
var t = parseInt ( rt ) ;
var days = Math . floor ( t / 86400 ) ;
var hrs = Math . floor ( ( t - days * 86400 ) / 3600 ) ;
var mins = Math . floor ( ( t - days * 86400 - hrs * 3600 ) / 60 ) ;
var str = days ? ( days + " " + ( days == 1 ? "day" : "days" ) + ", " ) : "" ;
str += ( hrs || days ) ? ( hrs + " " + ( hrs == 1 ? "hour" : "hours" ) ) : "" ;
if ( ! days && hrs ) str += ", " ;
if ( t > 59 && ! days ) str += mins + " min" ;
if ( t < 3600 && t > 59 ) str += ", " ;
if ( t < 3600 ) str += ( t - mins * 60 ) + " sec" ;
return str ;
}
function inforow ( key , val , unit = "" )
{
return ` <tr><td class="keytd"> ${ key } </td><td class="valtd"> ${ val } ${ unit } </td></tr> ` ;
}
function getLowestUnusedP ( )
{
var l = 1 ;
for ( var key in pJson ) if ( key == l ) l ++ ;
if ( l > 250 ) l = 250 ;
return l ;
}
function checkUsed ( i )
{
var id = gId ( ` p ${ i } id ` ) . value ;
if ( pJson [ id ] && ( i == 0 || id != i ) )
gId ( ` p ${ i } warn ` ) . innerHTML = ` ⚠ Overwriting ${ pName ( id ) } ! ` ;
else
2022-05-18 14:38:22 +02:00
gId ( ` p ${ i } warn ` ) . innerHTML = id > 250 ? "⚠ ID must be 250 or less." : "" ;
2022-03-10 22:36:09 +01:00
}
function pName ( i )
{
var n = "Preset " + i ;
if ( pJson && pJson [ i ] && pJson [ i ] . n ) n = pJson [ i ] . n ;
return n ;
}
function isPlaylist ( i )
{
return pJson [ i ] . playlist && pJson [ i ] . playlist . ps ;
}
function papiVal ( i )
{
if ( ! pJson || ! pJson [ i ] ) return "" ;
var o = Object . assign ( { } , pJson [ i ] ) ;
if ( o . win ) return o . win ;
delete o . n ; delete o . p ; delete o . ql ;
return JSON . stringify ( o ) ;
}
function qlName ( i )
{
if ( ! pJson || ! pJson [ i ] || ! pJson [ i ] . ql ) return "" ;
return pJson [ i ] . ql ;
}
function cpBck ( )
{
var copyText = gId ( "bck" ) ;
copyText . select ( ) ;
copyText . setSelectionRange ( 0 , 999999 ) ;
d . execCommand ( "copy" ) ;
showToast ( "Copied to clipboard!" ) ;
}
function presetError ( empty )
{
var hasBackup = false ; var bckstr = "" ;
try {
bckstr = localStorage . getItem ( "wledP" ) ;
if ( bckstr . length > 10 ) hasBackup = true ;
} catch ( e ) { }
2022-10-08 18:25:51 +02:00
var cn = ` <div class="pres c" ${ empty ? 'style="padding:8px;margin-top: 16px;"' : 'onclick="pmtLast=0;loadPresets();" style="cursor:pointer;padding:8px;margin-top: 16px;"' } > ` ;
2022-03-10 22:36:09 +01:00
if ( empty )
cn += ` You have no presets yet! ` ;
else
cn += ` Sorry, there was an issue loading your presets! ` ;
if ( hasBackup ) {
cn += ` <br><br> ` ;
if ( empty )
2023-09-16 14:41:47 +02:00
cn += ` However, there is backup preset data of a previous installation available.<br>(Saving a preset will hide this and overwrite the backup) ` ;
2022-03-10 22:36:09 +01:00
else
cn += ` Here is a backup of the last known good state: ` ;
2023-09-16 14:41:47 +02:00
cn += ` <textarea id="bck"></textarea><br><button class="btn" onclick="cpBck()">Copy to clipboard</button> ` ;
2023-09-16 20:29:32 +02:00
cn += ` <br><button type="button" class="btn" onclick="restore(gId('bck').value)">Restore</button> ` ;
2022-03-10 22:36:09 +01:00
}
cn += ` </div> ` ;
gId ( 'pcont' ) . innerHTML = cn ;
if ( hasBackup ) gId ( 'bck' ) . value = bckstr ;
}
2023-09-16 20:29:32 +02:00
function restore ( txt ) {
2023-09-16 14:41:47 +02:00
var req = new XMLHttpRequest ( ) ;
req . addEventListener ( 'load' , function ( ) { showToast ( this . responseText , this . status >= 400 ) } ) ;
req . addEventListener ( 'error' , function ( e ) { showToast ( e . stack , true ) ; } ) ;
req . open ( "POST" , getURL ( "/upload" ) ) ;
var formData = new FormData ( ) ;
var b = new Blob ( [ txt ] , { type : "application/json" } ) ;
formData . append ( "data" , b , '/presets.json' ) ;
req . send ( formData ) ;
2023-09-16 20:29:32 +02:00
setTimeout ( loadPresets , 2000 ) ;
2023-09-16 14:41:47 +02:00
return false ;
}
2022-03-10 22:36:09 +01:00
function loadPresets ( callback = null )
{
// 1st boot (because there is a callback)
if ( callback && pmt == pmtLS && pmt > 0 ) {
// we have a copy of the presets in local storage and don't need to fetch another one
populatePresets ( true ) ;
pmtLast = pmt ;
callback ( ) ;
return ;
}
// afterwards
if ( ! callback && pmt == pmtLast ) return ;
2023-06-04 18:40:29 +02:00
fetch ( getURL ( '/presets.json' ) , {
2022-03-10 22:36:09 +01:00
method : 'get'
} )
. then ( res => {
2022-08-14 22:16:42 +02:00
if ( res . status == "404" ) return { "0" : { } } ;
//if (!res.ok) showErrorToast();
2022-03-10 22:36:09 +01:00
return res . json ( ) ;
} )
. then ( json => {
pJson = json ;
pmtLast = pmt ;
populatePresets ( ) ;
} )
. catch ( ( e ) => {
2022-08-14 22:16:42 +02:00
//showToast(e, true);
2022-03-10 22:36:09 +01:00
presetError ( false ) ;
} )
. finally ( ( ) => {
if ( callback ) setTimeout ( callback , 99 ) ;
} ) ;
}
function loadPalettes ( callback = null )
{
2023-06-04 18:40:29 +02:00
fetch ( getURL ( '/json/palettes' ) , {
2022-03-10 22:36:09 +01:00
method : 'get'
} )
. then ( ( res ) => {
if ( ! res . ok ) showErrorToast ( ) ;
return res . json ( ) ;
} )
. then ( ( json ) => {
lJson = Object . entries ( json ) ;
populatePalettes ( ) ;
} )
. catch ( ( e ) => {
showToast ( e , true ) ;
} )
. finally ( ( ) => {
if ( callback ) callback ( ) ;
updateUI ( ) ;
} ) ;
}
function loadFX ( callback = null )
{
2023-06-04 18:40:29 +02:00
fetch ( getURL ( '/json/effects' ) , {
2022-03-10 22:36:09 +01:00
method : 'get'
} )
. then ( ( res ) => {
if ( ! res . ok ) showErrorToast ( ) ;
return res . json ( ) ;
} )
. then ( ( json ) => {
eJson = Object . entries ( json ) ;
populateEffects ( ) ;
} )
. catch ( ( e ) => {
2023-05-30 19:36:14 +02:00
//setTimeout(loadFX, 250); // retry
2022-03-10 22:36:09 +01:00
showToast ( e , true ) ;
} )
. finally ( ( ) => {
if ( callback ) callback ( ) ;
updateUI ( ) ;
} ) ;
}
function loadFXData ( callback = null )
{
2023-06-04 18:40:29 +02:00
fetch ( getURL ( '/json/fxdata' ) , {
2022-03-10 22:36:09 +01:00
method : 'get'
} )
. then ( ( res ) => {
if ( ! res . ok ) showErrorToast ( ) ;
return res . json ( ) ;
} )
. then ( ( json ) => {
fxdata = json || [ ] ;
// add default value for Solid
fxdata . shift ( )
2022-11-26 21:31:45 +01:00
fxdata . unshift ( ";!;" ) ;
2022-03-10 22:36:09 +01:00
} )
. catch ( ( e ) => {
fxdata = [ ] ;
2023-05-30 19:36:14 +02:00
//setTimeout(loadFXData, 250); // retry
2022-03-10 22:36:09 +01:00
showToast ( e , true ) ;
} )
. finally ( ( ) => {
if ( callback ) callback ( ) ;
updateUI ( ) ;
} ) ;
}
var pQL = [ ] ;
function populateQL ( )
{
var cn = "" ;
if ( pQL . length > 0 ) {
pQL . sort ( ( a , b ) => ( a [ 0 ] > b [ 0 ] ) ) ;
cn += ` <p class="labels hd">Quick load</p> ` ;
for ( var key of ( pQL || [ ] ) ) {
cn += ` <button class="btn btn-xs psts" id="p ${ key [ 0 ] } qlb" title=" ${ key [ 2 ] ? key [ 2 ] : '' } " onclick="setPreset( ${ key [ 0 ] } );"> ${ key [ 1 ] } </button> ` ;
}
2022-12-23 02:59:24 +01:00
gId ( 'pql' ) . classList . add ( 'expanded' ) ;
} else gId ( 'pql' ) . classList . remove ( 'expanded' ) ;
2022-03-10 22:36:09 +01:00
gId ( 'pql' ) . innerHTML = cn ;
}
function populatePresets ( fromls )
{
if ( fromls ) pJson = JSON . parse ( localStorage . getItem ( "wledP" ) ) ;
if ( ! pJson ) { setTimeout ( loadPresets , 250 ) ; return ; }
delete pJson [ "0" ] ;
var cn = "" ;
2023-11-04 09:39:08 +01:00
var arr = Object . entries ( pJson ) . sort ( cmpP ) ;
2022-03-10 22:36:09 +01:00
pQL = [ ] ;
var is = [ ] ;
pNum = 0 ;
for ( var key of ( arr || [ ] ) )
{
if ( ! isObj ( key [ 1 ] ) ) continue ;
let i = parseInt ( key [ 0 ] ) ;
var qll = key [ 1 ] . ql ;
if ( qll ) pQL . push ( [ i , qll , pName ( i ) ] ) ;
is . push ( i ) ;
cn += ` <div class="pres lstI" id="p ${ i } o"> ` ;
if ( cfg . comp . pid ) cn += ` <div class="pid"> ${ i } </div> ` ;
2022-03-26 23:22:18 +01:00
cn += ` <div class="pname lstIname" onclick="setPreset( ${ i } )"> ${ isPlaylist ( i ) ? "<i class='icons btn-icon'></i>" : "" } ${ pName ( i ) }
2022-06-23 21:00:12 +02:00
< i class = "icons edit-icon flr" id = "p${i}nedit" onclick = "tglSegn(${i+100})" > & # xe2c6 ; < / i > < / d i v >
2022-03-26 23:22:18 +01:00
< i class = "icons e-icon flr" id = "sege${i+100}" onclick = "expand(${i+100})" > & # xe395 ; < / i >
2022-03-10 22:36:09 +01:00
< div class = "presin lstIcontent" id = "seg${i+100}" > < / d i v >
< / d i v > ` ;
2022-08-24 23:04:51 +02:00
pNum ++ ;
2022-03-10 22:36:09 +01:00
}
gId ( 'pcont' ) . innerHTML = cn ;
if ( pNum > 0 ) {
if ( pmtLS != pmt && pmt != 0 ) {
localStorage . setItem ( "wledPmt" , pmt ) ;
pJson [ "0" ] = { } ;
localStorage . setItem ( "wledP" , JSON . stringify ( pJson ) ) ;
}
pmtLS = pmt ;
} else { presetError ( true ) ; }
updatePA ( ) ;
populateQL ( ) ;
}
2022-03-28 22:36:58 +02:00
function parseInfo ( i ) {
lastinfo = i ;
var name = i . name ;
2022-03-10 22:36:09 +01:00
gId ( 'namelabel' ) . innerHTML = name ;
2022-09-06 03:06:00 +02:00
if ( ! name . match ( /[\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uff66-\uff9f\u3131-\uD79D]/ ) )
gId ( 'namelabel' ) . style . transform = "rotate(180deg)" ; // rotate if no CJK characters
if ( name === "Dinnerbone" ) d . documentElement . style . transform = "rotate(180deg)" ; // Minecraft easter egg
2022-03-28 22:36:58 +02:00
if ( i . live ) name = "(Live) " + name ;
if ( loc ) name = "(L) " + name ;
2022-03-10 22:36:09 +01:00
d . title = name ;
2022-03-28 22:36:58 +02:00
ledCount = i . leds . count ;
2023-09-10 18:52:14 +02:00
//syncTglRecv = i.str;
2022-03-28 22:36:58 +02:00
maxSeg = i . leds . maxseg ;
pmt = i . fs . pmt ;
2023-03-30 21:35:23 +02:00
gId ( 'buttonNodes' ) . style . display = lastinfo . ndc > 0 ? null : "none" ;
2022-05-08 10:50:48 +02:00
// do we have a matrix set-up
mw = i . leds . matrix ? i . leds . matrix . w : 0 ;
mh = i . leds . matrix ? i . leds . matrix . h : 0 ;
isM = mw > 0 && mh > 0 ;
2022-08-01 17:32:40 +02:00
if ( ! isM ) {
2023-10-26 22:09:46 +02:00
//gId("filter0D").classList.remove('hide');
//gId("filter1D").classList.add('hide');
2023-04-01 11:02:49 +02:00
gId ( "filter2D" ) . classList . add ( 'hide' ) ;
} else {
2023-10-26 22:09:46 +02:00
//gId("filter0D").classList.add('hide');
//gId("filter1D").classList.remove('hide');
2023-04-01 11:02:49 +02:00
gId ( "filter2D" ) . classList . remove ( 'hide' ) ;
2022-08-01 17:32:40 +02:00
}
2022-09-05 00:43:26 +02:00
// if (i.noaudio) {
// gId("filterVol").classList.add("hide");
// gId("filterFreq").classList.add("hide");
// }
2022-08-01 17:32:40 +02:00
// if (!i.u || !i.u.AudioReactive) {
2022-09-05 00:43:26 +02:00
// gId("filterVol").classList.add("hide"); hideModes(" ♪"); // hide volume reactive effects
// gId("filterFreq").classList.add("hide"); hideModes(" ♫"); // hide frequency reactive effects
2022-08-01 17:32:40 +02:00
// }
2022-03-10 22:36:09 +01:00
}
2022-07-03 22:55:37 +02:00
//https://stackoverflow.com/questions/2592092/executing-script-elements-inserted-with-innerhtml
//var setInnerHTML = function(elm, html) {
// elm.innerHTML = html;
// Array.from(elm.querySelectorAll("script")).forEach( oldScript => {
// const newScript = document.createElement("script");
// Array.from(oldScript.attributes)
// .forEach( attr => newScript.setAttribute(attr.name, attr.value) );
// newScript.appendChild(document.createTextNode(oldScript.innerHTML));
// oldScript.parentNode.replaceChild(newScript, oldScript);
// });
//}
//setInnerHTML(obj, html);
2022-03-10 22:36:09 +01:00
function populateInfo ( i )
{
var cn = "" ;
2023-03-30 21:35:23 +02:00
var heap = i . freeheap / 1024 ;
2022-03-10 22:36:09 +01:00
heap = heap . toFixed ( 1 ) ;
var pwr = i . leds . pwr ;
var pwru = "Not calculated" ;
if ( pwr > 1000 ) { pwr /= 1000 ; pwr = pwr . toFixed ( ( pwr > 10 ) ? 0 : 1 ) ; pwru = pwr + " A" ; }
else if ( pwr > 0 ) { pwr = 50 * Math . round ( pwr / 50 ) ; pwru = pwr + " mA" ; }
2022-08-24 23:04:51 +02:00
var urows = "" ;
2022-03-10 22:36:09 +01:00
if ( i . u ) {
for ( const [ k , val ] of Object . entries ( i . u ) ) {
if ( val [ 1 ] )
urows += inforow ( k , val [ 0 ] , val [ 1 ] ) ;
else
urows += inforow ( k , val ) ;
}
}
var vcn = "Kuuhaku" ;
if ( i . cn ) vcn = i . cn ;
cn += ` v ${ i . ver } " ${ vcn } "<br><br><table>
$ { urows }
2022-09-03 00:01:11 +02:00
$ { urows === "" ? '' : '<tr><td colspan=2><hr style="height:1px;border-width:0;color:gray;background-color:gray"></td></tr>' }
2022-11-10 21:50:21 +01:00
$ { i . opt & 0x100 ? inforow ( "Debug" , "<button class=\"btn btn-xs\" onclick=\"requestJson({'debug':" + ( i . opt & 0x0080 ? "false" : "true" ) + "});\"><i class=\"icons " + ( i . opt & 0x0080 ? "on" : "off" ) + "\"></i></button>" ) : '' }
2022-03-10 22:36:09 +01:00
$ { inforow ( "Build" , i . vid ) }
$ { inforow ( "Signal strength" , i . wifi . signal + "% (" + i . wifi . rssi , " dBm)" ) }
$ { inforow ( "Uptime" , getRuntimeStr ( i . uptime ) ) }
2023-08-30 20:57:54 +02:00
$ { inforow ( "Time" , i . time ) }
2022-03-10 22:36:09 +01:00
$ { inforow ( "Free heap" , heap , " kB" ) }
$ { i . psram ? inforow ( "Free PSRAM" , ( i . psram / 1024 ) . toFixed ( 1 ) , " kB" ) : "" }
$ { inforow ( "Estimated current" , pwru ) }
$ { inforow ( "Average FPS" , i . leds . fps ) }
$ { inforow ( "MAC address" , i . mac ) }
$ { inforow ( "Filesystem" , i . fs . u + "/" + i . fs . t + " kB (" + Math . round ( i . fs . u * 100 / i . fs . t ) + "%)" ) }
$ { inforow ( "Environment" , i . arch + " " + i . core + " (" + i . lwip + ")" ) }
< / t a b l e > ` ;
gId ( 'kv' ) . innerHTML = cn ;
2022-07-03 22:55:37 +02:00
// update all sliders in Info
2023-11-04 09:39:08 +01:00
for ( let sd of ( d . querySelectorAll ( '#kv .sliderdisplay' ) || [ ] ) ) {
2022-07-03 22:55:37 +02:00
let s = sd . previousElementSibling ;
if ( s ) updateTrail ( s ) ;
}
2022-03-10 22:36:09 +01:00
}
function populateSegments ( s )
{
var cn = "" ;
2022-03-28 22:36:58 +02:00
let li = lastinfo ;
2022-03-10 22:36:09 +01:00
segCount = 0 ; lowestUnused = 0 ; lSeg = 0 ;
2022-07-27 17:00:55 +02:00
for ( var inst of ( s . seg || [ ] ) ) {
2022-03-10 22:36:09 +01:00
segCount ++ ;
let i = parseInt ( inst . id ) ;
if ( i == lowestUnused ) lowestUnused = i + 1 ;
if ( i > lSeg ) lSeg = i ;
2022-03-26 23:22:18 +01:00
let sg = gId ( ` seg ${ i } ` ) ;
2022-12-23 02:59:24 +01:00
let exp = sg ? ( sg . classList . contains ( 'expanded' ) || ( i === 0 && cfg . comp . segexp ) ) : false ;
2022-03-26 23:22:18 +01:00
2023-05-29 21:06:10 +02:00
// segment set icon color
let cG = "var(--c-b)" ;
2023-05-15 17:06:29 +02:00
switch ( inst . set ) {
2023-04-01 23:40:43 +02:00
case 1 : cG = "var(--c-r)" ; break ;
2023-05-29 21:06:10 +02:00
case 2 : cG = "var(--c-g)" ; break ;
case 3 : cG = "var(--c-l)" ; break ;
2023-04-01 23:40:43 +02:00
}
2023-03-30 21:35:23 +02:00
let segp = ` <div id="segp ${ i } " class="sbs"> ` +
` <i class="icons slider-icon pwr ${ inst . on ? "act" : "" } " id="seg ${ i } pwr" onclick="setSegPwr( ${ i } )"></i> ` +
` <div class="sliderwrap il"> ` +
` <input id="seg ${ i } bri" class="noslide" onchange="setSegBri( ${ i } )" oninput="updateTrail(this)" max="255" min="1" type="range" value=" ${ inst . bri } " /> ` +
` <div class="sliderdisplay"></div> ` +
` </div> ` +
` </div> ` ;
2022-08-22 16:47:25 +02:00
let staX = inst . start ;
let stoX = inst . stop ;
let staY = inst . startY ;
let stoY = inst . stopY ;
2023-05-29 21:06:10 +02:00
let isMSeg = isM && staX < mw * mh ; // 2D matrix segment
2022-07-27 00:11:24 +02:00
let rvXck = ` <label class="check revchkl">Reverse ${ isM ? '' : 'direction' } <input type="checkbox" id="seg ${ i } rev" onchange="setRev( ${ i } )" ${ inst . rev ? "checked" : "" } ><span class="checkmark"></span></label> ` ;
let miXck = ` <label class="check revchkl">Mirror<input type="checkbox" id="seg ${ i } mi" onchange="setMi( ${ i } )" ${ inst . mi ? "checked" : "" } ><span class="checkmark"></span></label> ` ;
2022-05-08 10:50:48 +02:00
let rvYck = "" , miYck = "" ;
2023-05-29 21:06:10 +02:00
if ( isMSeg ) {
2022-07-27 00:11:24 +02:00
rvYck = ` <label class="check revchkl">Reverse<input type="checkbox" id="seg ${ i } rY" onchange="setRevY( ${ i } )" ${ inst . rY ? "checked" : "" } ><span class="checkmark"></span></label> ` ;
miYck = ` <label class="check revchkl">Mirror<input type="checkbox" id="seg ${ i } mY" onchange="setMiY( ${ i } )" ${ inst . mY ? "checked" : "" } ><span class="checkmark"></span></label> ` ;
2022-05-08 10:50:48 +02:00
}
2023-03-30 21:35:23 +02:00
let map2D = ` <div id="seg ${ i } map2D" data-map="map2D" class="lbl-s hide">Expand 1D FX<br> ` +
` <div class="sel-p"><select class="sel-p" id="seg ${ i } m12" onchange="setM12( ${ i } )"> ` +
` <option value="0" ${ inst . m12 == 0 ? ' selected' : '' } >Pixels</option> ` +
` <option value="1" ${ inst . m12 == 1 ? ' selected' : '' } >Bar</option> ` +
` <option value="2" ${ inst . m12 == 2 ? ' selected' : '' } >Arc</option> ` +
` <option value="3" ${ inst . m12 == 3 ? ' selected' : '' } >Corner</option> ` +
` </select></div> ` +
` </div> ` ;
let sndSim = ` <div data-snd="si" class="lbl-s hide">Sound sim<br> ` +
` <div class="sel-p"><select class="sel-p" id="seg ${ i } si" onchange="setSi( ${ i } )"> ` +
` <option value="0" ${ inst . si == 0 ? ' selected' : '' } >BeatSin</option> ` +
` <option value="1" ${ inst . si == 1 ? ' selected' : '' } >WeWillRockYou</option> ` +
` </select></div> ` +
` </div> ` ;
2023-05-15 17:06:29 +02:00
cn += ` <div class="seg lstI ${ i == s . mainseg ? 'selected' : '' } ${ exp ? "expanded" : "" } " id="seg ${ i } " data-set=" ${ inst . set } "> ` +
2023-03-30 21:35:23 +02:00
` <label class="check schkl"> ` +
` <input type="checkbox" id="seg ${ i } sel" onchange="selSeg( ${ i } )" ${ inst . sel ? "checked" : "" } > ` +
` <span class="checkmark"></span> ` +
` </label> ` +
` <div class="segname" onclick="selSegEx( ${ i } )"> ` +
2023-04-02 18:05:59 +02:00
` <i class="icons e-icon frz" id="seg ${ i } frz" onclick="event.preventDefault();tglFreeze( ${ i } );">&#x ${ inst . frz ? ( li . live && li . liveseg == i ? 'e410' : 'e0e8' ) : 'e325' } ;</i> ` +
2023-03-30 21:35:23 +02:00
( inst . n ? inst . n : "Segment " + i ) +
2023-04-02 18:05:59 +02:00
` <div class="pop hide" onclick="event.preventDefault();event.stopPropagation();"> ` +
2023-05-15 17:06:29 +02:00
` <i class="icons g-icon" style="color: ${ cG } ;" onclick="this.nextElementSibling.classList.toggle('hide');">ɸ ${ String . fromCharCode ( inst . set + "A" . charCodeAt ( 0 ) ) } ;</i> ` +
2023-04-02 18:05:59 +02:00
` <div class="pop-c hide"><span style="color:var(--c-f);" onclick="setGrp( ${ i } ,0);">➊</span><span style="color:var(--c-r);" onclick="setGrp( ${ i } ,1);">➋</span><span style="color:var(--c-g);" onclick="setGrp( ${ i } ,2);">➌</span><span style="color:var(--c-l);" onclick="setGrp( ${ i } ,3);">➍</span></div> ` +
` </div> ` +
2023-03-30 21:35:23 +02:00
` <i class="icons edit-icon flr" id="seg ${ i } nedit" onclick="tglSegn( ${ i } )"></i> ` +
` </div> ` +
` <i class="icons e-icon flr" id="sege ${ i } " onclick="expand( ${ i } )"></i> ` +
( cfg . comp . segpwr ? segp : '' ) +
` <div class="segin" id="seg ${ i } in"> ` +
2023-06-27 16:01:20 +02:00
` <input type="text" class="ptxt" id="seg ${ i } t" autocomplete="off" maxlength= ${ li . arch == "esp8266" ? 32 : 64 } value=" ${ inst . n ? inst . n : "" } " placeholder="Enter name..."/> ` +
2023-03-30 21:35:23 +02:00
` <table class="infot segt"> ` +
` <tr> ` +
2023-05-29 21:06:10 +02:00
` <td> ${ isMSeg ? 'Start X' : 'Start LED' } </td> ` +
` <td> ${ isMSeg ? ( cfg . comp . seglen ? "Width" : "Stop X" ) : ( cfg . comp . seglen ? "LED count" : "Stop LED" ) } </td> ` +
` <td> ${ isMSeg ? '' : 'Offset' } </td> ` +
2023-03-30 21:35:23 +02:00
` </tr> ` +
` <tr> ` +
2023-05-29 21:06:10 +02:00
` <td><input class="segn" id="seg ${ i } s" type="number" min="0" max=" ${ ( isMSeg ? mw : ledCount ) - 1 } " value=" ${ staX } " oninput="updateLen( ${ i } )" onkeydown="segEnter( ${ i } )"></td> ` +
` <td><input class="segn" id="seg ${ i } e" type="number" min="0" max=" ${ ( isMSeg ? mw : ledCount ) } " value=" ${ stoX - ( cfg . comp . seglen ? staX : 0 ) } " oninput="updateLen( ${ i } )" onkeydown="segEnter( ${ i } )"></td> ` +
` <td ${ isMSeg ? 'style="text-align:revert;"' : '' } > ${ isMSeg ? miXck + '<br>' + rvXck : '' } <input class="segn ${ isMSeg ? 'hide' : '' } " id="seg ${ i } of" type="number" value=" ${ inst . of } " oninput="updateLen( ${ i } )"></td> ` +
2023-03-30 21:35:23 +02:00
` </tr> ` +
2023-05-29 21:06:10 +02:00
( isMSeg ? '<tr><td>Start Y</td><td>' + ( cfg . comp . seglen ? 'Height' : 'Stop Y' ) + '</td><td></td></tr>' +
2023-03-30 21:35:23 +02:00
'<tr>' +
'<td><input class="segn" id="seg' + i + 'sY" type="number" min="0" max="' + ( mh - 1 ) + '" value="' + staY + '" oninput="updateLen(' + i + ')" onkeydown="segEnter(' + i + ')"></td>' +
'<td><input class="segn" id="seg' + i + 'eY" type="number" min="0" max="' + mh + '" value="' + ( stoY - ( cfg . comp . seglen ? staY : 0 ) ) + '" oninput="updateLen(' + i + ')" onkeydown="segEnter(' + i + ')"></td>' +
'<td style="text-align:revert;">' + miYck + '<br>' + rvYck + '</td>' +
'</tr>' : '' ) +
` <tr> ` +
` <td>Grouping</td> ` +
` <td>Spacing</td> ` +
` <td></td> ` +
` </tr> ` +
` <tr> ` +
` <td><input class="segn" id="seg ${ i } grp" type="number" min="1" max="255" value=" ${ inst . grp } " oninput="updateLen( ${ i } )" onkeydown="segEnter( ${ i } )"></td> ` +
` <td><input class="segn" id="seg ${ i } spc" type="number" min="0" max="255" value=" ${ inst . spc } " oninput="updateLen( ${ i } )" onkeydown="segEnter( ${ i } )"></td> ` +
2023-05-29 21:06:10 +02:00
` <td><button class="btn btn-xs" onclick="setSeg( ${ i } )"><i class="icons btn-icon" id="segc ${ i } "></i></button></td> ` +
2023-03-30 21:35:23 +02:00
` </tr> ` +
` </table> ` +
` <div class="h bp" id="seg ${ i } len"></div> ` +
2023-05-29 21:06:10 +02:00
( ! isMSeg ? rvXck : '' ) +
( isMSeg && stoY - staY > 1 && stoX - staX > 1 ? map2D : '' ) +
2023-03-30 21:35:23 +02:00
( s . AudioReactive && s . AudioReactive . on ? "" : sndSim ) +
` <label class="check revchkl" id="seg ${ i } lbtm"> ` +
2023-05-29 21:06:10 +02:00
( isMSeg ? 'Transpose' : 'Mirror effect' ) + ( isMSeg ?
2023-03-30 21:35:23 +02:00
'<input type="checkbox" id="seg' + i + 'tp" onchange="setTp(' + i + ')" ' + ( inst . tp ? "checked" : "" ) + '>' :
'<input type="checkbox" id="seg' + i + 'mi" onchange="setMi(' + i + ')" ' + ( inst . mi ? "checked" : "" ) + '>' ) +
` <span class="checkmark"></span> ` +
` </label> ` +
` <div class="del"> ` +
` <button class="btn btn-xs" id="segr ${ i } " title="Repeat until end" onclick="rptSeg( ${ i } )"><i class="icons btn-icon"></i></button> ` +
` <button class="btn btn-xs" id="segd ${ i } " title="Delete" onclick="delSeg( ${ i } )"><i class="icons btn-icon"></i></button> ` +
` </div> ` +
` </div> ` +
( cfg . comp . segpwr ? '' : segp ) +
` </div> ` ;
2022-03-10 22:36:09 +01:00
}
gId ( 'segcont' ) . innerHTML = cn ;
2022-12-09 08:15:14 +01:00
let noNewSegs = ( lowestUnused >= maxSeg ) ;
resetUtil ( noNewSegs ) ;
2022-12-16 23:20:49 +01:00
if ( gId ( 'selall' ) ) gId ( 'selall' ) . checked = true ;
2022-03-10 22:36:09 +01:00
for ( var i = 0 ; i <= lSeg ; i ++ ) {
2023-07-14 01:12:19 +02:00
if ( ! gId ( ` seg ${ i } ` ) ) continue ;
2022-03-10 22:36:09 +01:00
updateLen ( i ) ;
updateTrail ( gId ( ` seg ${ i } bri ` ) ) ;
2023-02-25 09:41:15 +01:00
gId ( ` segr ${ i } ` ) . classList . add ( "hide" ) ;
2022-12-16 23:20:49 +01:00
if ( ! gId ( ` seg ${ i } sel ` ) . checked && gId ( 'selall' ) ) gId ( 'selall' ) . checked = false ; // uncheck if at least one is unselected.
2022-03-10 22:36:09 +01:00
}
2023-02-25 09:41:15 +01:00
if ( segCount < 2 ) {
gId ( ` segd ${ lSeg } ` ) . classList . add ( "hide" ) ;
2023-10-01 13:04:05 +02:00
if ( parseInt ( gId ( "seg0bri" ) . value ) == 255 ) gId ( ` segp0 ` ) . classList . add ( "hide" ) ;
2023-02-25 09:41:15 +01:00
}
2023-05-29 21:23:11 +02:00
if ( ! isM && ! noNewSegs && ( cfg . comp . seglen ? parseInt ( gId ( ` seg ${ lSeg } s ` ) . value ) : 0 ) + parseInt ( gId ( ` seg ${ lSeg } e ` ) . value ) < ledCount ) gId ( ` segr ${ lSeg } ` ) . classList . remove ( "hide" ) ;
2022-06-23 21:00:12 +02:00
gId ( 'segutil2' ) . style . display = ( segCount > 1 ) ? "block" : "none" ; // rsbtn parent
2022-11-16 20:55:21 +01:00
2023-02-10 19:49:43 +01:00
if ( Array . isArray ( li . maps ) && li . maps . length > 1 ) {
2022-11-16 20:55:21 +01:00
let cont = ` Ledmap: <select class="sel-sg" onchange="requestJson({'ledmap':parseInt(this.value)})"><option value="" selected>Unchanged</option> ` ;
2023-02-14 17:11:58 +01:00
for ( const k of ( li . maps || [ ] ) ) cont += ` <option value=" ${ k . id } "> ${ k . id == 0 ? 'Default' : ( k . n ? k . n : 'ledmap' + k . id + '.json' ) } </option> ` ;
2022-11-16 20:55:21 +01:00
cont += "</select></div>" ;
gId ( "ledmap" ) . innerHTML = cont ;
2022-12-23 02:59:24 +01:00
gId ( "ledmap" ) . classList . remove ( 'hide' ) ;
2022-11-16 20:55:21 +01:00
} else {
2022-12-23 02:59:24 +01:00
gId ( "ledmap" ) . classList . add ( 'hide' ) ;
2022-11-16 20:55:21 +01:00
}
2022-03-10 22:36:09 +01:00
}
function populateEffects ( )
{
2022-04-12 16:08:17 +02:00
var effects = eJson ;
2022-03-10 22:36:09 +01:00
var html = "" ;
2022-07-27 17:00:55 +02:00
effects . shift ( ) ; // temporary remove solid
for ( let i = 0 ; i < effects . length ; i ++ ) {
effects [ i ] = {
id : effects [ i ] [ 0 ] ,
name : effects [ i ] [ 1 ]
} ;
}
2022-03-10 22:36:09 +01:00
effects . sort ( ( a , b ) => ( a . name ) . localeCompare ( b . name ) ) ;
effects . unshift ( {
"id" : 0 ,
2022-04-11 22:18:44 +02:00
"name" : "Solid"
2022-03-10 22:36:09 +01:00
} ) ;
2022-07-27 17:00:55 +02:00
for ( let ef of effects ) {
2023-01-11 23:08:08 +01:00
// add slider and color control to setFX (used by requestjson)
2022-07-27 17:00:55 +02:00
let id = ef . id ;
2022-08-01 17:32:40 +02:00
let nm = ef . name + " " ;
2022-07-27 00:11:24 +02:00
let fd = "" ;
2022-08-09 18:09:43 +02:00
if ( ef . name . indexOf ( "RSVD" ) < 0 ) {
2022-07-27 17:00:55 +02:00
if ( Array . isArray ( fxdata ) && fxdata . length > id ) {
2022-11-26 21:31:45 +01:00
if ( fxdata [ id ] . length == 0 ) fd = ";;!;1"
2022-11-11 03:10:41 +01:00
else fd = fxdata [ id ] ;
2022-08-01 17:32:40 +02:00
let eP = ( fd == '' ) ? [ ] : fd . split ( ";" ) ; // effect parameters
let p = ( eP . length < 3 || eP [ 2 ] === '' ) ? [ ] : eP [ 2 ] . split ( "," ) ; // palette data
if ( p . length > 0 && ( p [ 0 ] !== "" && ! isNumeric ( p [ 0 ] ) ) ) nm += "🎨" ; // effects using palette
2022-11-26 21:31:45 +01:00
let m = ( eP . length < 4 || eP [ 3 ] === '' ) ? '1' : eP [ 3 ] ; // flags
if ( id == 0 ) m = '' ; // solid has no flags
if ( m . length > 0 ) {
2023-02-25 09:41:15 +01:00
if ( m . includes ( '0' ) ) nm += "•" ; // 0D effects (PWM & On/Off)
2022-11-26 21:31:45 +01:00
if ( m . includes ( '1' ) ) nm += "⋮" ; // 1D effects
if ( m . includes ( '2' ) ) nm += "▦" ; // 2D effects
if ( m . includes ( 'v' ) ) nm += "♪" ; // volume effects
if ( m . includes ( 'f' ) ) nm += "♫" ; // frequency effects
2022-08-01 17:32:40 +02:00
}
2022-07-27 17:00:55 +02:00
}
2022-08-22 01:17:10 +02:00
html += generateListItemHtml ( 'fx' , id , nm , 'setFX' , '' , fd ) ;
2022-03-10 22:36:09 +01:00
}
}
gId ( 'fxlist' ) . innerHTML = html ;
}
function populatePalettes ( )
{
2022-07-28 23:19:58 +02:00
lJson . shift ( ) ; // temporary remove default
lJson . sort ( ( a , b ) => ( a [ 1 ] ) . localeCompare ( b [ 1 ] ) ) ;
lJson . unshift ( [ 0 , "Default" ] ) ;
2022-03-10 22:36:09 +01:00
var html = "" ;
2022-07-28 23:19:58 +02:00
for ( let pa of lJson ) {
2022-03-10 22:36:09 +01:00
html += generateListItemHtml (
'palette' ,
2023-10-20 21:32:39 +02:00
pa [ 0 ] ,
2022-08-24 23:04:51 +02:00
pa [ 1 ] ,
'setPalette' ,
2022-07-28 23:19:58 +02:00
` <div class="lstIprev" style=" ${ genPalPrevCss ( pa [ 0 ] ) } "></div> `
2022-08-24 23:04:51 +02:00
) ;
2022-03-10 22:36:09 +01:00
}
gId ( 'pallist' ) . innerHTML = html ;
2023-04-14 17:15:02 +02:00
// append custom palettes (when loading for the 1st time)
2023-10-16 18:34:51 +02:00
let li = lastinfo ;
if ( ! isEmpty ( li ) && li . cpalcount ) {
for ( let j = 0 ; j < li . cpalcount ; j ++ ) {
2023-04-14 17:15:02 +02:00
let div = d . createElement ( "div" ) ;
gId ( 'pallist' ) . appendChild ( div ) ;
div . outerHTML = generateListItemHtml (
'palette' ,
255 - j ,
'~ Custom ' + j + ' ~' ,
'setPalette' ,
` <div class="lstIprev" style=" ${ genPalPrevCss ( 255 - j ) } "></div> `
) ;
}
}
2023-10-16 18:34:51 +02:00
if ( li . cpalcount > 0 ) gId ( "rmPal" ) . classList . remove ( "hide" ) ;
else gId ( "rmPal" ) . classList . add ( "hide" ) ;
2022-03-10 22:36:09 +01:00
}
function redrawPalPrev ( )
{
let palettes = d . querySelectorAll ( '#pallist .lstI' ) ;
for ( var pal of ( palettes || [ ] ) ) {
let lP = pal . querySelector ( '.lstIprev' ) ;
if ( lP ) {
lP . style = genPalPrevCss ( pal . dataset . id ) ;
}
}
}
function genPalPrevCss ( id )
{
if ( ! palettesData ) return ;
var paletteData = palettesData [ id ] ;
if ( ! paletteData ) return 'display: none' ;
// We need at least two colors for a gradient
if ( paletteData . length == 1 ) {
paletteData [ 1 ] = paletteData [ 0 ] ;
if ( Array . isArray ( paletteData [ 1 ] ) ) {
paletteData [ 1 ] [ 0 ] = 255 ;
}
}
var gradient = [ ] ;
for ( let j = 0 ; j < paletteData . length ; j ++ ) {
const e = paletteData [ j ] ;
let r , g , b ;
let index = false ;
if ( Array . isArray ( e ) ) {
2022-07-28 23:19:58 +02:00
index = Math . round ( e [ 0 ] / 255 * 100 ) ;
2022-03-10 22:36:09 +01:00
r = e [ 1 ] ;
g = e [ 2 ] ;
b = e [ 3 ] ;
} else if ( e == 'r' ) {
r = Math . random ( ) * 255 ;
g = Math . random ( ) * 255 ;
b = Math . random ( ) * 255 ;
} else {
let i = e [ 1 ] - 1 ;
var cd = gId ( 'csl' ) . children ;
r = parseInt ( cd [ i ] . dataset . r ) ;
g = parseInt ( cd [ i ] . dataset . g ) ;
b = parseInt ( cd [ i ] . dataset . b ) ;
}
if ( index === false ) {
2022-07-28 23:19:58 +02:00
index = Math . round ( j / paletteData . length * 100 ) ;
2022-03-10 22:36:09 +01:00
}
gradient . push ( ` rgb( ${ r } , ${ g } , ${ b } ) ${ index } % ` ) ;
}
return ` background: linear-gradient(to right, ${ gradient . join ( ) } ); ` ;
}
2022-07-13 12:41:33 +02:00
function generateListItemHtml ( listName , id , name , clickAction , extraHtml = '' , effectPar = '' )
2022-03-10 22:36:09 +01:00
{
2023-05-28 22:50:19 +02:00
return ` <div class="lstI ${ id == 0 ? ' sticky' : '' } " data-id=" ${ id } " ${ effectPar === '' ? '' : 'data-opt="' + effectPar + '" ' } onClick=" ${ clickAction } ( ${ id } )"> ` +
` <label title="( ${ id } )" class="radio schkl" onclick="event.preventDefault()"> ` + // (#1984)
2023-03-30 21:35:23 +02:00
` <input type="radio" value=" ${ id } " name=" ${ listName } "> ` +
` <span class="radiomark"></span> ` +
` <div class="lstIcontent"> ` +
` <span class="lstIname"> ${ name } </span> ` +
` </div> ` +
` </label> ` +
extraHtml +
` </div> ` ;
2022-03-10 22:36:09 +01:00
}
function btype ( b )
{
switch ( b ) {
2023-07-13 22:21:15 +02:00
case 2 :
2022-03-10 22:36:09 +01:00
case 32 : return "ESP32" ;
2023-07-13 22:21:15 +02:00
case 3 :
2022-11-02 14:56:50 +01:00
case 33 : return "ESP32-S2" ;
2023-07-13 22:21:15 +02:00
case 4 :
2022-11-02 14:56:50 +01:00
case 34 : return "ESP32-S3" ;
2023-07-13 22:21:15 +02:00
case 5 :
2022-11-02 14:56:50 +01:00
case 35 : return "ESP32-C3" ;
2023-07-13 22:21:15 +02:00
case 1 :
2022-03-10 22:36:09 +01:00
case 82 : return "ESP8266" ;
}
return "?" ;
}
function bname ( o )
{
if ( o . name == "WLED" ) return o . ip ;
return o . name ;
}
function populateNodes ( i , n )
{
var cn = "" ;
var urows = "" ;
var nnodes = 0 ;
if ( n . nodes ) {
n . nodes . sort ( ( a , b ) => ( a . name ) . localeCompare ( b . name ) ) ;
2022-07-27 17:00:55 +02:00
for ( var o of n . nodes ) {
2022-03-10 22:36:09 +01:00
if ( o . name ) {
2023-07-13 22:21:15 +02:00
let onoff = ` <i class="icons e-icon flr ${ o . type & 0x80 ? '' : 'off' } " onclick="rmtTgl(' ${ o . ip } ',this);""></i> ` ;
var url = ` <button class="btn" title=" ${ o . ip } " onclick="location.assign('http:// ${ o . ip } ');"><div class="bname"> ${ bname ( o ) } </div> ${ o . vid < 2307130 ? '' : onoff } </button> ` ;
urows += inforow ( url , ` ${ btype ( o . type & 0x7F ) } <br><i> ${ o . vid == 0 ? "N/A" : o . vid } </i> ` ) ;
2022-03-10 22:36:09 +01:00
nnodes ++ ;
}
}
}
if ( i . ndc < 0 ) cn += ` Instance List is disabled. ` ;
else if ( nnodes == 0 ) cn += ` No other instances found. ` ;
cn += ` <table>
$ { inforow ( "Current instance:" , i . name ) }
$ { urows }
< / t a b l e > ` ;
gId ( 'kn' ) . innerHTML = cn ;
}
function loadNodes ( )
{
2023-06-04 18:40:29 +02:00
fetch ( getURL ( '/json/nodes' ) , {
2022-03-10 22:36:09 +01:00
method : 'get'
} )
. then ( ( res ) => {
if ( ! res . ok ) showToast ( 'Could not load Node list!' , true ) ;
return res . json ( ) ;
} )
. then ( ( json ) => {
2022-05-20 14:48:40 +02:00
clearErrorToast ( 100 ) ;
2022-03-10 22:36:09 +01:00
populateNodes ( lastinfo , json ) ;
} )
. catch ( ( e ) => {
showToast ( e , true ) ;
} ) ;
}
// update the 'sliderdisplay' background div of a slider for a visual indication of slider position
function updateTrail ( e )
{
if ( e == null ) return ;
2022-03-14 20:22:20 +01:00
let sd = e . parentNode . getElementsByClassName ( 'sliderdisplay' ) [ 0 ] ;
2023-10-21 19:18:45 +02:00
if ( sd && getComputedStyle ( sd ) . getPropertyValue ( "--bg" ) . trim ( ) !== "none" ) { // trim() for Safari
2022-03-14 20:22:20 +01:00
var max = e . hasAttribute ( 'max' ) ? e . attributes . max . value : 255 ;
var perc = Math . round ( e . value * 100 / max ) ;
if ( perc < 50 ) perc += 2 ;
2022-07-27 00:11:24 +02:00
var val = ` linear-gradient(90deg, var(--bg) ${ perc } %, var(--c-6) ${ perc } %) ` ;
2022-03-14 20:22:20 +01:00
sd . style . backgroundImage = val ;
}
2022-03-10 22:36:09 +01:00
var b = e . parentNode . parentNode . getElementsByTagName ( 'output' ) [ 0 ] ;
if ( b ) b . innerHTML = e . value ;
}
// rangetouch slider function
function toggleBubble ( e )
{
var b = e . target . parentNode . parentNode . getElementsByTagName ( 'output' ) [ 0 ] ;
b . classList . toggle ( 'sliderbubbleshow' ) ;
}
// updates segment length upon input of segment values
function updateLen ( s )
{
if ( ! gId ( ` seg ${ s } s ` ) ) return ;
var start = parseInt ( gId ( ` seg ${ s } s ` ) . value ) ;
2023-01-24 16:35:31 +01:00
var stop = parseInt ( gId ( ` seg ${ s } e ` ) . value ) + ( cfg . comp . seglen ? start : 0 ) ;
var len = stop - start ;
2023-02-01 19:30:56 +01:00
let sY = gId ( ` seg ${ s } sY ` ) ;
let eY = gId ( ` seg ${ s } eY ` ) ;
let sX = gId ( ` seg ${ s } s ` ) ;
let eX = gId ( ` seg ${ s } e ` ) ;
let of = gId ( ` seg ${ s } of ` ) ;
let mySH = gId ( "mkSYH" ) ;
let mySD = gId ( "mkSYD" ) ;
2022-05-08 10:50:48 +02:00
if ( isM ) {
2023-02-01 19:30:56 +01:00
// do we have 1D segment *after* the matrix?
if ( start >= mw * mh ) {
if ( sY ) { sY . value = 0 ; sY . max = 0 ; sY . min = 0 ; }
if ( eY ) { eY . value = 1 ; eY . max = 1 ; eY . min = 0 ; }
sX . min = mw * mh ; sX . max = ledCount - 1 ;
eX . min = mw * mh + 1 ; eX . max = ledCount ;
if ( mySH ) mySH . classList . add ( "hide" ) ;
if ( mySD ) mySD . classList . add ( "hide" ) ;
if ( of ) of . classList . remove ( "hide" ) ;
2022-08-22 22:02:36 +02:00
} else {
2023-02-01 19:30:56 +01:00
// matrix setup
if ( mySH ) mySH . classList . remove ( "hide" ) ;
if ( mySD ) mySD . classList . remove ( "hide" ) ;
if ( of ) of . classList . add ( "hide" ) ;
let startY = parseInt ( sY . value ) ;
let stopY = parseInt ( eY . value ) + ( cfg . comp . seglen ? startY : 0 ) ;
len *= ( stopY - startY ) ;
let tPL = gId ( ` seg ${ s } lbtm ` ) ;
if ( stop - start > 1 && stopY - startY > 1 ) {
// 2D segment
if ( tPL ) tPL . classList . remove ( 'hide' ) ; // unhide transpose checkbox
let sE = gId ( 'fxlist' ) . querySelector ( ` .lstI[data-id=" ${ selectedFx } "] ` ) ;
if ( sE ) {
let sN = sE . querySelector ( ".lstIname" ) . innerText ;
let seg = gId ( ` seg ${ s } map2D ` ) ;
if ( seg ) {
if ( sN . indexOf ( "\u25A6" ) < 0 ) seg . classList . remove ( 'hide' ) ; // unhide mapping for 1D effects (| in name)
else seg . classList . add ( 'hide' ) ; // hide mapping otherwise
}
}
} else {
// 1D segment in 2D set-up
if ( tPL ) {
tPL . classList . add ( 'hide' ) ; // hide transpose checkbox
gId ( ` seg ${ s } tp ` ) . checked = false ; // and uncheck it
}
2022-09-06 15:47:41 +02:00
}
2022-08-13 00:58:27 +02:00
}
2022-05-08 10:50:48 +02:00
}
2022-03-10 22:36:09 +01:00
var out = "(delete)" ;
if ( len > 1 ) {
out = ` ${ len } LEDs ` ;
} else if ( len == 1 ) {
out = "1 LED" ;
}
if ( gId ( ` seg ${ s } grp ` ) != null )
{
var grp = parseInt ( gId ( ` seg ${ s } grp ` ) . value ) ;
var spc = parseInt ( gId ( ` seg ${ s } spc ` ) . value ) ;
if ( grp == 0 ) grp = 1 ;
var virt = Math . ceil ( len / ( grp + spc ) ) ;
if ( ! isNaN ( virt ) && ( grp > 1 || spc > 0 ) ) out += ` ( ${ virt } virtual) ` ;
}
2023-02-01 19:30:56 +01:00
if ( isM && start >= mw * mh ) out += " [strip]" ;
2022-03-10 22:36:09 +01:00
gId ( ` seg ${ s } len ` ) . innerHTML = out ;
}
// updates background color of currently selected preset
function updatePA ( )
{
let ps ;
ps = gEBCN ( "pres" ) ; for ( let p of ps ) p . classList . remove ( 'selected' ) ;
ps = gEBCN ( "psts" ) ; for ( let p of ps ) p . classList . remove ( 'selected' ) ;
if ( currentPreset > 0 ) {
var acv = gId ( ` p ${ currentPreset } o ` ) ;
2022-12-23 02:59:24 +01:00
if ( acv /*&& !acv.classList.contains('expanded')*/ ) {
2022-03-10 22:36:09 +01:00
acv . classList . add ( 'selected' ) ;
2022-04-02 19:18:54 +02:00
/ *
2022-03-10 22:36:09 +01:00
// scroll selected preset into view (on WS refresh)
acv . scrollIntoView ( {
behavior : 'smooth' ,
2022-03-26 23:22:18 +01:00
block : 'center'
2022-03-10 22:36:09 +01:00
} ) ;
2022-04-02 19:18:54 +02:00
* /
2022-03-10 22:36:09 +01:00
}
acv = gId ( ` p ${ currentPreset } qlb ` ) ;
if ( acv ) acv . classList . add ( 'selected' ) ;
}
}
function updateUI ( )
{
2022-12-23 02:59:24 +01:00
gId ( 'buttonPower' ) . className = ( isOn ) ? 'active' : '' ;
gId ( 'buttonNl' ) . className = ( nlA ) ? 'active' : '' ;
gId ( 'buttonSync' ) . className = ( syncSend ) ? 'active' : '' ;
2023-10-20 21:32:39 +02:00
gId ( 'pxmb' ) . style . display = ( isM ) ? "inline-block" : "none" ;
2022-03-10 22:36:09 +01:00
updateSelectedFx ( ) ;
2022-12-23 02:59:24 +01:00
updateSelectedPalette ( selectedPal ) ; // must be after updateSelectedFx() to un-hide color slots for * palettes
2022-03-10 22:36:09 +01:00
updateTrail ( gId ( 'sliderBri' ) ) ;
updateTrail ( gId ( 'sliderSpeed' ) ) ;
updateTrail ( gId ( 'sliderIntensity' ) ) ;
updateTrail ( gId ( 'sliderC1' ) ) ;
updateTrail ( gId ( 'sliderC2' ) ) ;
updateTrail ( gId ( 'sliderC3' ) ) ;
2022-03-14 20:22:20 +01:00
if ( hasRGB ) {
updateTrail ( gId ( 'sliderR' ) ) ;
updateTrail ( gId ( 'sliderG' ) ) ;
2023-10-19 10:42:10 +02:00
updateTrail ( gId ( 'sliderB' ) ) ;
2022-03-14 20:22:20 +01:00
}
if ( hasWhite ) updateTrail ( gId ( 'sliderW' ) ) ;
2022-03-10 22:36:09 +01:00
var ccfg = cfg . comp . colors ;
2023-02-25 09:41:15 +01:00
gId ( 'wwrap' ) . style . display = ( hasWhite ) ? "block" : "none" ; // white channel
gId ( 'wbal' ) . style . display = ( hasCCT ) ? "block" : "none" ; // white balance
gId ( 'hexw' ) . style . display = ( ccfg . hex ) ? "block" : "none" ; // HEX input
gId ( 'picker' ) . style . display = ( hasRGB && ccfg . picker ) ? "block" : "none" ; // color picker wheel
gId ( 'hwrap' ) . style . display = ( hasRGB && ! ccfg . picker ) ? "block" : "none" ; // hue slider
gId ( 'swrap' ) . style . display = ( hasRGB && ! ccfg . picker ) ? "block" : "none" ; // saturation slider
gId ( 'vwrap' ) . style . display = ( hasRGB ) ? "block" : "none" ; // brightness (value) slider
gId ( 'kwrap' ) . style . display = ( hasRGB && ! hasCCT ) ? "block" : "none" ; // Kelvin slider
gId ( 'rgbwrap' ) . style . display = ( hasRGB && ccfg . rgb ) ? "block" : "none" ; // RGB sliders
gId ( 'qcs-w' ) . style . display = ( hasRGB && ccfg . quick ) ? "block" : "none" ; // quick selection
2023-03-02 18:21:55 +01:00
//gId('csl').style.display = (hasRGB || hasWhite) ? "block":"none"; // color selectors (hide for On/Off bus)
2023-02-25 09:41:15 +01:00
//gId('palw').style.display = (hasRGB) ? "inline-block":"none"; // palettes are shown/hidden in setEffectParameters()
2022-03-10 22:36:09 +01:00
updatePA ( ) ;
updatePSliders ( ) ;
}
2022-12-23 02:59:24 +01:00
function updateSelectedPalette ( s )
2022-03-10 22:36:09 +01:00
{
var parent = gId ( 'pallist' ) ;
2022-12-23 02:59:24 +01:00
var selPaletteInput = parent . querySelector ( ` input[name="palette"][value=" ${ s } "] ` ) ;
2022-03-10 22:36:09 +01:00
if ( selPaletteInput ) selPaletteInput . checked = true ;
var selElement = parent . querySelector ( '.selected' ) ;
if ( selElement ) selElement . classList . remove ( 'selected' ) ;
2022-12-23 02:59:24 +01:00
var selectedPalette = parent . querySelector ( ` .lstI[data-id=" ${ s } "] ` ) ;
if ( selectedPalette ) parent . querySelector ( ` .lstI[data-id=" ${ s } "] ` ) . classList . add ( 'selected' ) ;
// in case of special palettes (* Colors...), force show color selectors (if hidden by effect data)
let cd = gId ( 'csl' ) . children ; // color selectors
if ( s > 1 && s < 6 ) {
cd [ 0 ] . classList . remove ( 'hide' ) ; // * Color 1
if ( s > 2 ) cd [ 1 ] . classList . remove ( 'hide' ) ; // * Color 1 & 2
2023-09-16 12:30:57 +02:00
if ( s > 3 ) cd [ 2 ] . classList . remove ( 'hide' ) ; // all colors
2022-12-23 02:59:24 +01:00
} else {
for ( let i of cd ) if ( i . dataset . hide == '1' ) i . classList . add ( 'hide' ) ;
}
2022-03-10 22:36:09 +01:00
}
function updateSelectedFx ( )
{
var parent = gId ( 'fxlist' ) ;
var selEffectInput = parent . querySelector ( ` input[name="fx"][value=" ${ selectedFx } "] ` ) ;
if ( selEffectInput ) selEffectInput . checked = true ;
var selElement = parent . querySelector ( '.selected' ) ;
2022-07-21 20:18:48 +02:00
if ( selElement ) {
selElement . classList . remove ( 'selected' ) ;
selElement . style . bottom = null ; // remove element style added in slider handling
}
2022-03-10 22:36:09 +01:00
var selectedEffect = parent . querySelector ( ` .lstI[data-id=" ${ selectedFx } "] ` ) ;
if ( selectedEffect ) {
selectedEffect . classList . add ( 'selected' ) ;
2022-07-23 22:00:19 +02:00
setEffectParameters ( selectedFx ) ;
2023-02-25 09:41:15 +01:00
// hide non-0D effects if segment only has 1 pixel (0D)
2023-11-04 09:39:08 +01:00
parent . querySelectorAll ( '.lstI' ) . forEach ( ( fx ) => {
let ds = fx . dataset ;
if ( ds . opt ) {
let opts = ds . opt . split ( ";" ) ;
if ( ds . id > 0 ) {
if ( segLmax == 0 ) fx . classList . add ( 'hide' ) ; // none of the segments selected (hide all effects)
else {
if ( ( segLmax == 1 && ( ! opts [ 3 ] || opts [ 3 ] . indexOf ( "0" ) < 0 ) ) || ( ! isM && opts [ 3 ] && ( ( opts [ 3 ] . indexOf ( "2" ) >= 0 && opts [ 3 ] . indexOf ( "1" ) < 0 ) ) ) ) fx . classList . add ( 'hide' ) ;
else fx . classList . remove ( 'hide' ) ;
}
2023-03-02 18:21:55 +01:00
}
}
2023-11-04 09:39:08 +01:00
} ) ;
2023-02-25 09:41:15 +01:00
// hide 2D mapping and/or sound simulation options
2022-07-20 21:22:23 +02:00
var selectedName = selectedEffect . querySelector ( ".lstIname" ) . innerText ;
var segs = gId ( "segcont" ) . querySelectorAll ( ` div[data-map="map2D"] ` ) ;
2022-12-23 02:59:24 +01:00
for ( const seg of segs ) if ( selectedName . indexOf ( "\u25A6" ) < 0 ) seg . classList . remove ( 'hide' ) ; else seg . classList . add ( 'hide' ) ;
2022-11-24 04:15:24 +01:00
var segs = gId ( "segcont" ) . querySelectorAll ( ` div[data-snd="si"] ` ) ;
2023-05-30 13:23:26 +02:00
for ( const seg of segs ) if ( selectedName . indexOf ( "\u266A" ) < 0 && selectedName . indexOf ( "\u266B" ) < 0 ) seg . classList . add ( 'hide' ) ; else seg . classList . remove ( 'hide' ) ; // also "♫ "?
2022-03-10 22:36:09 +01:00
}
}
function displayRover ( i , s )
{
2022-03-28 22:36:58 +02:00
gId ( 'rover' ) . style . transform = ( i . live && s . lor == 0 && i . liveseg < 0 ) ? "translateY(0px)" : "translateY(100%)" ;
2022-03-10 22:36:09 +01:00
var sour = i . lip ? i . lip : "" ; if ( sour . length > 2 ) sour = " from " + sour ;
gId ( 'lv' ) . innerHTML = ` WLED is receiving live ${ i . lm } data ${ sour } ` ;
gId ( 'roverstar' ) . style . display = ( i . live && s . lor ) ? "block" : "none" ;
}
function cmpP ( a , b )
{
2023-11-04 09:39:08 +01:00
if ( cfg . comp . idsort || ! a [ 1 ] . n ) return ( parseInt ( a [ 0 ] ) > parseInt ( b [ 0 ] ) ) ;
2022-03-10 22:36:09 +01:00
// sort playlists first, followed by presets with characters and last presets with special 1st character
const c = a [ 1 ] . n . charCodeAt ( 0 ) ;
const d = b [ 1 ] . n . charCodeAt ( 0 ) ;
if ( ( c > 47 && c < 58 ) || ( c > 64 && c < 91 ) || ( c > 96 && c < 123 ) || c > 255 ) x = '=' ; else x = '>' ;
if ( ( d > 47 && d < 58 ) || ( d > 64 && d < 91 ) || ( d > 96 && d < 123 ) || d > 255 ) y = '=' ; else y = '>' ;
const n = ( a [ 1 ] . playlist ? '<' : x ) + a [ 1 ] . n ;
return n . localeCompare ( ( b [ 1 ] . playlist ? '<' : y ) + b [ 1 ] . n , undefined , { numeric : true } ) ;
}
function makeWS ( ) {
if ( ws || lastinfo . ws < 0 ) return ;
2023-06-04 18:40:29 +02:00
let url = loc ? getURL ( '/ws' ) . replace ( "http" , "ws" ) : "ws://" + window . location . hostname + "/ws" ;
ws = new WebSocket ( url ) ;
2022-03-10 22:36:09 +01:00
ws . binaryType = "arraybuffer" ;
ws . onmessage = ( e ) => {
2022-04-17 11:13:13 +02:00
if ( e . data instanceof ArrayBuffer ) return ; // liveview packet
2022-03-10 22:36:09 +01:00
var json = JSON . parse ( e . data ) ;
if ( json . leds ) return ; // JSON liveview packet
clearTimeout ( jsonTimeout ) ;
jsonTimeout = null ;
lastUpdate = new Date ( ) ;
clearErrorToast ( ) ;
gId ( 'connind' ) . style . backgroundColor = "var(--c-l)" ;
// json object should contain json.info AND json.state (but may not)
var i = json . info ;
if ( i ) {
2022-03-28 22:36:58 +02:00
parseInfo ( i ) ;
2022-03-10 22:36:09 +01:00
if ( isInfo ) populateInfo ( i ) ;
} else
i = lastinfo ;
var s = json . state ? json . state : json ;
displayRover ( i , s ) ;
readState ( s ) ;
} ;
ws . onclose = ( e ) => {
gId ( 'connind' ) . style . backgroundColor = "var(--c-r)" ;
2023-06-27 01:51:24 +02:00
if ( wsRpt ++ < 5 ) setTimeout ( makeWS , 1500 ) ; // retry WS connection
2022-03-10 22:36:09 +01:00
ws = null ;
}
ws . onopen = ( e ) => {
//ws.send("{'v':true}"); // unnecessary (https://github.com/Aircoookie/WLED/blob/master/wled00/ws.cpp#L18)
2023-06-27 01:51:24 +02:00
wsRpt = 0 ;
2022-03-10 22:36:09 +01:00
reqsLegal = true ;
}
}
function readState ( s , command = false )
{
if ( ! s ) return false ;
2022-07-19 16:16:43 +02:00
if ( s . success ) return true ; // no data to process
2022-03-10 22:36:09 +01:00
isOn = s . on ;
2022-03-26 23:22:18 +01:00
gId ( 'sliderBri' ) . value = s . bri ;
2022-03-10 22:36:09 +01:00
nlA = s . nl . on ;
nlDur = s . nl . dur ;
nlTar = s . nl . tbri ;
nlFade = s . nl . fade ;
syncSend = s . udpn . send ;
if ( s . pl < 0 ) currentPreset = s . ps ;
else currentPreset = s . pl ;
tr = s . transition ;
gId ( 'tt' ) . value = tr / 10 ;
2022-08-24 23:04:51 +02:00
2022-03-10 22:36:09 +01:00
populateSegments ( s ) ;
var selc = 0 ;
var sellvl = 0 ; // 0: selc is invalid, 1: selc is mainseg, 2: selc is first selected
hasRGB = hasWhite = hasCCT = false ;
2023-02-25 09:41:15 +01:00
segLmax = 0 ;
2022-03-10 22:36:09 +01:00
for ( let i = 0 ; i < ( s . seg || [ ] ) . length ; i ++ )
{
if ( sellvl == 0 && s . seg [ i ] . id == s . mainseg ) {
selc = i ;
sellvl = 1 ;
}
if ( s . seg [ i ] . sel ) {
if ( sellvl < 2 ) selc = i ; // get first selected segment
sellvl = 2 ;
var lc = lastinfo . leds . seglc [ s . seg [ i ] . id ] ;
hasRGB |= ! ! ( lc & 0x01 ) ;
2023-02-14 01:33:06 +01:00
hasWhite |= ! ! ( lc & 0x02 ) ;
2022-03-10 22:36:09 +01:00
hasCCT |= ! ! ( lc & 0x04 ) ;
2023-02-28 15:25:11 +01:00
let sLen = ( s . seg [ i ] . stop - s . seg [ i ] . start ) * ( s . seg [ i ] . stopY ? ( s . seg [ i ] . stopY - s . seg [ i ] . startY ) : 1 ) ;
2023-02-25 09:41:15 +01:00
segLmax = segLmax < sLen ? sLen : segLmax ;
2022-03-10 22:36:09 +01:00
}
}
var i = s . seg [ selc ] ;
if ( sellvl == 1 ) {
var lc = lastinfo . leds . seglc [ i . id ] ;
hasRGB = ! ! ( lc & 0x01 ) ;
2023-02-14 01:33:06 +01:00
hasWhite = ! ! ( lc & 0x02 ) ;
2022-03-10 22:36:09 +01:00
hasCCT = ! ! ( lc & 0x04 ) ;
}
if ( ! i ) {
showToast ( 'No Segments!' , true ) ;
updateUI ( ) ;
2022-07-19 16:16:43 +02:00
return true ;
2022-03-10 22:36:09 +01:00
}
2022-08-24 23:04:51 +02:00
2023-04-02 18:05:59 +02:00
if ( s . seg . length > 2 ) d . querySelectorAll ( ".pop" ) . forEach ( ( e ) => { e . classList . remove ( "hide" ) ; } ) ;
2023-10-28 21:02:49 +02:00
var cd = gId ( 'csl' ) . querySelectorAll ( "button" ) ;
2022-03-10 22:36:09 +01:00
for ( let e = cd . length - 1 ; e >= 0 ; e -- ) {
cd [ e ] . dataset . r = i . col [ e ] [ 0 ] ;
cd [ e ] . dataset . g = i . col [ e ] [ 1 ] ;
cd [ e ] . dataset . b = i . col [ e ] [ 2 ] ;
2023-03-02 18:21:55 +01:00
if ( hasWhite || ( ! hasRGB && ! hasWhite ) ) { cd [ e ] . dataset . w = i . col [ e ] [ 3 ] ; }
2022-03-10 22:36:09 +01:00
setCSL ( cd [ e ] ) ;
}
selectSlot ( csel ) ;
if ( i . cct != null && i . cct >= 0 ) gId ( "sliderA" ) . value = i . cct ;
gId ( 'sliderSpeed' ) . value = i . sx ;
gId ( 'sliderIntensity' ) . value = i . ix ;
2022-05-21 13:19:11 +02:00
gId ( 'sliderC1' ) . value = i . c1 ? i . c1 : 0 ;
gId ( 'sliderC2' ) . value = i . c2 ? i . c2 : 0 ;
gId ( 'sliderC3' ) . value = i . c3 ? i . c3 : 0 ;
2022-12-08 17:17:54 +01:00
gId ( 'checkO1' ) . checked = ! ( ! i . o1 ) ;
gId ( 'checkO2' ) . checked = ! ( ! i . o2 ) ;
gId ( 'checkO3' ) . checked = ! ( ! i . o3 ) ;
2022-03-10 22:36:09 +01:00
if ( s . error && s . error != 0 ) {
var errstr = "" ;
switch ( s . error ) {
case 10 :
errstr = "Could not mount filesystem!" ;
break ;
case 11 :
errstr = "Not enough space to save preset!" ;
break ;
case 12 :
errstr = "Preset not found." ;
break ;
case 13 :
errstr = "Missing ir.json." ;
break ;
case 19 :
errstr = "A filesystem error has occured." ;
break ;
}
showToast ( 'Error ' + s . error + ": " + errstr , true ) ;
}
selectedPal = i . pal ;
selectedFx = i . fx ;
redrawPalPrev ( ) ; // if any color changed (random palette did at least)
updateUI ( ) ;
2022-07-19 16:16:43 +02:00
return true ;
2022-03-10 22:36:09 +01:00
}
2023-01-11 23:08:08 +01:00
// control HTML elements for Slider and Color Control (original ported form WLED-SR)
2022-03-10 22:36:09 +01:00
// Technical notes
// ===============
// If an effect name is followed by an @, slider and color control is effective.
// If not effective then:
// - For AC effects (id<128) 2 sliders and 3 colors and the palette will be shown
// - For SR effects (id>128) 5 sliders and 3 colors and the palette will be shown
// If effective (@)
// - a ; seperates slider controls (left) from color controls (middle) and palette control (right)
// - if left, middle or right is empty no controls are shown
// - a , seperates slider controls (max 5) or color controls (max 3). Palette has only one value
// - a ! means that the default is used.
// - For sliders: Effect speeds, Effect intensity, Custom 1, Custom 2, Custom 3
// - For colors: Fx color, Background color, Custom
// - For palette: prompt for color palette OR palette ID if numeric (will hide palette selection)
//
// Note: If palette is on and no colors are specified 1,2 and 3 is shown in each color circle.
// If a color is specified, the 1,2 or 3 is replaced by that specification.
// Note: Effects can override default pattern behaviour
// - FadeToBlack can override the background setting
// - Defining SEGCOL(<i>) can override a specific palette using these values (e.g. Color Gradient)
2022-07-23 22:00:19 +02:00
function setEffectParameters ( idx )
2022-03-10 22:36:09 +01:00
{
if ( ! ( Array . isArray ( fxdata ) && fxdata . length > idx ) ) return ;
2022-11-11 03:10:41 +01:00
var controlDefined = fxdata [ idx ] . length ;
var effectPar = fxdata [ idx ] ;
2022-07-13 12:41:33 +02:00
var effectPars = ( effectPar == '' ) ? [ ] : effectPar . split ( ";" ) ;
var slOnOff = ( effectPars . length == 0 || effectPars [ 0 ] == '' ) ? [ ] : effectPars [ 0 ] . split ( "," ) ;
var coOnOff = ( effectPars . length < 2 || effectPars [ 1 ] == '' ) ? [ ] : effectPars [ 1 ] . split ( "," ) ;
var paOnOff = ( effectPars . length < 3 || effectPars [ 2 ] == '' ) ? [ ] : effectPars [ 2 ] . split ( "," ) ;
2022-08-24 23:04:51 +02:00
2022-03-10 22:36:09 +01:00
// set html slider items on/off
2023-10-20 21:32:39 +02:00
let sliders = d . querySelectorAll ( "#sliders .sliderwrap" ) ;
sliders . forEach ( ( slider , i ) => {
let text = slider . getAttribute ( "tooltip" ) ;
2023-10-21 00:13:45 +02:00
if ( ( ! controlDefined && i < ( ( idx < 128 ) ? 2 : nSliders ) ) || ( slOnOff . length > i && slOnOff [ i ] != "" ) ) {
if ( slOnOff . length > i && slOnOff [ i ] != "!" ) text = slOnOff [ i ] ;
2023-10-20 21:32:39 +02:00
slider . setAttribute ( "tooltip" , text ) ;
slider . parentElement . classList . remove ( 'hide' ) ;
} else
slider . parentElement . classList . add ( 'hide' ) ;
} ) ;
2023-10-20 14:27:00 +02:00
2023-10-20 17:20:49 +02:00
if ( slOnOff . length > 5 ) { // up to 3 checkboxes
2022-08-12 17:58:20 +02:00
gId ( 'fxopt' ) . classList . remove ( 'fade' ) ;
2023-10-20 21:32:39 +02:00
let checks = d . querySelectorAll ( "#sliders .ochkl" ) ;
checks . forEach ( ( check , i ) => {
let text = check . getAttribute ( "tooltip" ) ;
2022-09-21 21:09:52 +02:00
if ( 5 + i < slOnOff . length && slOnOff [ 5 + i ] !== '' ) {
2023-10-21 00:13:45 +02:00
if ( slOnOff . length > 5 + i && slOnOff [ 5 + i ] != "!" ) text = slOnOff [ 5 + i ] ;
2023-10-20 21:32:39 +02:00
check . setAttribute ( "tooltip" , text ) ;
2023-10-20 17:20:49 +02:00
check . classList . remove ( 'hide' ) ;
2022-08-12 17:58:20 +02:00
} else
2023-10-20 21:32:39 +02:00
check . classList . add ( 'hide' ) ;
} ) ;
} else gId ( 'fxopt' ) . classList . add ( 'fade' ) ;
2022-04-10 11:02:57 +02:00
2022-06-28 12:36:23 +02:00
// set the bottom position of selected effect (sticky) as the top of sliders div
2023-10-20 11:25:07 +02:00
function setSelectedEffectPosition ( ) {
2022-08-12 17:58:20 +02:00
let top = parseInt ( getComputedStyle ( gId ( "sliders" ) ) . height ) ;
top += 5 ;
let sel = d . querySelector ( '#fxlist .selected' ) ;
2022-08-22 01:17:10 +02:00
if ( sel ) sel . style . bottom = top + "px" ; // we will need to remove this when unselected (in setFX())
2023-10-20 11:25:07 +02:00
}
setSelectedEffectPosition ( ) ;
setInterval ( setSelectedEffectPosition , 750 ) ;
2022-03-10 22:36:09 +01:00
// set html color items on/off
var cslLabel = '' ;
var sep = '' ;
2022-07-07 23:07:20 +02:00
var cslCnt = 0 , oCsel = csel ;
2023-11-04 09:39:08 +01:00
// for (let i=0; i<gId("csl").querySelectorAll("button"); i++) {
d . querySelectorAll ( "#csl button" ) . forEach ( ( e , i ) => {
2022-03-10 22:36:09 +01:00
var btn = gId ( "csl" + i ) ;
// if no controlDefined or coOnOff has a value
if ( coOnOff . length > i && coOnOff [ i ] != "" ) {
2022-12-23 02:59:24 +01:00
btn . classList . remove ( 'hide' ) ;
btn . dataset . hide = 0 ;
if ( coOnOff [ i ] != "!" ) {
2022-03-10 22:36:09 +01:00
var abbreviation = coOnOff [ i ] . substr ( 0 , 2 ) ;
btn . innerHTML = abbreviation ;
if ( abbreviation != coOnOff [ i ] ) {
cslLabel += sep + abbreviation + '=' + coOnOff [ i ] ;
sep = ', ' ;
}
}
else if ( i == 0 ) btn . innerHTML = "Fx" ;
else if ( i == 1 ) btn . innerHTML = "Bg" ;
else btn . innerHTML = "Cs" ;
2022-07-07 23:07:20 +02:00
if ( ! cslCnt || oCsel == i ) selectSlot ( i ) ; // select 1st displayed slot or old one
2022-06-17 16:18:35 +02:00
cslCnt ++ ;
2022-11-11 03:10:41 +01:00
} else if ( ! controlDefined ) { // if no controls then all buttons should be shown for color 1..3
2022-12-23 02:59:24 +01:00
btn . classList . remove ( 'hide' ) ;
btn . dataset . hide = 0 ;
2022-03-10 22:36:09 +01:00
btn . innerHTML = ` ${ i + 1 } ` ;
2022-07-07 23:07:20 +02:00
if ( ! cslCnt || oCsel == i ) selectSlot ( i ) ; // select 1st displayed slot or old one
2022-06-17 16:18:35 +02:00
cslCnt ++ ;
2022-03-10 22:36:09 +01:00
} else {
2022-12-23 02:59:24 +01:00
btn . classList . add ( 'hide' ) ;
btn . dataset . hide = 1 ;
btn . innerHTML = ` ${ i + 1 } ` ; // name hidden buttons 1..3 for * palettes
2022-03-10 22:36:09 +01:00
}
2023-11-04 09:39:08 +01:00
} ) ;
2022-03-10 22:36:09 +01:00
gId ( "cslLabel" ) . innerHTML = cslLabel ;
2023-10-16 18:34:51 +02:00
if ( cslLabel !== "" ) gId ( "cslLabel" ) . classList . remove ( "hide" ) ;
else gId ( "cslLabel" ) . classList . add ( "hide" ) ;
2022-08-24 23:04:51 +02:00
2022-03-10 22:36:09 +01:00
// set palette on/off
var palw = gId ( "palw" ) ; // wrapper
var pall = gId ( "pall" ) ; // label
2023-10-16 18:34:51 +02:00
var icon = '<i class="icons sel-icon" onclick="tglHex()"></i> ' ;
var text = 'Color palette' ;
2022-03-10 22:36:09 +01:00
// if not controlDefined or palette has a value
2023-02-25 09:41:15 +01:00
if ( hasRGB && ( ( ! controlDefined ) || ( paOnOff . length > 0 && paOnOff [ 0 ] != "" && isNaN ( paOnOff [ 0 ] ) ) ) ) {
palw . style . display = "inline-block" ;
2022-03-10 22:36:09 +01:00
if ( paOnOff . length > 0 && paOnOff [ 0 ] . indexOf ( "=" ) > 0 ) {
// embeded default values
var dPos = paOnOff [ 0 ] . indexOf ( "=" ) ;
var v = Math . max ( 0 , Math . min ( 255 , parseInt ( paOnOff [ 0 ] . substr ( dPos + 1 ) ) ) ) ;
paOnOff [ 0 ] = paOnOff [ 0 ] . substring ( 0 , dPos ) ;
}
2023-10-16 18:34:51 +02:00
if ( paOnOff . length > 0 && paOnOff [ 0 ] != "!" ) text = paOnOff [ 0 ] ;
2022-03-10 22:36:09 +01:00
} else {
2022-11-11 03:10:41 +01:00
// disable palette list
2023-10-16 18:34:51 +02:00
text += ' not used' ;
2022-03-10 22:36:09 +01:00
palw . style . display = "none" ;
}
2023-10-16 18:34:51 +02:00
pall . innerHTML = icon + text ;
2023-01-11 23:08:08 +01:00
// not all color selectors shown, hide palettes created from color selectors
// NOTE: this will disallow user to select "* Color ..." palettes which may be undesirable in some cases or for some users
//for (let e of (gId('pallist').querySelectorAll('.lstI')||[])) {
// let fltr = "* C";
// if (cslCnt==1 && csel==0) fltr = "* Colors";
// else if (cslCnt==2) fltr = "* Colors Only";
// if (cslCnt < 3 && e.querySelector('.lstIname').innerText.indexOf(fltr)>=0) e.classList.add('hide'); else e.classList.remove('hide');
//}
2022-03-10 22:36:09 +01:00
}
var jsonTimeout ;
var reqsLegal = false ;
function requestJson ( command = null )
{
2022-04-17 11:13:13 +02:00
gId ( 'connind' ) . style . backgroundColor = "var(--c-y)" ;
2022-03-10 22:36:09 +01:00
if ( command && ! reqsLegal ) return ; // stop post requests from chrome onchange event on page restore
if ( ! jsonTimeout ) jsonTimeout = setTimeout ( ( ) => { if ( ws ) ws . close ( ) ; ws = null ; showErrorToast ( ) } , 3000 ) ;
var req = null ;
var useWs = ( ws && ws . readyState === WebSocket . OPEN ) ;
var type = command ? 'post' : 'get' ;
if ( command ) {
2022-04-24 19:30:14 +02:00
command . v = true ; // force complete /json/si API response
2022-03-10 22:36:09 +01:00
command . time = Math . floor ( Date . now ( ) / 1000 ) ;
var t = gId ( 'tt' ) ;
if ( t . validity . valid && command . transition == null ) {
var tn = parseInt ( t . value * 10 ) ;
if ( tn != tr ) command . transition = tn ;
}
req = JSON . stringify ( command ) ;
2022-10-25 03:27:16 +02:00
if ( req . length > 1340 ) useWs = false ; // do not send very long requests over websocket
if ( req . length > 500 && lastinfo && lastinfo . arch == "esp8266" ) useWs = false ; // esp8266 can only handle 500 bytes
2022-03-10 22:36:09 +01:00
} ;
if ( useWs ) {
ws . send ( req ? req : '{"v":true}' ) ;
return ;
}
2023-06-04 18:40:29 +02:00
fetch ( getURL ( '/json/si' ) , {
2022-03-10 22:36:09 +01:00
method : type ,
headers : {
"Content-type" : "application/json; charset=UTF-8"
} ,
body : req
} )
. then ( res => {
clearTimeout ( jsonTimeout ) ;
jsonTimeout = null ;
if ( ! res . ok ) showErrorToast ( ) ;
return res . json ( ) ;
} )
. then ( json => {
lastUpdate = new Date ( ) ;
2022-05-20 14:48:40 +02:00
clearErrorToast ( 3000 ) ;
2022-03-10 22:36:09 +01:00
gId ( 'connind' ) . style . backgroundColor = "var(--c-g)" ;
if ( ! json ) { showToast ( 'Empty response' , true ) ; return ; }
if ( json . success ) return ;
if ( json . info ) {
2022-07-28 23:19:58 +02:00
let i = json . info ;
parseInfo ( i ) ;
2023-04-14 17:15:02 +02:00
populatePalettes ( i ) ;
2022-07-28 23:19:58 +02:00
if ( isInfo ) populateInfo ( i ) ;
2022-03-10 22:36:09 +01:00
}
var s = json . state ? json . state : json ;
readState ( s ) ;
2022-04-27 12:31:47 +02:00
//load presets and open websocket sequentially
2022-06-05 10:16:56 +02:00
if ( ! pJson || isEmpty ( pJson ) ) setTimeout ( ( ) => {
2022-04-17 11:13:13 +02:00
loadPresets ( ( ) => {
2023-06-27 01:51:24 +02:00
wsRpt = 0 ;
2022-04-17 11:13:13 +02:00
if ( ! ( ws && ws . readyState === WebSocket . OPEN ) ) makeWS ( ) ;
} ) ;
} , 25 ) ;
2022-03-10 22:36:09 +01:00
reqsLegal = true ;
} )
. catch ( ( e ) => {
showToast ( e , true ) ;
} ) ;
}
function togglePower ( )
{
isOn = ! isOn ;
var obj = { "on" : isOn } ;
2022-03-28 23:08:29 +02:00
if ( isOn && lastinfo && lastinfo . live && lastinfo . liveseg >= 0 ) {
obj . live = false ;
obj . seg = [ ] ;
obj . seg [ 0 ] = { "id" : lastinfo . liveseg , "frz" : false } ;
}
2023-10-19 18:54:54 +02:00
if ( cfg . comp . on > 0 && isOn ) obj = { "ps" : cfg . comp . on } ; // don't use setPreset()
if ( cfg . comp . off > 0 && ! isOn ) obj = { "ps" : cfg . comp . off } ; // don't use setPreset()
2022-03-10 22:36:09 +01:00
requestJson ( obj ) ;
}
function toggleNl ( )
{
nlA = ! nlA ;
if ( nlA )
{
showToast ( ` Timer active. Your light will turn ${ nlTar > 0 ? "on" : "off" } ${ nlMode ? "over" : "after" } ${ nlDur } minutes. ` ) ;
} else {
showToast ( 'Timer deactivated.' ) ;
}
var obj = { "nl" : { "on" : nlA } } ;
requestJson ( obj ) ;
}
function toggleSync ( )
{
syncSend = ! syncSend ;
if ( syncSend ) showToast ( 'Other lights in the network will now sync to this one.' ) ;
else showToast ( 'This light and other lights in the network will no longer sync.' ) ;
var obj = { "udpn" : { "send" : syncSend } } ;
2023-09-10 18:52:14 +02:00
//if (syncTglRecv) obj.udpn.recv = syncSend;
2022-03-10 22:36:09 +01:00
requestJson ( obj ) ;
}
function toggleLiveview ( )
{
2022-08-24 23:04:51 +02:00
if ( isInfo && isM ) toggleInfo ( ) ;
if ( isNodes && isM ) toggleNodes ( ) ;
isLv = ! isLv ;
2023-06-27 01:51:24 +02:00
let wsOn = ws && ws . readyState === WebSocket . OPEN ;
2022-08-24 23:04:51 +02:00
var lvID = "liveview" ;
2023-10-19 10:42:10 +02:00
if ( isM && wsOn ) {
2023-06-27 01:51:24 +02:00
lvID += "2D" ;
if ( isLv ) gId ( 'klv2D' ) . innerHTML = ` <iframe id=" ${ lvID } " src="about:blank"></iframe> ` ;
gId ( 'mlv2D' ) . style . transform = ( isLv ) ? "translateY(0px)" : "translateY(100%)" ;
2022-07-26 11:08:26 +02:00
}
2022-08-24 23:04:51 +02:00
gId ( lvID ) . style . display = ( isLv ) ? "block" : "none" ;
2023-06-27 01:51:24 +02:00
gId ( lvID ) . src = ( isLv ) ? getURL ( "/" + lvID + ( ( wsOn ) ? "?ws" : "" ) ) : "about:blank" ;
gId ( 'buttonSr' ) . classList . toggle ( "active" ) ;
if ( ! isLv && wsOn ) ws . send ( '{"lv":false}' ) ;
2022-08-24 23:04:51 +02:00
size ( ) ;
2022-03-10 22:36:09 +01:00
}
function toggleInfo ( )
{
if ( isNodes ) toggleNodes ( ) ;
2022-07-28 16:35:57 +02:00
if ( isLv && isM ) toggleLiveview ( ) ;
2022-03-10 22:36:09 +01:00
isInfo = ! isInfo ;
if ( isInfo ) requestJson ( ) ;
gId ( 'info' ) . style . transform = ( isInfo ) ? "translateY(0px)" : "translateY(100%)" ;
gId ( 'buttonI' ) . className = ( isInfo ) ? "active" : "" ;
}
function toggleNodes ( )
{
if ( isInfo ) toggleInfo ( ) ;
2022-07-28 16:35:57 +02:00
if ( isLv && isM ) toggleLiveview ( ) ;
2022-03-10 22:36:09 +01:00
isNodes = ! isNodes ;
if ( isNodes ) loadNodes ( ) ;
gId ( 'nodes' ) . style . transform = ( isNodes ) ? "translateY(0px)" : "translateY(100%)" ;
gId ( 'buttonNodes' ) . className = ( isNodes ) ? "active" : "" ;
}
function makeSeg ( )
{
2023-01-22 11:29:31 +01:00
var ns = 0 , ct = 0 ;
2022-03-10 22:36:09 +01:00
var lu = lowestUnused ;
2022-05-08 10:50:48 +02:00
let li = lastinfo ;
2022-03-10 22:36:09 +01:00
if ( lu > 0 ) {
2023-01-22 11:29:31 +01:00
let xend = parseInt ( gId ( ` seg ${ lu - 1 } e ` ) . value , 10 ) + ( cfg . comp . seglen ? parseInt ( gId ( ` seg ${ lu - 1 } s ` ) . value , 10 ) : 0 ) ;
if ( isM ) {
ns = 0 ;
ct = mw ;
} else {
if ( xend < ledCount ) ns = xend ;
ct = ledCount - ( cfg . comp . seglen ? ns : 0 )
}
2022-03-10 22:36:09 +01:00
}
gId ( 'segutil' ) . scrollIntoView ( {
behavior : 'smooth' ,
block : 'start' ,
} ) ;
2023-03-30 21:35:23 +02:00
var cn = ` <div class="seg lstI expanded"> ` +
` <div class="segin"> ` +
` <input type="text" id="seg ${ lu } t" autocomplete="off" maxlength=32 value="" placeholder="New segment ${ lu } "/> ` +
` <table class="segt"> ` +
` <tr> ` +
` <td width="38%"> ${ isM ? 'Start X' : 'Start LED' } </td> ` +
` <td width="38%"> ${ isM ? ( cfg . comp . seglen ? "Width" : "Stop X" ) : ( cfg . comp . seglen ? "LED count" : "Stop LED" ) } </td> ` +
` </tr> ` +
` <tr> ` +
` <td><input class="segn" id="seg ${ lu } s" type="number" min="0" max=" ${ isM ? mw - 1 : ledCount - 1 } " value=" ${ ns } " oninput="updateLen( ${ lu } )" onkeydown="segEnter( ${ lu } )"></td> ` +
` <td><input class="segn" id="seg ${ lu } e" type="number" min="0" max=" ${ ct } " value=" ${ ct } " oninput="updateLen( ${ lu } )" onkeydown="segEnter( ${ lu } )"></td> ` +
` <td><button class="btn btn-xs" onclick="setSeg( ${ lu } );"><i class="icons bth-icon" id="segc ${ lu } "></i></button></td> ` +
` </tr> ` +
` <tr id="mkSYH" class=" ${ isM ? "" : "hide" } "><td>Start Y</td><td> ${ cfg . comp . seglen ? 'Height' : 'Stop Y' } </td></tr> ` +
` <tr id="mkSYD" class=" ${ isM ? "" : "hide" } "> ` +
` <td><input class="segn" id="seg ${ lu } sY" type="number" min="0" max=" ${ mh - 1 } " value="0" oninput="updateLen( ${ lu } )" onkeydown="segEnter( ${ lu } )"></td> ` +
` <td><input class="segn" id="seg ${ lu } eY" type="number" min="0" max=" ${ mh } " value=" ${ isM ? mh : 1 } " oninput="updateLen( ${ lu } )" onkeydown="segEnter( ${ lu } )"></td> ` +
` </tr> ` +
` </table> ` +
` <div class="h" id="seg ${ lu } len"> ${ ledCount - ns } LEDs</div> ` +
` <div class="c"><button class="btn btn-p" onclick="resetUtil()">Cancel</button></div> ` +
` </div> ` +
` </div> ` ;
2022-03-10 22:36:09 +01:00
gId ( 'segutil' ) . innerHTML = cn ;
}
2022-12-09 08:15:14 +01:00
function resetUtil ( off = false )
2022-03-10 22:36:09 +01:00
{
2023-04-04 15:53:03 +02:00
gId ( 'segutil' ) . innerHTML = ` <div class="seg btn btn-s ${ off ? ' off' : '' } " style="padding:0;"> `
2022-07-27 00:11:24 +02:00
+ '<label class="check schkl"><input type="checkbox" id="selall" onchange="selSegAll(this)"><span class="checkmark"></span></label>'
2023-04-01 23:40:43 +02:00
+ ` <div class="segname" ${ off ? '' : 'onclick="makeSeg()"' } ><i class="icons btn-icon"></i>Add segment</div> `
2023-04-02 18:05:59 +02:00
+ '<div class="pop hide" onclick="event.stopPropagation();">'
2023-04-04 15:53:03 +02:00
+ ` <i class="icons g-icon" onclick="this.nextElementSibling.classList.toggle('hide');"></i> `
2023-04-02 18:05:59 +02:00
+ '<div class="pop-c hide"><span style="color:var(--c-f);" onclick="selGrp(0);">➊</span><span style="color:var(--c-r);" onclick="selGrp(1);">➋</span><span style="color:var(--c-g);" onclick="selGrp(2);">➌</span><span style="color:var(--c-l);" onclick="selGrp(3);">➍</span></div>'
2023-04-01 23:40:43 +02:00
+ '</div></div>' ;
2022-03-10 22:36:09 +01:00
}
2022-12-09 08:15:14 +01:00
function makePlSel ( el , incPl = false )
{
2022-03-10 22:36:09 +01:00
var plSelContent = "" ;
delete pJson [ "0" ] ; // remove filler preset
2023-11-04 09:39:08 +01:00
Object . entries ( pJson ) . sort ( cmpP ) . forEach ( ( a ) => {
2022-07-27 17:00:55 +02:00
var n = a [ 1 ] . n ? a [ 1 ] . n : "Preset " + a [ 0 ] ;
2023-11-04 09:39:08 +01:00
if ( cfg . comp . idsort ) n = a [ 0 ] + ' ' + n ;
if ( ! ( ! incPl && a [ 1 ] . playlist && a [ 1 ] . playlist . ps ) ) // skip playlists, sub-playlists not yet supported
plSelContent += ` <option value=" ${ a [ 0 ] } " ${ a [ 0 ] == el ? "selected" : "" } > ${ n } </option> ` ;
} ) ;
2022-03-10 22:36:09 +01:00
return plSelContent ;
}
2022-12-09 08:15:14 +01:00
function refreshPlE ( p )
{
2022-03-10 22:36:09 +01:00
var plEDiv = gId ( ` ple ${ p } ` ) ;
if ( ! plEDiv ) return ;
var content = "<div class=\"first c\">Playlist entries</div>" ;
2023-11-04 09:39:08 +01:00
plJson [ p ] . ps . forEach ( ( e , i ) => { content += makePlEntry ( p , i ) ; } ) ;
2022-03-10 22:36:09 +01:00
content += ` <div class="hrz"></div> ` ;
plEDiv . innerHTML = content ;
var dels = plEDiv . getElementsByClassName ( "btn-pl-del" ) ;
if ( dels . length < 2 ) dels [ 0 ] . style . display = "none" ;
2023-11-04 09:39:08 +01:00
d . querySelectorAll ( ` #seg ${ p + 100 } .sel ` ) . forEach ( ( i ) => {
2022-03-10 22:36:09 +01:00
if ( i . dataset . val ) {
if ( parseInt ( i . dataset . val ) > 0 ) i . value = i . dataset . val ;
else plJson [ p ] . ps [ i . dataset . index ] = parseInt ( i . value ) ;
}
2023-11-04 09:39:08 +01:00
} ) ;
2022-03-10 22:36:09 +01:00
}
// p: preset ID, i: ps index
2022-12-09 08:15:14 +01:00
function addPl ( p , i )
{
2022-03-10 22:36:09 +01:00
plJson [ p ] . ps . splice ( i + 1 , 0 , 0 ) ;
plJson [ p ] . dur . splice ( i + 1 , 0 , plJson [ p ] . dur [ i ] ) ;
plJson [ p ] . transition . splice ( i + 1 , 0 , plJson [ p ] . transition [ i ] ) ;
refreshPlE ( p ) ;
}
2022-12-09 08:15:14 +01:00
function delPl ( p , i )
{
2022-03-10 22:36:09 +01:00
if ( plJson [ p ] . ps . length < 2 ) return ;
plJson [ p ] . ps . splice ( i , 1 ) ;
plJson [ p ] . dur . splice ( i , 1 ) ;
plJson [ p ] . transition . splice ( i , 1 ) ;
refreshPlE ( p ) ;
}
2022-12-09 08:15:14 +01:00
function plePs ( p , i , field )
{
2022-03-10 22:36:09 +01:00
plJson [ p ] . ps [ i ] = parseInt ( field . value ) ;
}
2022-12-09 08:15:14 +01:00
function pleDur ( p , i , field )
{
2022-03-10 22:36:09 +01:00
if ( field . validity . valid )
plJson [ p ] . dur [ i ] = Math . floor ( field . value * 10 ) ;
}
2022-12-09 08:15:14 +01:00
function pleTr ( p , i , field )
{
2022-03-10 22:36:09 +01:00
if ( field . validity . valid )
plJson [ p ] . transition [ i ] = Math . floor ( field . value * 10 ) ;
}
2022-12-09 08:15:14 +01:00
function plR ( p )
{
2022-03-10 22:36:09 +01:00
var pl = plJson [ p ] ;
pl . r = gId ( ` pl ${ p } rtgl ` ) . checked ;
if ( gId ( ` pl ${ p } rptgl ` ) . checked ) { // infinite
pl . repeat = 0 ;
delete pl . end ;
gId ( ` pl ${ p } o1 ` ) . style . display = "none" ;
} else {
pl . repeat = parseInt ( gId ( ` pl ${ p } rp ` ) . value ) ;
pl . end = parseInt ( gId ( ` pl ${ p } selEnd ` ) . value ) ;
gId ( ` pl ${ p } o1 ` ) . style . display = "block" ;
}
}
2022-12-09 08:15:14 +01:00
function makeP ( i , pl )
{
2022-03-10 22:36:09 +01:00
var content = "" ;
if ( pl ) {
2022-11-30 19:34:32 +01:00
if ( i === 0 ) plJson [ 0 ] = {
ps : [ 1 ] ,
dur : [ 100 ] ,
transition : [ tr ] ,
repeat : 0 ,
r : false ,
end : 0
} ;
2022-03-10 22:36:09 +01:00
var rep = plJson [ i ] . repeat ? plJson [ i ] . repeat : 0 ;
2023-10-19 10:42:10 +02:00
content =
2022-03-10 22:36:09 +01:00
` <div id="ple ${ i } " style="margin-top:10px;"></div><label class="check revchkl">Shuffle
< input type = "checkbox" id = "pl${i}rtgl" onchange = "plR(${i})" $ { plJson [ i ] . r || rep < 0 ? "checked" : "" } >
2022-07-27 00:11:24 +02:00
< span class = "checkmark" > < / s p a n >
2022-03-10 22:36:09 +01:00
< / l a b e l >
< label class = "check revchkl" > Repeat indefinitely
< input type = "checkbox" id = "pl${i}rptgl" onchange = "plR(${i})" $ { rep > 0 ? "" : "checked" } >
2022-07-27 00:11:24 +02:00
< span class = "checkmark" > < / s p a n >
2022-03-10 22:36:09 +01:00
< / l a b e l >
< div id = "pl${i}o1" style = "display:${rep>0?" block ":" none "}" >
2023-02-25 09:41:15 +01:00
< div class = "c" > Repeat < input type = "number" id = "pl${i}rp" oninput = "plR(${i})" max = 127 min = 0 value = $ { rep > 0 ? rep : 1 } > times < / d i v >
2022-03-10 22:36:09 +01:00
< div class = "sel" > End preset : < br >
2022-07-24 16:21:29 +02:00
< div class = "sel-p" > < select class = "sel-ple" id = "pl${i}selEnd" onchange = "plR(${i})" data - val = $ { plJson [ i ] . end ? plJson [ i ] . end : 0 } >
2022-04-20 18:05:59 +02:00
< option value = "0" > None < / o p t i o n >
< option value = "255" > Restore preset < / o p t i o n >
2022-07-24 16:21:29 +02:00
$ { makePlSel ( plJson [ i ] . end ? plJson [ i ] . end : 0 , true ) }
< / s e l e c t > < / d i v > < / d i v >
2022-03-10 22:36:09 +01:00
< / d i v >
< div class = "c" > < button class = "btn btn-p" onclick = "testPl(${i}, this)" > < i class = 'icons btn-icon' > & # xe139 ; < / i > T e s t < / b u t t o n > < / d i v > ` ;
} else {
content =
` <label class="check revchkl">
2023-10-23 19:26:02 +02:00
< span class = "lstIname" > Include brightness < / s p a n >
2022-03-10 22:36:09 +01:00
< input type = "checkbox" id = "p${i}ibtgl" checked >
2022-07-27 00:11:24 +02:00
< span class = "checkmark" > < / s p a n >
2022-03-10 22:36:09 +01:00
< / l a b e l >
< label class = "check revchkl" >
2023-10-23 19:26:02 +02:00
< span class = "lstIname" > Save segment bounds < / s p a n >
2022-03-10 22:36:09 +01:00
< input type = "checkbox" id = "p${i}sbtgl" checked >
2022-07-27 00:11:24 +02:00
< span class = "checkmark" > < / s p a n >
2022-03-10 22:36:09 +01:00
< / l a b e l >
< label class = "check revchkl" >
2023-10-23 19:26:02 +02:00
< span class = "lstIname" > Checked segments only < / s p a n >
2022-03-10 22:36:09 +01:00
< input type = "checkbox" id = "p${i}sbchk" >
2022-07-27 00:11:24 +02:00
< span class = "checkmark" > < / s p a n >
2022-03-10 22:36:09 +01:00
< / l a b e l > ` ;
2023-02-10 19:49:43 +01:00
if ( Array . isArray ( lastinfo . maps ) && lastinfo . maps . length > 1 ) {
2022-11-16 20:55:21 +01:00
content += ` <div class="lbl-l">Ledmap: <div class="sel-p"><select class="sel-p" id="p ${ i } lmp"><option value="">Unchanged</option> ` ;
2023-02-14 17:11:58 +01:00
for ( const k of ( lastinfo . maps || [ ] ) ) content += ` <option value=" ${ k . id } " ${ ( i > 0 && pJson [ i ] . ledmap == k . id ) ? " selected" : "" } > ${ k . id == 0 ? 'Default' : ( k . n ? k . n : 'ledmap' + k . id + '.json' ) } </option> ` ;
2022-08-23 20:57:11 +02:00
content += "</select></div></div>" ;
2022-03-10 22:36:09 +01:00
}
}
2023-02-25 09:41:15 +01:00
return ` <input type="text" class="ptxt ${ i == 0 ? 'show' : '' } " id="p ${ i } txt" autocomplete="off" maxlength=32 value=" ${ ( i > 0 ) ? pName ( i ) : "" } " placeholder="Enter name..."/>
< div class = "c" > Quick load label : < input type = "text" class = "stxt" maxlength = 2 value = "${qlName(i)}" id = "p${i}ql" autocomplete = "off" / > < / d i v >
2022-03-10 22:36:09 +01:00
< div class = "h" > ( leave empty for no Quick load button ) < / d i v >
< div $ { pl && i == 0 ? "style='display:none'" : "" } >
< label class = "check revchkl" >
2023-10-23 19:26:02 +02:00
< span class = "lstIname" > $ { pl ? "Show playlist editor" : ( i > 0 ) ? "Overwrite with state" : "Use current state" } < / s p a n >
2022-08-24 23:04:51 +02:00
< input type = "checkbox" id = "p${i}cstgl" onchange = "tglCs(${i})" $ { ( i == 0 || pl ) ? "checked" : "" } >
< span class = "checkmark" > < / s p a n >
< / l a b e l >
2022-03-10 22:36:09 +01:00
< / d i v >
2023-02-25 09:41:15 +01:00
< div class = "po2" id = "p${i}o2" > API command < br > < textarea class = "apitxt" id = "p${i}api" > < / t e x t a r e a > < / d i v >
2022-08-24 23:04:51 +02:00
< div class = "po1" id = "p${i}o1" > $ { content } < / d i v >
2023-02-25 09:41:15 +01:00
< div class = "c m6" > Save to ID < input id = "p${i}id" type = "number" oninput = "checkUsed(${i})" max = 250 min = 1 value = $ { ( i > 0 ) ? i : getLowestUnusedP ( ) } > < / d i v >
2022-03-10 22:36:09 +01:00
< div class = "c" >
< button class = "btn btn-p" onclick = "saveP(${i},${pl})" > < i class = "icons btn-icon" > & # xe390 ; < / i > S a v e < / b u t t o n >
$ { ( i > 0 ) ? '<button class="btn btn-p" id="p' + i + 'del" onclick="delP(' + i + ')"><i class="icons btn-icon"></i>Delete' : '<button class="btn btn-p" onclick="resetPUtil()">Cancel' } < / b u t t o n >
< / d i v >
< div class = "pwarn ${(i>0)?" bp ":" "} c" id = "p${i}warn" > < / d i v >
$ { ( i > 0 ) ? ( '<div class="h">ID ' + i + '</div>' ) : "" } ` ;
}
function makePUtil ( )
{
2022-03-11 23:38:50 +01:00
let p = gId ( 'putil' ) ;
p . classList . remove ( 'staybot' ) ;
2022-04-06 05:45:39 +02:00
p . classList . add ( 'pres' ) ;
p . innerHTML = ` <div class="presin expanded"> ${ makeP ( 0 ) } </div> ` ;
2022-08-23 20:57:11 +02:00
let pTx = gId ( 'p0txt' ) ;
pTx . focus ( ) ;
pTx . value = eJson . find ( ( o ) => { return o . id == selectedFx } ) . name ;
pTx . select ( ) ;
2022-03-11 23:38:50 +01:00
p . scrollIntoView ( {
behavior : 'smooth' ,
block : 'center'
} ) ;
gId ( 'psFind' ) . classList . remove ( 'staytop' ) ;
2022-03-10 22:36:09 +01:00
}
2022-08-24 23:04:51 +02:00
function makePlEntry ( p , i )
{
return ` <div class="plentry">
< div class = "hrz" > < / d i v >
2022-03-10 22:36:09 +01:00
< table >
< tr >
< td width = "80%" colspan = 2 >
2022-07-24 16:21:29 +02:00
< div class = "sel-p" > < select class = "sel-pl" onchange = "plePs(${p},${i},this)" data - val = "${plJson[p].ps[i]}" data - index = "${i}" >
$ { makePlSel ( plJson [ p ] . ps [ i ] ) }
< / s e l e c t > < / d i v >
2022-03-10 22:36:09 +01:00
< / t d >
< td class = "c" > < button class = "btn btn-pl-add" onclick = "addPl(${p},${i})" > < i class = "icons btn-icon" > & # xe18a ; < / i > < / b u t t o n > < / t d >
< / t r >
< tr >
< td class = "c" > Duration < / t d >
< td class = "c" > Transition < / t d >
< td class = "c" > # $ { i + 1 } < / t d >
< / t r >
< tr >
2023-02-25 09:41:15 +01:00
< td class = "c" width = "40%" > < input class = "segn" type = "number" placeholder = "Duration" max = 6553.0 min = 0.2 step = 0.1 oninput = "pleDur(${p},${i},this)" value = "${plJson[p].dur[i]/10.0}" > s < / t d >
< td class = "c" width = "40%" > < input class = "segn" type = "number" placeholder = "Transition" max = 65.0 min = 0.0 step = 0.1 oninput = "pleTr(${p},${i},this)" value = "${plJson[p].transition[i]/10.0}" > s < / t d >
2022-03-10 22:36:09 +01:00
< td class = "c" > < button class = "btn btn-pl-del" onclick = "delPl(${p},${i})" > < i class = "icons btn-icon" > & # xe037 ; < / i > < / b u t t o n > < / d i v > < / t d >
< / t r >
< / t a b l e >
< / d i v > ` ;
}
function makePlUtil ( )
{
if ( pNum < 2 ) {
showToast ( "You need at least 2 presets to make a playlist!" ) ; //return;
}
2022-03-11 23:38:50 +01:00
let p = gId ( 'putil' ) ;
p . classList . remove ( 'staybot' ) ;
2022-11-19 01:59:58 +01:00
p . classList . add ( 'pres' ) ;
p . innerHTML = ` <div class="presin expanded" id="seg100"> ${ makeP ( 0 , true ) } </div></div> ` ;
2022-03-10 22:36:09 +01:00
refreshPlE ( 0 ) ;
2022-08-23 20:57:11 +02:00
gId ( 'p0txt' ) . focus ( ) ;
2022-03-11 23:38:50 +01:00
p . scrollIntoView ( {
behavior : 'smooth' ,
block : 'center'
} ) ;
gId ( 'psFind' ) . classList . remove ( 'staytop' ) ;
2022-03-10 22:36:09 +01:00
}
function resetPUtil ( )
{
2022-03-11 23:38:50 +01:00
gId ( 'psFind' ) . classList . add ( 'staytop' ) ;
2022-04-06 05:45:39 +02:00
let p = gId ( 'putil' ) ;
p . classList . add ( 'staybot' ) ;
p . classList . remove ( 'pres' ) ;
2022-11-19 01:59:58 +01:00
p . innerHTML = ` <button class="btn btn-s" onclick="makePUtil()" style="float:left;"><i class="icons btn-icon"></i>Preset</button> `
+ ` <button class="btn btn-s" onclick="makePlUtil()" style="float:right;"><i class="icons btn-icon"></i>Playlist</button> ` ;
2022-03-10 22:36:09 +01:00
}
function tglCs ( i )
{
var pss = gId ( ` p ${ i } cstgl ` ) . checked ;
gId ( ` p ${ i } o1 ` ) . style . display = pss ? "block" : "none" ;
gId ( ` p ${ i } o2 ` ) . style . display = ! pss ? "block" : "none" ;
}
function tglSegn ( s )
{
2022-03-26 23:22:18 +01:00
let t = gId ( s < 100 ? ` seg ${ s } t ` : ` p ${ s - 100 } txt ` ) ;
2022-08-23 20:57:11 +02:00
if ( t ) {
2022-12-23 02:59:24 +01:00
t . classList . toggle ( 'show' ) ;
2022-08-23 20:57:11 +02:00
t . focus ( ) ;
t . select ( ) ;
}
2022-03-26 23:22:18 +01:00
event . preventDefault ( ) ;
event . stopPropagation ( ) ;
2022-03-10 22:36:09 +01:00
}
2022-03-11 23:38:50 +01:00
function selSegAll ( o )
{
var obj = { "seg" : [ ] } ;
2023-07-14 01:12:19 +02:00
for ( let i = 0 ; i <= lSeg ; i ++ ) if ( gId ( ` seg ${ i } ` ) ) obj . seg . push ( { "id" : i , "sel" : o . checked } ) ;
2022-03-11 23:38:50 +01:00
requestJson ( obj ) ;
}
2022-03-10 22:36:09 +01:00
function selSegEx ( s )
{
var obj = { "seg" : [ ] } ;
2023-07-14 01:12:19 +02:00
for ( let i = 0 ; i <= lSeg ; i ++ ) if ( gId ( ` seg ${ i } ` ) ) obj . seg . push ( { "id" : i , "sel" : ( i == s ) } ) ;
2022-03-10 22:36:09 +01:00
obj . mainseg = s ;
requestJson ( obj ) ;
}
function selSeg ( s )
{
var sel = gId ( ` seg ${ s } sel ` ) . checked ;
var obj = { "seg" : { "id" : s , "sel" : sel } } ;
requestJson ( obj ) ;
}
2023-04-01 23:40:43 +02:00
function selGrp ( g )
{
event . preventDefault ( ) ;
event . stopPropagation ( ) ;
2023-05-15 17:06:29 +02:00
var sel = gId ( ` segcont ` ) . querySelectorAll ( ` div[data-set=" ${ g } "] ` ) ;
2023-04-01 23:40:43 +02:00
var obj = { "seg" : [ ] } ;
2023-07-14 01:12:19 +02:00
for ( let i = 0 ; i <= lSeg ; i ++ ) if ( gId ( ` seg ${ i } ` ) ) obj . seg . push ( { "id" : i , "sel" : false } ) ;
2023-04-01 23:40:43 +02:00
if ( sel ) for ( let s of sel || [ ] ) {
let i = parseInt ( s . id . substring ( 3 ) ) ;
obj . seg [ i ] = { "id" : i , "sel" : true } ;
}
if ( obj . seg . length ) requestJson ( obj ) ;
}
2022-03-10 22:36:09 +01:00
function rptSeg ( s )
{
2022-05-08 10:50:48 +02:00
//TODO: 2D support
2022-03-10 22:36:09 +01:00
var name = gId ( ` seg ${ s } t ` ) . value ;
var start = parseInt ( gId ( ` seg ${ s } s ` ) . value ) ;
var stop = parseInt ( gId ( ` seg ${ s } e ` ) . value ) ;
if ( stop == 0 ) { return ; }
var rev = gId ( ` seg ${ s } rev ` ) . checked ;
var mi = gId ( ` seg ${ s } mi ` ) . checked ;
var sel = gId ( ` seg ${ s } sel ` ) . checked ;
2022-12-23 02:59:24 +01:00
var pwr = gId ( ` seg ${ s } pwr ` ) . classList . contains ( 'act' ) ;
2022-04-17 11:13:13 +02:00
var obj = { "seg" : { "id" : s , "n" : name , "start" : start , "stop" : ( cfg . comp . seglen ? start : 0 ) + stop , "rev" : rev , "mi" : mi , "on" : pwr , "bri" : parseInt ( gId ( ` seg ${ s } bri ` ) . value ) , "sel" : sel } } ;
2022-03-10 22:36:09 +01:00
if ( gId ( ` seg ${ s } grp ` ) ) {
var grp = parseInt ( gId ( ` seg ${ s } grp ` ) . value ) ;
var spc = parseInt ( gId ( ` seg ${ s } spc ` ) . value ) ;
var ofs = parseInt ( gId ( ` seg ${ s } of ` ) . value ) ;
obj . seg . grp = grp ;
obj . seg . spc = spc ;
obj . seg . of = ofs ;
}
obj . seg . rpt = true ;
expand ( s ) ;
requestJson ( obj ) ;
}
function setSeg ( s )
{
var name = gId ( ` seg ${ s } t ` ) . value ;
2022-11-19 11:57:38 +01:00
let sX = gId ( ` seg ${ s } s ` ) ;
let eX = gId ( ` seg ${ s } e ` ) ;
var start = parseInt ( sX . value ) ;
2023-02-01 19:30:56 +01:00
var stop = parseInt ( eX . value ) + ( cfg . comp . seglen ? start : 0 ) ;
2022-11-19 11:57:38 +01:00
if ( start < sX . min || start > sX . max ) { sX . value = sX . min ; return ; } // prevent out of bounds
2023-02-01 19:30:56 +01:00
if ( stop < eX . min || stop - ( cfg . comp . seglen ? start : 0 ) > eX . max ) { eX . value = eX . max ; return ; } // prevent out of bounds
2022-03-10 22:36:09 +01:00
if ( ( cfg . comp . seglen && stop == 0 ) || ( ! cfg . comp . seglen && stop <= start ) ) { delSeg ( s ) ; return ; }
2023-02-01 19:30:56 +01:00
var obj = { "seg" : { "id" : s , "n" : name , "start" : start , "stop" : stop } } ;
if ( isM && start < mw * mh ) {
2022-11-19 11:57:38 +01:00
let sY = gId ( ` seg ${ s } sY ` ) ;
let eY = gId ( ` seg ${ s } eY ` ) ;
var startY = parseInt ( sY . value ) ;
2023-02-01 19:30:56 +01:00
var stopY = parseInt ( eY . value ) + ( cfg . comp . seglen ? startY : 0 ) ;
2022-11-19 11:57:38 +01:00
if ( startY < sY . min || startY > sY . max ) { sY . value = sY . min ; return ; } // prevent out of bounds
2022-12-13 14:40:41 +01:00
if ( stopY < eY . min || stopY > eY . max ) { eY . value = eY . max ; return ; } // prevent out of bounds
2022-05-08 10:50:48 +02:00
obj . seg . startY = startY ;
2023-02-01 19:30:56 +01:00
obj . seg . stopY = stopY ;
2022-05-08 10:50:48 +02:00
}
2023-02-01 19:30:56 +01:00
let g = gId ( ` seg ${ s } grp ` ) ;
if ( g ) { // advanced options, not present in new segment dialog (makeSeg())
let grp = parseInt ( g . value ) ;
let spc = parseInt ( gId ( ` seg ${ s } spc ` ) . value ) ;
let ofs = parseInt ( gId ( ` seg ${ s } of ` ) . value ) ;
2022-03-10 22:36:09 +01:00
obj . seg . grp = grp ;
obj . seg . spc = spc ;
obj . seg . of = ofs ;
2023-02-15 20:36:54 +01:00
if ( isM && gId ( ` seg ${ s } tp ` ) ) obj . seg . tp = gId ( ` seg ${ s } tp ` ) . checked ;
2022-03-10 22:36:09 +01:00
}
2022-11-19 11:57:38 +01:00
resetUtil ( ) ; // close add segment dialog just in case
2022-03-10 22:36:09 +01:00
requestJson ( obj ) ;
}
function delSeg ( s )
{
if ( segCount < 2 ) {
showToast ( "You need to have multiple segments to delete one!" ) ;
return ;
}
segCount -- ;
var obj = { "seg" : { "id" : s , "stop" : 0 } } ;
requestJson ( obj ) ;
}
function setRev ( s )
{
var rev = gId ( ` seg ${ s } rev ` ) . checked ;
var obj = { "seg" : { "id" : s , "rev" : rev } } ;
requestJson ( obj ) ;
}
2022-05-08 10:50:48 +02:00
function setRevY ( s )
{
var rev = gId ( ` seg ${ s } rY ` ) . checked ;
var obj = { "seg" : { "id" : s , "rY" : rev } } ;
requestJson ( obj ) ;
}
2022-03-10 22:36:09 +01:00
function setMi ( s )
{
var mi = gId ( ` seg ${ s } mi ` ) . checked ;
var obj = { "seg" : { "id" : s , "mi" : mi } } ;
requestJson ( obj ) ;
}
2022-05-08 10:50:48 +02:00
function setMiY ( s )
{
var mi = gId ( ` seg ${ s } mY ` ) . checked ;
var obj = { "seg" : { "id" : s , "mY" : mi } } ;
requestJson ( obj ) ;
}
2022-11-24 04:15:24 +01:00
function setM12 ( s )
2022-07-10 14:30:10 +02:00
{
2022-11-24 04:15:24 +01:00
var value = gId ( ` seg ${ s } m12 ` ) . selectedIndex ;
var obj = { "seg" : { "id" : s , "m12" : value } } ;
2022-07-10 14:30:10 +02:00
requestJson ( obj ) ;
}
2022-11-24 04:15:24 +01:00
function setSi ( s )
2022-07-10 14:30:10 +02:00
{
2022-11-24 04:15:24 +01:00
var value = gId ( ` seg ${ s } si ` ) . selectedIndex ;
var obj = { "seg" : { "id" : s , "si" : value } } ;
2022-07-10 14:30:10 +02:00
requestJson ( obj ) ;
}
2022-05-08 10:50:48 +02:00
function setTp ( s )
{
var tp = gId ( ` seg ${ s } tp ` ) . checked ;
var obj = { "seg" : { "id" : s , "tp" : tp } } ;
requestJson ( obj ) ;
}
2023-04-01 23:40:43 +02:00
function setGrp ( s , g )
{
event . preventDefault ( ) ;
event . stopPropagation ( ) ;
2023-05-15 17:06:29 +02:00
var obj = { "seg" : { "id" : s , "set" : g } } ;
2023-04-01 23:40:43 +02:00
requestJson ( obj ) ;
}
2022-03-10 22:36:09 +01:00
function setSegPwr ( s )
{
2022-12-23 02:59:24 +01:00
var pwr = gId ( ` seg ${ s } pwr ` ) . classList . contains ( 'act' ) ;
2022-03-26 23:22:18 +01:00
var obj = { "seg" : { "id" : s , "on" : ! pwr } } ;
2022-03-10 22:36:09 +01:00
requestJson ( obj ) ;
}
function setSegBri ( s )
{
var obj = { "seg" : { "id" : s , "bri" : parseInt ( gId ( ` seg ${ s } bri ` ) . value ) } } ;
requestJson ( obj ) ;
}
function tglFreeze ( s = null )
{
var obj = { "seg" : { "frz" : "t" } } ; // toggle
2022-04-01 00:59:19 +02:00
if ( s !== null ) {
obj . seg . id = s ;
// if live segment, enter live override (which also unfreezes)
if ( lastinfo && s == lastinfo . liveseg && lastinfo . live ) obj = { "lor" : 1 } ;
}
2022-03-10 22:36:09 +01:00
requestJson ( obj ) ;
}
2022-08-22 01:17:10 +02:00
function setFX ( ind = null )
2022-03-10 22:36:09 +01:00
{
if ( ind === null ) {
ind = parseInt ( d . querySelector ( '#fxlist input[name="fx"]:checked' ) . value ) ;
} else {
d . querySelector ( ` #fxlist input[name="fx"][value=" ${ ind } "] ` ) . checked = true ;
}
2023-06-16 10:24:56 +02:00
var obj = { "seg" : { "fx" : parseInt ( ind ) , "fxdef" : cfg . comp . fxdef } } ; // fxdef sets effect parameters to default values
2022-03-10 22:36:09 +01:00
requestJson ( obj ) ;
}
function setPalette ( paletteId = null )
{
if ( paletteId === null ) {
paletteId = parseInt ( d . querySelector ( '#pallist input[name="palette"]:checked' ) . value ) ;
} else {
d . querySelector ( ` #pallist input[name="palette"][value=" ${ paletteId } "] ` ) . checked = true ;
}
2022-08-22 01:17:10 +02:00
2022-03-10 22:36:09 +01:00
var obj = { "seg" : { "pal" : paletteId } } ;
requestJson ( obj ) ;
}
function setBri ( )
{
var obj = { "bri" : parseInt ( gId ( 'sliderBri' ) . value ) } ;
requestJson ( obj ) ;
}
function setSpeed ( )
{
var obj = { "seg" : { "sx" : parseInt ( gId ( 'sliderSpeed' ) . value ) } } ;
requestJson ( obj ) ;
}
function setIntensity ( )
{
var obj = { "seg" : { "ix" : parseInt ( gId ( 'sliderIntensity' ) . value ) } } ;
requestJson ( obj ) ;
}
2022-04-10 11:02:57 +02:00
function setCustom ( i = 1 )
{
if ( i < 1 || i > 3 ) return ;
var obj = { "seg" : { } } ;
var val = parseInt ( gId ( ` sliderC ${ i } ` ) . value ) ;
2022-05-21 13:19:11 +02:00
if ( i === 3 ) obj . seg . c3 = val ;
else if ( i === 2 ) obj . seg . c2 = val ;
else obj . seg . c1 = val ;
2022-04-10 11:02:57 +02:00
requestJson ( obj ) ;
}
2022-08-12 17:58:20 +02:00
function setOption ( i = 1 , v = false )
{
if ( i < 1 || i > 3 ) return ;
var obj = { "seg" : { } } ;
if ( i === 3 ) obj . seg . o3 = ! ( ! v ) ; //make sure it is bool
else if ( i === 2 ) obj . seg . o2 = ! ( ! v ) ; //make sure it is bool
else obj . seg . o1 = ! ( ! v ) ; //make sure it is bool
requestJson ( obj ) ;
}
2022-03-10 22:36:09 +01:00
function setLor ( i )
{
var obj = { "lor" : i } ;
requestJson ( obj ) ;
}
function setPreset ( i )
{
2022-06-05 10:16:56 +02:00
var obj = { "ps" : i } ;
2023-03-03 19:57:09 +01:00
if ( ! isPlaylist ( i ) && pJson && pJson [ i ] && ( ! pJson [ i ] . win || pJson [ i ] . win . indexOf ( "Please" ) <= 0 ) ) {
2022-12-13 14:27:44 +01:00
// we will send the complete preset content as to avoid delay introduced by
// async nature of applyPreset() and having to read the preset from file system.
obj = { "pd" : i } ; // use "pd" instead of "ps" to indicate that we are sending the preset content directly
2022-06-03 18:38:46 +02:00
Object . assign ( obj , pJson [ i ] ) ;
2022-12-13 14:27:44 +01:00
delete obj . ql ; // no need for quick load
delete obj . n ; // no need for name
2022-06-01 22:11:25 +02:00
}
2022-03-10 22:36:09 +01:00
if ( isPlaylist ( i ) ) obj . on = true ; // force on
showToast ( "Loading preset " + pName ( i ) + " (" + i + ")" ) ;
requestJson ( obj ) ;
}
function saveP ( i , pl )
{
pI = parseInt ( gId ( ` p ${ i } id ` ) . value ) ;
if ( ! pI || pI < 1 ) pI = ( i > 0 ) ? i : getLowestUnusedP ( ) ;
2022-05-18 14:38:22 +02:00
if ( pI > 250 ) { alert ( "Preset ID must be 250 or less." ) ; return ; }
2022-03-10 22:36:09 +01:00
pN = gId ( ` p ${ i } txt ` ) . value ;
if ( pN == "" ) pN = ( pl ? "Playlist " : "Preset " ) + pI ;
var obj = { } ;
if ( ! gId ( ` p ${ i } cstgl ` ) . checked ) {
var raw = gId ( ` p ${ i } api ` ) . value ;
try {
obj = JSON . parse ( raw ) ;
} catch ( e ) {
obj . win = raw ;
if ( raw . length < 2 ) {
gId ( ` p ${ i } warn ` ) . innerHTML = "⚠ Please enter your API command first" ;
return ;
} else if ( raw . indexOf ( '{' ) > - 1 ) {
gId ( ` p ${ i } warn ` ) . innerHTML = "⚠ Syntax error in custom JSON API command" ;
return ;
} else if ( raw . indexOf ( "Please" ) == 0 ) {
2022-08-24 23:04:51 +02:00
gId ( ` p ${ i } warn ` ) . innerHTML = "⚠ Please refresh the page before modifying this preset" ;
2022-03-10 22:36:09 +01:00
return ;
}
}
obj . o = true ;
} else {
if ( pl ) {
obj . playlist = plJson [ i ] ;
obj . on = true ;
obj . o = true ;
} else {
obj . ib = gId ( ` p ${ i } ibtgl ` ) . checked ;
obj . sb = gId ( ` p ${ i } sbtgl ` ) . checked ;
obj . sc = gId ( ` p ${ i } sbchk ` ) . checked ;
2022-11-22 22:17:30 +01:00
if ( gId ( ` p ${ i } lmp ` ) && gId ( ` p ${ i } lmp ` ) . value !== "" ) obj . ledmap = parseInt ( gId ( ` p ${ i } lmp ` ) . value ) ;
2022-03-10 22:36:09 +01:00
}
}
obj . psave = pI ; obj . n = pN ;
var pQN = gId ( ` p ${ i } ql ` ) . value ;
if ( pQN . length > 0 ) obj . ql = pQN ;
showToast ( "Saving " + pN + " (" + pI + ")" ) ;
requestJson ( obj ) ;
if ( obj . o ) {
pJson [ pI ] = obj ;
delete pJson [ pI ] . psave ;
delete pJson [ pI ] . o ;
delete pJson [ pI ] . v ;
delete pJson [ pI ] . time ;
} else {
pJson [ pI ] = { "n" : pN , "win" : "Please refresh the page to see this newly saved command." } ;
if ( obj . win ) pJson [ pI ] . win = obj . win ;
if ( obj . ql ) pJson [ pI ] . ql = obj . ql ;
}
populatePresets ( ) ;
resetPUtil ( ) ;
2022-10-08 18:25:51 +02:00
setTimeout ( ( ) => { pmtLast = 0 ; loadPresets ( ) ; } , 750 ) ; // force reloading of presets
2022-03-10 22:36:09 +01:00
}
function testPl ( i , bt ) {
if ( bt . dataset . test == 1 ) {
bt . dataset . test = 0 ;
bt . innerHTML = "<i class='icons btn-icon'></i>Test" ;
stopPl ( ) ;
return ;
}
bt . dataset . test = 1 ;
bt . innerHTML = "<i class='icons btn-icon'></i>Stop" ;
var obj = { } ;
obj . playlist = plJson [ i ] ;
obj . on = true ;
requestJson ( obj ) ;
}
function stopPl ( ) {
requestJson ( { playlist : { } } )
}
function delP ( i ) {
var bt = gId ( ` p ${ i } del ` ) ;
if ( bt . dataset . cnf == 1 ) {
var obj = { "pdel" : i } ;
requestJson ( obj ) ;
delete pJson [ i ] ;
populatePresets ( ) ;
2022-12-23 02:59:24 +01:00
gId ( 'putil' ) . classList . add ( 'staybot' ) ;
2022-03-10 22:36:09 +01:00
} else {
bt . style . color = "var(--c-r)" ;
bt . innerHTML = "<i class='icons btn-icon'></i>Delete!" ;
bt . dataset . cnf = 1 ;
}
}
function selectSlot ( b )
{
csel = b ;
var cd = gId ( 'csl' ) . children ;
for ( let i of cd ) i . classList . remove ( 'xxs-w' ) ;
cd [ b ] . classList . add ( 'xxs-w' ) ;
2022-03-12 21:25:39 +01:00
setPicker ( rgbStr ( cd [ b ] . dataset ) ) ;
2022-03-10 22:36:09 +01:00
// force slider update on initial load (picker "color:change" not fired if black)
if ( cpick . color . value == 0 ) updatePSliders ( ) ;
gId ( 'sliderW' ) . value = parseInt ( cd [ b ] . dataset . w ) ;
2022-03-14 20:22:20 +01:00
updateTrail ( gId ( 'sliderW' ) ) ;
2022-03-10 22:36:09 +01:00
redrawPalPrev ( ) ;
}
// set the color from a hex string. Used by quick color selectors
var lasth = 0 ;
function pC ( col )
{
if ( col == "rnd" ) {
col = { h : 0 , s : 0 , v : 100 } ;
col . s = Math . floor ( ( Math . random ( ) * 50 ) + 50 ) ;
do {
col . h = Math . floor ( Math . random ( ) * 360 ) ;
} while ( Math . abs ( col . h - lasth ) < 50 ) ;
lasth = col . h ;
}
setPicker ( col ) ;
setColor ( 0 ) ;
}
function updatePSliders ( ) {
// update RGB sliders
var col = cpick . color . rgb ;
gId ( 'sliderR' ) . value = col . r ;
gId ( 'sliderG' ) . value = col . g ;
gId ( 'sliderB' ) . value = col . b ;
// update hex field
var str = cpick . color . hexString . substring ( 1 ) ;
var w = parseInt ( gId ( "csl" ) . children [ csel ] . dataset . w ) ;
if ( w > 0 ) str += w . toString ( 16 ) ;
gId ( 'hexc' ) . value = str ;
gId ( 'hexcnf' ) . style . backgroundColor = "var(--c-3)" ;
2022-04-26 22:16:08 +02:00
// update HSV sliders
var c ;
let h = cpick . color . hue ;
let s = cpick . color . saturation ;
let v = cpick . color . value ;
gId ( "sliderH" ) . value = h ;
gId ( "sliderS" ) . value = s ;
gId ( 'sliderV' ) . value = v ;
c = iro . Color . hsvToRgb ( { "h" : h , "s" : 100 , "v" : 100 } ) ;
gId ( "sliderS" ) . nextElementSibling . style . backgroundImage = 'linear-gradient(90deg, #aaa -15%, rgb(' + c . r + ',' + c . g + ',' + c . b + '))' ;
c = iro . Color . hsvToRgb ( { "h" : h , "s" : s , "v" : 100 } ) ;
gId ( 'sliderV' ) . nextElementSibling . style . backgroundImage = 'linear-gradient(90deg, #000 -15%, rgb(' + c . r + ',' + c . g + ',' + c . b + '))' ;
2022-03-10 22:36:09 +01:00
// update Kelvin slider
gId ( 'sliderK' ) . value = cpick . color . kelvin ;
}
function hexEnter ( )
{
if ( event . keyCode == 13 ) fromHex ( ) ;
}
function segEnter ( s ) {
if ( event . keyCode == 13 ) setSeg ( s ) ;
}
function fromHex ( )
{
var str = gId ( 'hexc' ) . value ;
let w = parseInt ( str . substring ( 6 ) , 16 ) ;
try {
setPicker ( "#" + str . substring ( 0 , 6 ) ) ;
} catch ( e ) {
setPicker ( "#ffaa00" ) ;
}
gId ( "csl" ) . children [ csel ] . dataset . w = isNaN ( w ) ? 0 : w ;
setColor ( 2 ) ;
}
function setPicker ( rgb ) {
var c = new iro . Color ( rgb ) ;
if ( c . value > 0 ) cpick . color . set ( c ) ;
else cpick . color . setChannel ( 'hsv' , 'v' , 0 ) ;
2022-03-14 20:22:20 +01:00
updateTrail ( gId ( 'sliderR' ) ) ;
updateTrail ( gId ( 'sliderG' ) ) ;
updateTrail ( gId ( 'sliderB' ) ) ;
2022-03-10 22:36:09 +01:00
}
2022-04-26 22:16:08 +02:00
function fromH ( )
{
cpick . color . setChannel ( 'hsv' , 'h' , gId ( 'sliderH' ) . value ) ;
}
function fromS ( )
{
cpick . color . setChannel ( 'hsv' , 's' , gId ( 'sliderS' ) . value ) ;
}
2022-03-10 22:36:09 +01:00
function fromV ( )
{
cpick . color . setChannel ( 'hsv' , 'v' , gId ( 'sliderV' ) . value ) ;
}
function fromK ( )
{
cpick . color . set ( { kelvin : gId ( 'sliderK' ) . value } ) ;
}
function fromRgb ( )
{
var r = gId ( 'sliderR' ) . value ;
var g = gId ( 'sliderG' ) . value ;
var b = gId ( 'sliderB' ) . value ;
setPicker ( ` rgb( ${ r } , ${ g } , ${ b } ) ` ) ;
let cd = gId ( 'csl' ) . children ; // color slots
cd [ csel ] . dataset . r = r ;
cd [ csel ] . dataset . g = g ;
cd [ csel ] . dataset . b = b ;
setCSL ( cd [ csel ] ) ;
}
function fromW ( )
{
let w = gId ( 'sliderW' ) ;
let cd = gId ( 'csl' ) . children ; // color slots
cd [ csel ] . dataset . w = w . value ;
setCSL ( cd [ csel ] ) ;
2022-03-14 20:22:20 +01:00
updateTrail ( w ) ;
2022-03-10 22:36:09 +01:00
}
// sr 0: from RGB sliders, 1: from picker, 2: from hex
function setColor ( sr )
{
var cd = gId ( 'csl' ) . children ; // color slots
let cdd = cd [ csel ] . dataset ;
let w = 0 , r , g , b ;
if ( sr == 1 && isRgbBlack ( cdd ) ) cpick . color . setChannel ( 'hsv' , 'v' , 100 ) ;
if ( sr != 2 && hasWhite ) w = parseInt ( gId ( 'sliderW' ) . value ) ;
var col = cpick . color . rgb ;
cdd . r = r = hasRGB ? col . r : w ;
cdd . g = g = hasRGB ? col . g : w ;
cdd . b = b = hasRGB ? col . b : w ;
cdd . w = w ;
setCSL ( cd [ csel ] ) ;
var obj = { "seg" : { "col" : [ [ ] , [ ] , [ ] ] } } ;
obj . seg . col [ csel ] = [ r , g , b , w ] ;
requestJson ( obj ) ;
}
function setBalance ( b )
{
var obj = { "seg" : { "cct" : parseInt ( b ) } } ;
requestJson ( obj ) ;
}
2023-07-13 22:21:15 +02:00
function rmtTgl ( ip , i ) {
event . preventDefault ( ) ;
event . stopPropagation ( ) ;
fetch ( ` http:// ${ ip } /win&T=2 ` , { method : 'get' } )
. then ( ( r ) => {
return r . text ( ) ;
} )
. then ( ( t ) => {
let c = ( new window . DOMParser ( ) ) . parseFromString ( t , "text/xml" ) ;
// perhaps just i.classList.toggle("off"); would be enough
if ( c . getElementsByTagName ( 'ac' ) [ 0 ] . textContent === "0" ) {
i . classList . add ( "off" ) ;
} else {
i . classList . remove ( "off" ) ;
}
} ) ;
}
2022-03-10 22:36:09 +01:00
var hc = 0 ;
setInterval ( ( ) => {
if ( ! isInfo ) return ;
hc += 18 ;
if ( hc > 300 ) hc = 0 ;
if ( hc > 200 ) hc = 306 ;
if ( hc == 144 ) hc += 36 ;
if ( hc == 108 ) hc += 18 ;
gId ( 'heart' ) . style . color = ` hsl( ${ hc } , 100%, 50%) ` ;
} , 910 ) ;
function openGH ( ) { window . open ( "https://github.com/Aircoookie/WLED/wiki" ) ; }
var cnfr = false ;
function cnfReset ( )
{
if ( ! cnfr ) {
var bt = gId ( 'resetbtn' ) ;
bt . style . color = "var(--c-r)" ;
bt . innerHTML = "Confirm Reboot" ;
cnfr = true ; return ;
}
2023-06-07 21:37:54 +02:00
window . location . href = getURL ( "/reset" ) ;
2022-03-10 22:36:09 +01:00
}
var cnfrS = false ;
function rSegs ( )
{
var bt = gId ( 'rsbtn' ) ;
if ( ! cnfrS ) {
bt . style . color = "var(--c-r)" ;
bt . innerHTML = "Confirm reset" ;
cnfrS = true ; return ;
}
cnfrS = false ;
bt . style . color = "var(--c-f)" ;
bt . innerHTML = "Reset segments" ;
var obj = { "seg" : [ { "start" : 0 , "stop" : ledCount , "sel" : true } ] } ;
2022-07-17 15:58:41 +02:00
if ( isM ) {
obj . seg [ 0 ] . stop = mw ;
obj . seg [ 0 ] . startX = 0 ;
obj . seg [ 0 ] . stopY = mh ;
}
2022-03-10 22:36:09 +01:00
for ( let i = 1 ; i <= lSeg ; i ++ ) obj . seg . push ( { "stop" : 0 } ) ;
requestJson ( obj ) ;
}
function loadPalettesData ( callback = null )
{
if ( palettesData ) return ;
const lsKey = "wledPalx" ;
var lsPalData = localStorage . getItem ( lsKey ) ;
if ( lsPalData ) {
try {
var d = JSON . parse ( lsPalData ) ;
if ( d && d . vid == d . vid ) {
palettesData = d . p ;
2022-04-17 11:13:13 +02:00
if ( callback ) callback ( ) ;
2022-03-10 22:36:09 +01:00
return ;
}
} catch ( e ) { }
}
palettesData = { } ;
getPalettesData ( 0 , ( ) => {
localStorage . setItem ( lsKey , JSON . stringify ( {
p : palettesData ,
vid : lastinfo . vid
} ) ) ;
2022-04-24 19:47:55 +02:00
redrawPalPrev ( ) ;
2022-04-17 11:13:13 +02:00
if ( callback ) setTimeout ( callback , 99 ) ;
2022-03-10 22:36:09 +01:00
} ) ;
}
function getPalettesData ( page , callback )
{
2023-06-04 18:40:29 +02:00
fetch ( getURL ( ` /json/palx?page= ${ page } ` ) , {
2022-03-10 22:36:09 +01:00
method : 'get' ,
headers : {
"Content-type" : "application/json; charset=UTF-8"
}
} )
. then ( res => {
if ( ! res . ok ) showErrorToast ( ) ;
return res . json ( ) ;
} )
. then ( json => {
palettesData = Object . assign ( { } , palettesData , json . p ) ;
if ( page < json . m ) setTimeout ( ( ) => { getPalettesData ( page + 1 , callback ) ; } , 50 ) ;
else callback ( ) ;
} )
. catch ( ( error ) => {
showToast ( error , true ) ;
} ) ;
}
2023-03-30 21:35:23 +02:00
/ *
2022-06-20 22:17:01 +02:00
function hideModes ( txt )
2022-05-11 12:29:46 +02:00
{
2022-06-20 22:17:01 +02:00
for ( let e of ( gId ( 'fxlist' ) . querySelectorAll ( '.lstI' ) || [ ] ) ) {
2022-08-01 22:01:24 +02:00
let iT = e . querySelector ( '.lstIname' ) . innerText ;
let f = false ;
if ( txt === "2D" ) f = iT . indexOf ( "\u25A6" ) >= 0 && iT . indexOf ( "\u22EE" ) < 0 ; // 2D && !1D
else f = iT . indexOf ( txt ) >= 0 ;
2022-12-23 02:59:24 +01:00
if ( f ) e . classList . add ( 'hide' ) ; //else e.classList.remove('hide');
2022-05-11 12:29:46 +02:00
}
}
2023-03-30 21:35:23 +02:00
* /
2022-03-10 22:36:09 +01:00
function search ( f , l = null )
{
f . nextElementSibling . style . display = ( f . value !== '' ) ? 'block' : 'none' ;
if ( ! l ) return ;
var el = gId ( l ) . querySelectorAll ( '.lstI' ) ;
2022-04-27 12:31:47 +02:00
// filter list items but leave (Default & Solid) always visible
2022-04-11 22:18:44 +02:00
for ( i = ( l === 'pcont' ? 0 : 1 ) ; i < el . length ; i ++ ) {
2022-03-10 22:36:09 +01:00
var it = el [ i ] ;
var itT = it . querySelector ( '.lstIname' ) . innerText . toUpperCase ( ) ;
2022-08-01 22:01:24 +02:00
it . style . display = ( itT . indexOf ( f . value . toUpperCase ( ) ) < 0 ) ? 'none' : '' ;
2022-03-10 22:36:09 +01:00
}
}
function clean ( c )
{
c . style . display = 'none' ;
var i = c . previousElementSibling ;
i . value = '' ;
i . focus ( ) ;
i . dispatchEvent ( new Event ( 'input' ) ) ;
2022-07-27 00:11:24 +02:00
if ( i . parentElement . id == 'fxFind' ) {
gId ( "filters" ) . querySelectorAll ( "input[type=checkbox]" ) . forEach ( ( e ) => { e . checked = false ; } ) ;
}
}
2023-10-26 22:09:46 +02:00
function filterFocus ( e )
{
let t = e . explicitOriginalTarget ;
2023-10-27 16:35:03 +02:00
let f = gId ( "filters" ) ;
if ( e . type === "focus" ) f . classList . remove ( 'fade' ) ; // immediately show (still has transition)
// compute sticky top (with delay for transition)
setTimeout ( ( ) => {
let sti = parseInt ( getComputedStyle ( d . documentElement ) . getPropertyValue ( '--sti' ) ) + ( e . type === "focus" ? 1 : - 1 ) * f . offsetHeight ;
sCol ( '--sti' , sti + "px" ) ;
} , 252 ) ;
if ( e . type === "blur" ) {
do {
if ( t . id && ( t . id === "fxFind" ) ) { setTimeout ( ( ) => { t . firstElementChild . focus ( ) ; } , 150 ) ; return ; }
t = t . parentElement ;
} while ( t . tagName !== "BODY" ) ;
setTimeout ( ( ) => { f . classList . add ( 'fade' ) ; } , 255 ) ; // wait with hiding
}
2023-10-26 22:09:46 +02:00
}
2022-07-27 00:11:24 +02:00
function filterFx ( o )
{
if ( ! o ) return ;
let i = gId ( 'fxFind' ) . children [ 0 ] ;
i . value = ! o . checked ? '' : o . dataset . flt ;
i . focus ( ) ;
i . dispatchEvent ( new Event ( 'input' ) ) ;
gId ( "filters" ) . querySelectorAll ( "input[type=checkbox]" ) . forEach ( ( e ) => { if ( e !== o ) e . checked = false ; } ) ;
2022-03-10 22:36:09 +01:00
}
// make sure "dur" and "transition" are arrays with at least the length of "ps"
function formatArr ( pl ) {
var l = pl . ps . length ;
if ( ! Array . isArray ( pl . dur ) ) {
var v = pl . dur ;
if ( isNaN ( v ) ) v = 100 ;
pl . dur = [ v ] ;
}
var l2 = pl . dur . length ;
if ( l2 < l )
{
for ( var i = 0 ; i < l - l2 ; i ++ )
pl . dur . push ( pl . dur [ l2 - 1 ] ) ;
}
if ( ! Array . isArray ( pl . transition ) ) {
var v = pl . transition ;
if ( isNaN ( v ) ) v = tr ;
pl . transition = [ v ] ;
}
var l2 = pl . transition . length ;
if ( l2 < l )
{
for ( var i = 0 ; i < l - l2 ; i ++ )
pl . transition . push ( pl . transition [ l2 - 1 ] ) ;
}
}
2022-03-26 23:22:18 +01:00
function expand ( i )
2022-03-10 22:36:09 +01:00
{
2022-03-26 23:22:18 +01:00
var seg = i < 100 ? gId ( 'seg' + i ) : gId ( ` p ${ i - 100 } o ` ) ;
let ps = gId ( "pcont" ) . children ; // preset wrapper
2022-12-23 02:59:24 +01:00
if ( i > 100 ) for ( let p of ps ) { p . classList . remove ( 'selected' ) ; if ( p !== seg ) p . classList . remove ( 'expanded' ) ; } // collapse all other presets & remove selected
2022-03-10 22:36:09 +01:00
2022-12-23 02:59:24 +01:00
seg . classList . toggle ( 'expanded' ) ;
2022-03-10 22:36:09 +01:00
// presets
if ( i >= 100 ) {
var p = i - 100 ;
2022-03-26 23:22:18 +01:00
if ( seg . classList . contains ( 'expanded' ) ) {
2022-03-10 22:36:09 +01:00
if ( isPlaylist ( p ) ) {
plJson [ p ] = pJson [ p ] . playlist ;
// make sure all keys are present in plJson[p]
formatArr ( plJson [ p ] ) ;
if ( isNaN ( plJson [ p ] . repeat ) ) plJson [ p ] . repeat = 0 ;
if ( ! plJson [ p ] . r ) plJson [ p ] . r = false ;
if ( isNaN ( plJson [ p ] . end ) ) plJson [ p ] . end = 0 ;
2022-03-26 23:22:18 +01:00
gId ( 'seg' + i ) . innerHTML = makeP ( p , true ) ;
2022-03-10 22:36:09 +01:00
refreshPlE ( p ) ;
} else {
2022-03-26 23:22:18 +01:00
gId ( 'seg' + i ) . innerHTML = makeP ( p ) ;
2022-03-10 22:36:09 +01:00
}
var papi = papiVal ( p ) ;
gId ( ` p ${ p } api ` ) . value = papi ;
if ( papi . indexOf ( "Please" ) == 0 ) gId ( ` p ${ p } cstgl ` ) . checked = false ;
tglCs ( p ) ;
2022-12-23 02:59:24 +01:00
gId ( 'putil' ) . classList . remove ( 'staybot' ) ;
2022-03-26 23:22:18 +01:00
} else {
updatePA ( ) ;
gId ( 'seg' + i ) . innerHTML = "" ;
2022-12-23 02:59:24 +01:00
gId ( 'putil' ) . classList . add ( 'staybot' ) ;
2022-03-26 23:22:18 +01:00
}
2022-03-10 22:36:09 +01:00
}
2022-03-26 23:22:18 +01:00
seg . scrollIntoView ( {
2022-03-10 22:36:09 +01:00
behavior : 'smooth' ,
2022-03-26 23:22:18 +01:00
block : 'center'
2022-03-10 22:36:09 +01:00
} ) ;
}
function unfocusSliders ( )
{
gId ( "sliderBri" ) . blur ( ) ;
gId ( "sliderSpeed" ) . blur ( ) ;
gId ( "sliderIntensity" ) . blur ( ) ;
}
// sliding UI
const _C = d . querySelector ( '.container' ) , N = 4 ;
2022-04-02 07:42:04 +02:00
let iSlide = 0 , x0 = null , scrollS = 0 , locked = false ;
2022-03-10 22:36:09 +01:00
function unify ( e ) { return e . changedTouches ? e . changedTouches [ 0 ] : e ; }
function hasIroClass ( classList )
{
for ( var i = 0 ; i < classList . length ; i ++ ) {
var element = classList [ i ] ;
if ( element . startsWith ( 'Iro' ) ) return true ;
}
return false ;
}
2023-02-25 09:41:15 +01:00
//required by rangetouch.js
2022-03-10 22:36:09 +01:00
function lock ( e )
{
if ( pcMode ) return ;
var l = e . target . classList ;
var pl = e . target . parentElement . classList ;
if ( l . contains ( 'noslide' ) || hasIroClass ( l ) || hasIroClass ( pl ) ) return ;
x0 = unify ( e ) . clientX ;
scrollS = gEBCN ( "tabcontent" ) [ iSlide ] . scrollTop ;
_C . classList . toggle ( 'smooth' , ! ( locked = true ) ) ;
}
2023-02-25 09:41:15 +01:00
//required by rangetouch.js
2022-03-10 22:36:09 +01:00
function move ( e )
{
if ( ! locked || pcMode ) return ;
var clientX = unify ( e ) . clientX ;
var dx = clientX - x0 ;
var s = Math . sign ( dx ) ;
2022-04-02 07:42:04 +02:00
var f = + ( s * dx / wW ) . toFixed ( 2 ) ;
2022-03-10 22:36:09 +01:00
if ( ( clientX != 0 ) &&
( iSlide > 0 || s < 0 ) && ( iSlide < N - 1 || s > 0 ) &&
f > 0.12 &&
gEBCN ( "tabcontent" ) [ iSlide ] . scrollTop == scrollS )
{
_C . style . setProperty ( '--i' , iSlide -= s ) ;
f = 1 - f ;
updateTablinks ( iSlide ) ;
}
_C . style . setProperty ( '--f' , f ) ;
_C . classList . toggle ( 'smooth' , ! ( locked = false ) ) ;
x0 = null ;
}
function size ( )
{
wW = window . innerWidth ;
var h = gId ( 'top' ) . clientHeight ;
sCol ( '--th' , h + "px" ) ;
sCol ( '--bh' , gId ( 'bot' ) . clientHeight + "px" ) ;
if ( isLv ) h -= 4 ;
sCol ( '--tp' , h + "px" ) ;
togglePcMode ( ) ;
2023-03-31 13:26:03 +02:00
lastw = wW ;
2022-03-10 22:36:09 +01:00
}
function togglePcMode ( fromB = false )
{
if ( fromB ) {
pcModeA = ! pcModeA ;
localStorage . setItem ( 'pcm' , pcModeA ) ;
}
2023-03-31 13:26:03 +02:00
pcMode = ( wW >= 1024 ) && pcModeA ;
if ( cpick ) cpick . resize ( pcMode && wW > 1023 && wW < 1250 ? 230 : 260 ) ; // for tablet in landscape
if ( ! fromB && ( ( wW < 1024 && lastw < 1024 ) || ( wW >= 1024 && lastw >= 1024 ) ) ) return ; // no change in size and called from size()
2022-03-10 22:36:09 +01:00
openTab ( 0 , true ) ;
updateTablinks ( 0 ) ;
gId ( 'buttonPcm' ) . className = ( pcMode ) ? "active" : "" ;
gId ( 'bot' ) . style . height = ( pcMode && ! cfg . comp . pcmbot ) ? "0" : "auto" ;
sCol ( '--bh' , gId ( 'bot' ) . clientHeight + "px" ) ;
_C . style . width = ( pcMode ) ? '100%' : '400%' ;
}
function mergeDeep ( target , ... sources )
{
if ( ! sources . length ) return target ;
const source = sources . shift ( ) ;
if ( isObj ( target ) && isObj ( source ) ) {
for ( const key in source ) {
if ( isObj ( source [ key ] ) ) {
if ( ! target [ key ] ) Object . assign ( target , { [ key ] : { } } ) ;
mergeDeep ( target [ key ] , source [ key ] ) ;
} else {
Object . assign ( target , { [ key ] : source [ key ] } ) ;
}
}
}
return mergeDeep ( target , ... sources ) ;
}
2023-10-20 21:32:39 +02:00
function tooltip ( )
{
const elements = d . querySelectorAll ( "[tooltip]" ) ;
elements . forEach ( ( element ) => {
element . addEventListener ( "mouseover" , ( ) => {
const tooltip = d . createElement ( "span" ) ;
tooltip . className = "tooltip" ;
tooltip . textContent = element . getAttribute ( "tooltip" ) ;
2023-10-26 22:09:46 +02:00
2023-10-20 21:32:39 +02:00
let { top , left , width } = element . getBoundingClientRect ( ) ;
2023-10-26 22:09:46 +02:00
2023-10-20 21:32:39 +02:00
d . body . appendChild ( tooltip ) ;
2023-10-26 22:09:46 +02:00
2023-10-20 21:32:39 +02:00
const { offsetHeight , offsetWidth } = tooltip ;
2023-10-28 21:02:49 +02:00
const offset = element . classList . contains ( "sliderwrap" ) ? 4 : 10 ;
2023-10-21 19:18:45 +02:00
top -= offsetHeight + offset ;
left += ( width - offsetWidth ) / 2 ;
2023-10-20 21:32:39 +02:00
tooltip . style . top = top + "px" ;
tooltip . style . left = left + "px" ;
tooltip . classList . add ( "visible" ) ;
} ) ;
2023-10-26 22:09:46 +02:00
2023-10-20 21:32:39 +02:00
element . addEventListener ( "mouseout" , ( ) => {
2023-10-30 13:43:50 +01:00
const tooltips = d . querySelectorAll ( '.tooltip' ) ;
tooltips . forEach ( ( tooltip ) => {
tooltip . classList . remove ( "visible" ) ;
d . body . removeChild ( tooltip ) ;
} ) ;
2023-10-20 21:32:39 +02:00
} ) ;
} ) ;
2023-10-19 17:47:37 +02:00
} ;
2022-03-10 22:36:09 +01:00
size ( ) ;
_C . style . setProperty ( '--n' , N ) ;
2023-03-31 13:26:03 +02:00
window . addEventListener ( 'resize' , size , true ) ;
2022-03-10 22:36:09 +01:00
_C . addEventListener ( 'mousedown' , lock , false ) ;
_C . addEventListener ( 'touchstart' , lock , false ) ;
_C . addEventListener ( 'mouseout' , move , false ) ;
_C . addEventListener ( 'mouseup' , move , false ) ;
_C . addEventListener ( 'touchend' , move , false ) ;