From f273dab45d970ac74350872a085ea59b5cd0439a Mon Sep 17 00:00:00 2001 From: sparks-alec Date: Sun, 28 May 2023 07:06:18 -0400 Subject: [PATCH 01/21] update styles for QLab 5 --- plugins/qlab/img/disarmed-pattern-light.png | Bin 2556 -> 3431 bytes plugins/qlab/img/playhead.afdesign | Bin 0 -> 22559 bytes plugins/qlab/img/playhead.png | Bin 0 -> 542 bytes plugins/qlab/styles.css | 101 +++++++++++--------- 4 files changed, 56 insertions(+), 45 deletions(-) create mode 100644 plugins/qlab/img/playhead.afdesign create mode 100644 plugins/qlab/img/playhead.png diff --git a/plugins/qlab/img/disarmed-pattern-light.png b/plugins/qlab/img/disarmed-pattern-light.png index 4d74cc7ff6774d4578e9b1a6866344cc60054a2b..eb60191375f122acca4f09be3a936fd7dd1bcc18 100644 GIT binary patch literal 3431 zcmcgve{37|6?caSC0)0UR%{&|JC6QHf%BcuKHGP*TM|2IOygoD-s*(5>G|$^_F?t&m-xQ#z0do;_kHj4eYY#NX~W|Buhu&pj>VDIa69~OfM?mlI{5o(%lD2t z9E%pI@r0F#wh5A+bBeN_M9yL^4|0biu)dfVr3|u&Buc4TkUW3%I7z5-klf5gnP@(Q z(rW7t19j}!6qj~nBwi-hud5Ff1pvq)OC*Z9tY!+uAZgQ3N$FQPqV}# zrI|Nkh!RE^nUbOAEmhYDj43Adf)yl5kP~HaS*l>?3r5yPmL(cxQ4VRANxPgbx>i4* z)-Byk>-mbAW|ngX*`q6BFl43~TB=eFW10ucd69 zO0I;Ep4E-0E~6mno*{~aLNP;E)GWx%_6*I$gFdP9GrT@&+96HN~D-oG4%;hsVT?-B_l;FWG#{|ThN#&r%VF$ET{pyyHYom6?@V$WbMX5vqlHEnJ zT|)MyfraWURW$FTub`N^V)cp!YD__e&$H<&#=U6-1T}YGmydJm$Y+mD*Lsj)p&8NT zMMDIpLxve76+_PvCECXMB`m%ChDm%rK1{J^Ccc!0(nByply9V|l+Tt`S@bZh8`@G) ze16K~;uVTlWFJtB!f-N5%8JJ~{cJ8*R$VN``r@oxV1dDWKx>#S{|^z*n~f@^aQP$l z2`)_Jb4P5C50xIt8@gPOs>fWC&fJ!wsBq`?m97}Dnc&SXdNQ0U*V1E6_E6a6{1<=eIa1$pZu!ZB`cC&GW7Zwsb9nEm>*|OhI(;^BJhSeFGe$r< z^yAgXo_=}R)~|i>`~Bn3?Ec0r%CV0QJoDa9FTQzTX=LkyjgNh}|Is_IJiGSnnL8V~ zzB``l7&&pxONU;%KI&}vPUgLj>W}X~c;v&Ok*<|19GRm}-u$0|iQ|cpG%xn?f8X-# z6Y=xX@QH^2AAIS2%bL-ZJGZpC8}AnDmOs>&cwyqP?r)8ce7#}vwI|8v?;E{o zm1i)r?`L;!7=JW=`)zHze)8a+$K{Vd)w?f@Kk)1Jciz3~;y&@%g??$#uy66iKgXEi z-7kK;@XMcc{rA-cKRh-t+-p4~bDi(*hcA(<^R1mGe5@?R&jQDR zgI|F~7z`TYt-JpA#%~tCNPIs0`fn$<4~$>?`-#E9yV~j&uDW+$^3Cqk?{DhL?0&d# z%gJ{JetDWd)NI2zob v-aZk(nR5=w8-_OS-DoVm>fGLcZ;xzKH)q~YkI>iP@1scbrtp!b?fw4);RICd delta 1211 zcmbVLOH30{6rHvp{{({qH35bxE*fcPI&CR^f$}R#MLzOTNicEjJlfHAW}JB-L?g=5 zg<9Uuu+YSXurM)7{3Hg932uyTB&;xTb$HZlNpB{pu*d_0?BZo$K{ zgh+u5zGN=1q}8Z`6g{e_phJl%8tDP4-p$}evmNK>w&Djly|_EKYdSzTrP6MfP}1H= zgn*hj1_t4o^e!d^8k^W8Ms~*Ge?ch;LzYe1+PC|PVIV?1tVwFJ)U;Py3Q=;;Anl8zK@Dy^SgD6ON;2?T_& z(dw`U$>4}g3N|4;lm~VgA$TpXHnaVl@3OO-joB`t$P-Z86hWBJueW8IxGh!}CNgFS zFXV4})fATR^Z?G68P-&inArN$0^z72N1)iuu*_c*$rc9^F3oMs4Lg4yn25nG~@kbLeJJOUU^vb z{xZ%vJy*AEJ)kXH*Vc>dp@Xxtx9Z69<-=L?)0a#BP|Yqsv7)}CuV*EaCMBR diff --git a/plugins/qlab/img/playhead.afdesign b/plugins/qlab/img/playhead.afdesign new file mode 100644 index 0000000000000000000000000000000000000000..2fe34924cf8aba554884f46e7587675a15413414 GIT binary patch literal 22559 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 y38b7_nA5DLWuX`T+V{x12M#J2?#}~%{ literal 0 HcmV?d00001 diff --git a/plugins/qlab/img/playhead.png b/plugins/qlab/img/playhead.png new file mode 100644 index 0000000000000000000000000000000000000000..382664e90f39c53c1c26b74db826dba916715494 GIT binary patch literal 542 zcmV+(0^$9MP)X1^@s62ynlM00045X+uL$Oh+I{ zL{cC$O8@{Uk!_H%OF~f?#gC;F@rH=D3M9c6lTw#PGqA|Ptf1PSSJOc4b>E8!qN$-V z8X5|Rs44mbswww|q6mVZt^R;Of|A#{TB+5AkMrYj?!6xeXlc)hyy*mxFIX}W9d*ym zFSy%Y;6e{Hc;V57X@ukP7$f-B=2O0hL+S0HdTZct+jF3u^dId#?_Tc*Z~o?+PDd(j z3hD>PFAQk`4j(zbW*Le%$la1lk#`hlYJ99XS>v;6W+o9KUy(aSR!@;1$-ZQRpK0KH zZdH8kptjf2h1qFp7pE67MB&Cb#*m0;Q6nOZ}%+N7-pE1_L$L zB;b0p?bkk_?E}@QqyPW_ literal 0 HcmV?d00001 diff --git a/plugins/qlab/styles.css b/plugins/qlab/styles.css index 65d297e..d7fc750 100644 --- a/plugins/qlab/styles.css +++ b/plugins/qlab/styles.css @@ -33,61 +33,60 @@ td img { } tr:nth-child(even) { - background: #323232; + background: #2d2d2d; } tr:nth-child(odd) { - background: #2c2c2c; + background: #262626; } -tr:nth-child(even).q-red { - background: linear-gradient(to top, #2c2c2c, #4c3130 1px); -} tr:nth-child(odd).q-red { - background: linear-gradient(to top, #323232, #513635 1px); + background: linear-gradient(to top, #262626, #49302f 1px); } -.q-red td:first-of-type { - padding-left: 5px; - border-left: 5px #ff4242 solid; +tr:nth-child(even).q-red { + background: linear-gradient(to top, #2d2c2d, #513635 1px); +} +.q-red td.playhead { + background: linear-gradient(to right, #ff4242 55%, transparent 55%); +} + +tr:nth-child(odd).q-orange { + background: linear-gradient(to top, #262626, #443526 1px); } tr:nth-child(even).q-orange { - background: linear-gradient(to top, #2c2c2c, #4b3e2a 1px); + background: linear-gradient(to top, #2d2c2d, #4a3b2c 1px); } -tr:nth-child(odd).q-orange { - background: linear-gradient(to top, #323232, #514330 1px); +.q-orange td.playhead { + background: linear-gradient(to right, #ffa500 55%, transparent 55%); } -.q-orange td:first-of-type { - padding-left: 5px; - border-left: 5px #ffa500 solid; + +tr:nth-child(odd).q-green { + background: linear-gradient(to top, #262626, #2d3d27 1px); } tr:nth-child(even).q-green { - background: linear-gradient(to top, #2c2c2c, #2d452d 1px); + background: linear-gradient(to top, #2d2c2d, #32422e 1px); } -tr:nth-child(odd).q-green { - background: linear-gradient(to top, #323232, #324b33 1px); +.q-green td.playhead { + background: linear-gradient(to right, #01d52f 55%, transparent 55%); } -.q-green td:first-of-type { - padding-left: 5px; - border-left: 5px #01d52f solid; + +tr:nth-child(odd).q-blue { + background: linear-gradient(to top, #262626, #292d40 1px); } tr:nth-child(even).q-blue { - background: linear-gradient(to top, #2c2c2c, #323647 1px); + background: linear-gradient(to top, #2d2c2d, #2f3346 1px); } -tr:nth-child(odd).q-blue { - background: linear-gradient(to top, #323232, #363b4b 1px); +.q-blue td.playhead { + background: linear-gradient(to right, #536de0 55%, transparent 55%); } -.q-blue td:first-of-type { - padding-left: 5px; - border-left: 5px #536de0 solid; + +tr:nth-child(odd).q-purple { + background: linear-gradient(to top, #262626, #342639 1px); } tr:nth-child(even).q-purple { - background: linear-gradient(to top, #2c2c2c, #39273d 1px); + background: linear-gradient(to top, #2d2c2d, #382c3f 1px); } -tr:nth-child(odd).q-purple { - background: linear-gradient(to top, #323232, #433147 1px); -} -.q-purple td:first-of-type { - padding-left: 5px; - border-left: 5px #a601c0 solid; +.q-purple td.playhead { + background: linear-gradient(to right, #a601c0 55%, transparent 55%); } tr.q-armed-false td { @@ -96,13 +95,22 @@ tr.q-armed-false td { } tr.playback-position { - background-image: none !important; - background: #1557da !important; - color: white !important; + background: #444 !important; + color: white; +} +tr.playback-position td.playhead { + padding: 0px; +} +tr.playback-position td.playhead::before { + content: url(img/playhead.png); } tr.playback-position .q-gray-text { color: white !important; } +tr.selected { + background: #1557da !important; + color: white !important; +} .gLeft { height: 24px; @@ -140,22 +148,19 @@ tr.playback-position .q-gray-text { border-radius: 6px; } .gMode-1 { - border-color: #5a5a87; + border-color: #48477f; } .gMode-2 { - border-color: #5a5a87; + border-color: #48477f; } .gMode-3 { - border-color: #429242; + border-color: #43a424; } .gMode-4 { - border-color: #925fc0; -} -.gMode-4 { - border-color: #925fc0; + border-color: #7f26a5; } .gMode-6 { - border-color: #d05b15; + border-color: #ee6a21; } .gMode-, .gMode-0 { @@ -172,6 +177,12 @@ tr.playback-position .q-gray-text { font-size: 14px; text-align: center; } +.q-target { + text-align: center; + font-size: 12px; + padding-left: 5px; + padding-right: 5px; +} .q-gray-text { color: #424242; } From 03ad8f96825b30fe61e56ac8199ca81d875935cf Mon Sep 17 00:00:00 2001 From: sparks-alec Date: Sun, 28 May 2023 07:06:57 -0400 Subject: [PATCH 02/21] refactor QLab --- plugins/qlab/cue.ejs | 261 +++++++++++-------- plugins/qlab/cuelist.ejs | 27 +- plugins/qlab/main.js | 529 +++++++++++++------------------------- plugins/qlab/template.ejs | 30 +-- 4 files changed, 362 insertions(+), 485 deletions(-) diff --git a/plugins/qlab/cue.ejs b/plugins/qlab/cue.ejs index e2b97ef..d6f78cf 100644 --- a/plugins/qlab/cue.ejs +++ b/plugins/qlab/cue.ejs @@ -1,145 +1,190 @@ +<% let cueKeys = allCues[cue.uniqueID]; %> <% let playbackPosition = workspace.playbackPosition==cue.uniqueID ? "playback-position" : "" %> - +<% let selected = workspace.selected.includes(cue.uniqueID) ? "selected": "" %> - +<% if(cueKeys && cueKeys.parent){ %> -
- <% if(cue.broken){ %> - + - <% }else if(cue.running){ %> - - - <% }else if(cue.paused){ %> - - - <% }else if(cue.loaded){ %> - - - <% }else if(cue.flagged){ %> - - - <% }else{ %> + + - <% } %> -
- <% if(cue.type == "Group" && cue.groupMode){ %> - - - <% }else{ %> - - - <% } %> + +
+ <% if(cueKeys.isBroken){ %> + + + <% }else if(cueKeys.isRunning){ %> + + + <% }else if(cueKeys.isPaused){ %> + + + <% }else if(cueKeys.isLoaded){ %> + + + <% }else if(cueKeys.isFlagged){ %> + + + <% }else{ %> + + <% } %> +
- - -
<%= cue.number || " " %>
- - - <% for(let i=0; i - - <% if(cue.cues && cue.cues.length==0 && i==cue.nestedGroupModes.length-1){ %> - - - <% }else if(cue.cues && i==cue.nestedGroupModes.length-1){ %> - - - <% }else if(cue.nestedGroupPosition[i]<=0){ %> - <% if(i - - - <% }else{ %> - - - <% } %> - - <% }else if(i - - - <% }else{ %> - - - <% } %> - - - <% } %> - - - - - - - <% if(cue.type=="Group"){ %> - <% if(cue.cues.length==0){ %> - <%= cue.listName %> + + <% if(cueKeys.type == "Group" && cueKeys.mode){ %> + <% }else{ %> - <%= cue.listName %> + <% } %> - <% }else if(cue.nestedGroupPosition[1]==0){ %> - <%= cue.listName %> - - <% }else{ %> - <%= cue.listName %> - - <% } %> - <%= cue.target %> + +
<%= cueKeys.number || " " %>
+ + + + + <% for(let i=0; i + + <% if(cue.type=="Group" && cue.cues.length==0 && i==cueKeys.nestedGroupModes.length-1){ %> + + + <% }else if(cue.type=="Group" && i==cueKeys.nestedGroupModes.length-1){ %> + + + <% }else if(cueKeys.nestedGroupPosition[i]<=0){ %> + <% if(cueKeys.nestedGroupPosition.slice(i+1).every(v => v === 0)){ %> + + + <% }else{ %> + + + <% } %> + + <% }else if(i + + + <% }else if(cueKeys.nestedGroupPosition[i]==undefined && cueKeys.nestedGroupPosition[i-1]==0){ %> + + + <% }else{ %> + + + <% } %> + + + <% } %> + + + + + + <% if(cue.type=="Group"){ %> + + <% if(cue.cues.length==0){ %> + <%= cueKeys.listName %> + + <% }else{ %> + <%= cueKeys.listName %> + + <% } %> + + <% }else if(cueKeys.nestedGroupPosition[cueKeys.nestedGroupModes.length-2]==0){ %> + <%= cueKeys.listName %> + + <% }else{ %> + <%= cueKeys.listName %> + + <% } %> + + + <% if(cueKeys.currentCueTarget){ %> + <%= allCues[cueKeys.currentCueTarget].number || allCues[cueKeys.currentCueTarget].listName %> - <% if(cue.preWait){ %> - <%= elapsedTime(cue.preWait, cue.preWaitElapsed, "preWait", cue) %> + <% }else{ %> + - <% }else{ %> - 00:00.00 - - <% } %> + <% } %> + - <% let cueTypesWithAction = ["Audio", "Mic", "Video", "Camera", "Text", "Light", "Fade", "Network", "MIDI File", "Timecode", "Wait"]; %> - <% if((cue.type=="Group" && cue.groupMode==3) || cueTypesWithAction.indexOf(cue.type)>=0){ %> - <%= elapsedTime(cue.duration, cue.actionElapsed, "action", cue) %> + + <% if(cueKeys.preWait){ %> + <%= elapsedTime(cueKeys.preWait, cueKeys.preWaitElapsed, "preWait", cueKeys) %> - <% }else{ %> - + <% }else{ %> + 00:00.00 - <% } %> + <% } %> - <% if(cue.postWait){ %> - <%= elapsedTime(cue.postWait, cue.postWaitElapsed, "postWait", cue) %> + + <% let cueTypesWithAction = ["Audio", "Mic", "Video", "Camera", "Text", "Light", "Fade", "Network", "MIDI File", "Timecode", "Wait"]; %> + <% if((cueKeys.type=="Group" && cueKeys.mode==3) || (cueKeys.type=="Group" && cueKeys.mode==6) || cueTypesWithAction.includes(cueKeys.type)){ %> + <%= elapsedTime(cueKeys.duration, cueKeys.actionElapsed, "action", cueKeys) %> - <% }else{ %> - 00:00.00 + <% }else{ %> + - <% } %> + <% } %> + + + + <% if(cueKeys.postWait){ %> + <%= elapsedTime(cueKeys.postWait, cueKeys.postWaitElapsed, "postWait", cueKeys) %> + + <% }else{ %> + 00:00.00 + + <% } %> - <% if(cue.continueMode==2){ %> -
+ + <% if(cueKeys.continueMode==2){ %> +
- <% }else if(cue.continueMode==1){ %> -
+ <% }else if(cueKeys.continueMode==1){ %> +
- <% }else{ %> - + <% }else{ %> + - <% } %> + <% } %> -<% - function prettyFormatTime(seconds){ + + <% }else{ %> + + + + + +
<%= cue.number || " " %>
+ <%= cue.listName %> + + 00:00.00 + + 00:00.00 + + + +<% } %> + +<% +function prettyFormatTime(seconds){ if(!seconds){ return "00:00.00"; } @@ -151,14 +196,14 @@ return string.substring(startIndex, string.length-2) } - function elapsedTime(def, value, type, cue){ +function elapsedTime(def, value, type, cue){ - if(type=="action" && cue.paused){ + if(type=="action" && cue.isPaused){ value-=cue.preWait; } let border, fill; - if(cue.paused){ + if(cue.isPaused){ border = "#f6e737"; fill = "rgba(255, 240, 60, 0.7)"; }else{ @@ -171,7 +216,7 @@ let percent = value/def*100; let bg = `style='background: black; background: linear-gradient(90deg, ${fill} ${percent}%, transparent ${percent}%);`; - if(cue.paused){ + if(cue.isPaused){ bg+= "outline-color: "+border+";"; bg+= "color: white;"; } @@ -179,7 +224,7 @@ return `
${prettyFormatTime(Math.min(def, value))}`; - }else if(type=="postWait" || type=="action"){ + }else if(type=="postWait" && cue.continueMode==0){ return `${prettyFormatTime(def)}`; }else{ diff --git a/plugins/qlab/cuelist.ejs b/plugins/qlab/cuelist.ejs index 93f1ecc..a3d64f8 100644 --- a/plugins/qlab/cuelist.ejs +++ b/plugins/qlab/cuelist.ejs @@ -1,13 +1,13 @@ -

<%= workspace.displayName %> — <%= cueList.displayName %>

+

<%= workspace.displayName %> — <%= cueList.listName %>

- + - + @@ -18,17 +18,17 @@ - - - + + + - <% for(const cue_id in cueList.cues){ %> - <%= displayCueRow(cueList.cues[cue_id]) %> + <% for(let i=0; i + <%= displayCueRow(cueList.cues[i]) %> <% } %> @@ -36,16 +36,13 @@ <% - - - function displayCueRow(q){ - let html = rowTemplate({cue: allCues[q.uniqueID], allCues: allCues, workspace: workspace}); + let html = rowTemplate({cue: q, allCues: allCues, workspace: workspace}); if(q.cues){ - q.cues.forEach(q =>{ - html += displayCueRow(q); - }) + for(let i=0; i { - device.data.workspaces[wksp.uniqueID] = { - uniqueID: wksp.uniqueID, - displayName: wksp.displayName, - cueLists: {}, - cues: {}, + for (let i = 0; i < json.length; i++) { + device.data.workspaces[json[i].uniqueID] = { + version: json[i].version, + displayName: json[i].displayName, + port: json[i].port, + udpReplyPort: json[i].udpReplyPort, + cueLists: [], + selected: [], }; - device.send(`/workspace/${wksp.uniqueID}/connect`, device.fields.passcode); - }); - - if (Object.keys(device.data.workspaces).length === 0) { - device.data.permission = 'no workspaces'; - device.draw(); + device.send(`/workspace/${json[i].uniqueID}/connect`, device.fields.passcode); } - } else if (match(oscAddressParts, ['reply', 'workspace', '*', 'connect'])) { - device.send(`/workspace/${oscAddressParts[2]}/updates`, [{ type: 'i', value: 1 }]); - device.send(`/workspace/${oscAddressParts[2]}/cueLists`); - } else if (match(oscAddressParts, ['reply', 'workspace', '*', 'cueLists'])) { this.deviceInfoUpdate(device, 'status', 'ok'); - const workspace = device.data.workspaces[oscAddressParts[2]]; - const json = JSON.parse(oscData.args[0]); - - workspace.cueLists = {}; - workspace.cues = {}; - - if (json.status === 'denied') { + } else if (/reply\/workspace\/.*\/connect/.test(oscData.address)) { + const json = JSON.parse(oscData.args[0]).data; + if (json === 'badpass') { device.data.permission = 'denied'; - device.draw(); - } else if (json.data) { - device.data.permission = 'ok'; - - json.data.forEach((cueList) => { - workspace.cueLists[cueList.uniqueID] = cueList; - addCueToWorkspace(workspace, cueList); - }); - - device.draw(); - - setTimeout(() => { - json.data.forEach((ql) => { - workspace.cueLists[ql.uniqueID] = ql; - getValuesForKeys(device, json.workspace_id, ql); - }); - }, 0); - } - } else if ( - match(oscAddressParts, ['reply', 'cue_id', '*', 'children']) || - match(oscAddressParts, ['reply', 'workspace', '*', 'cue_id', '*', 'children']) - ) { - const json = JSON.parse(oscData.args[0]); - const workspace = device.data.workspaces[json.workspace_id]; - const cueID = json.address.substring(55, 91); - const cue = workspace.cues[cueID]; - - if (!_.isEqual(cue.cues, json.data)) { - workspace.cueLists[cueID].cues = json.data; - addCueToWorkspace(workspace, workspace.cueLists[cueID]); - device.draw(); - getValuesForKeys(device, json.workspace_id, cue); - } - } else if ( - match(oscAddressParts, ['reply', 'cue_id', '*', 'valuesForKeys']) || - match(oscAddressParts, ['reply', 'workspace', '*', 'cue_id', '*', 'valuesForKeys']) - ) { - const json = JSON.parse(oscData.args[0]); - const cueValues = json.data; - const workspace = device.data.workspaces[json.workspace_id]; - let cue = workspace.cues[cueValues.uniqueID]; - - if (!cue) { - workspace.cues[cueValues.uniqueID] = {}; - cue = workspace.cues[cueValues.uniqueID]; - } - - cue.uniqueID = cueValues.uniqueID; - cue.number = cueValues.number; - cue.name = cueValues.name; - cue.listName = cueValues.listName; - cue.displayName = cueValues.displayName; - cue.broken = cueValues.isBroken; - cue.running = cueValues.isRunning; - cue.loaded = cueValues.isLoaded; - cue.flagged = cueValues.isFlagged; - cue.paused = cueValues.isPaused; - cue.type = cueValues.type; - // cue.cues = cueValues.children; - cue.preWait = cueValues.preWait; - cue.postWait = cueValues.postWait; - cue.duration = cueValues.currentDuration; - cue.colorName = cueValues.colorName; - cue.continueMode = cueValues.continueMode; - cue.groupMode = cueValues.mode; - cue.parent = cueValues.parent; - cue.cartRows = cueValues.cartRows; - cue.cartColumns = cueValues.cartColumns; - cue.cartPosition = cueValues.cartPosition; - cue.preWaitElapsed = cueValues.preWaitElapsed; - cue.actionElapsed = cueValues.actionElapsed; - cue.postWaitElapsed = cueValues.postWaitElapsed; - - // QLab 5 fix - if (cueValues.type === 'Group' || cueValues.type === 'Cue List') { - cue.cues = cueValues.children; } else { - cue.cues = undefined; - } - - const nestedGroupModes = []; - const nestedGroupPosition = []; - - let obj = cue; - let sum = 0; - - if (obj.cues) { - sum += obj.cues.length; - } - - while (obj.parent !== '[root group of cue lists]') { - let pos = _.findIndex(workspace.cues[obj.parent].cues, { - uniqueID: obj.uniqueID, - }); - pos = Math.abs(pos - workspace.cues[obj.parent].cues.length) - 1; - - if (obj.cues === undefined) { - sum += pos; - } - - nestedGroupPosition.unshift(sum); - - if (obj.cues) { - sum += pos; - nestedGroupModes.unshift(obj.groupMode); - } else { - nestedGroupModes.unshift(workspace.cues[obj.parent].groupMode); - } - - obj = workspace.cues[obj.parent]; - } - - cue.nestedGroupModes = nestedGroupModes; - cue.nestedGroupPosition = nestedGroupPosition; - - device.update('updateCueData', { - cue, - allCues: workspace.cues, - workspace, - }); - } else if (match(oscAddressParts, ['reply', 'cue_id', '*', 'preWaitElapsed'])) { - const oscArgs = JSON.parse(oscData.args[0]); - if (oscArgs.status !== 'error') { - const workspace = device.data.workspaces[oscArgs.workspace_id]; - if (workspace) { - const cue = workspace.cues[oscArgs.address.substring(55, 91)]; - - if (cue) { - cue.preWaitElapsed = oscArgs.data; - lastElapsedUpdate = Date.now(); - - device.update('updateCueData', { - cue, - allCues: workspace.cues, - workspace, - }); - } - } - } - } else if (match(oscAddressParts, ['reply', 'cue_id', '*', 'actionElapsed'])) { - const oscArgs = JSON.parse(oscData.args[0]); - if (oscArgs.status !== 'error') { - const workspace = device.data.workspaces[oscArgs.workspace_id]; - if (workspace) { - const cue = workspace.cues[oscArgs.address.substring(55, 91)]; - - if (cue) { - cue.actionElapsed = oscArgs.data; - lastElapsedUpdate = Date.now(); - - device.update('updateCueData', { - cue, - allCues: workspace.cues, - workspace, - }); - } - } - } - } else if (match(oscAddressParts, ['reply', 'cue_id', '*', 'postWaitElapsed'])) { - const oscArgs = JSON.parse(oscData.args[0]); - if (oscArgs.status !== 'error') { - const workspace = device.data.workspaces[oscArgs.workspace_id]; - if (workspace) { - const cue = workspace.cues[oscArgs.address.substring(55, 91)]; - - if (cue) { - cue.postWaitElapsed = oscArgs.data; - lastElapsedUpdate = Date.now(); - - device.update('updateCueData', { - cue, - allCues: workspace.cues, - workspace, - }); - } - } - } - } else if (match(oscAddressParts, ['update', 'workspace', '*', 'cue_id', '*'])) { - const workspace = device.data.workspaces[oscAddressParts[2]]; - - if (workspace) { - const cueLists = Object.keys(workspace.cueLists); - const cueID = oscAddressParts[4]; - - if (cueID !== '[root group of cue lists') { - if (cueLists.includes(cueID)) { - device.send(`/workspace/${workspace.uniqueID}/cue_id/${cueID}/children`); - } - - device.send(`/workspace/${oscAddressParts[2]}/cue_id/${cueID}/valuesForKeys`, [ - { type: 's', value: valuesForKeysString }, - ]); - } - } - } else if (match(oscAddressParts, ['update', 'workspace', '*'])) { - // occurs when cue lists are reordered or a list is deleted - device.send(`/workspace/${oscAddressParts[2]}/cueLists`); - } else if (match(oscAddressParts, ['update', 'workspace', '*', 'dashboard'])) { - // this workspace might be new, let's check - if (device.data.workspaces[oscAddressParts[2]] === undefined) { - device.send('/workspaces'); - } - } else if (match(oscAddressParts, ['update', 'workspace', '*', 'cueList', '*', 'playbackPosition'])) { - const workspace = device.data.workspaces[oscAddressParts[2]]; - - if (workspace) { - const cue = workspace.cues[oscData.args[0]]; - - if (cue) { - workspace.playbackPosition = oscData.args[0] ? cue.uniqueID : ''; - device.update('updatePlaybackPosition', { cue }); - } - } - } else if (match(oscAddressParts, ['update', 'workspace', '*', 'disconnect'])) { - delete device.data.workspaces[oscAddressParts[2]]; - if (Object.keys(device.data.workspaces).length === 0) { - device.data.permission = 'no workspaces'; + device.data.permission = 'ok'; + device.send(`/workspace/${msgAddr[2]}/cueLists`); + device.send(`/workspace/${msgAddr[2]}/updates`, [{ type: 'i', value: 1 }]); } + } else if (/reply\/workspace\/.*\/cueLists/.test(oscData.address)) { + const json = JSON.parse(oscData.args[0]).data; + device.data.workspaces[msgAddr[2]].cueLists = json; + device.data.somethingPlaying = false; + processCueList(device.data.workspaces[msgAddr[2]].cueLists, Object.keys(device.data.cueKeys), device, []); device.draw(); - } else if (match(oscAddressParts, ['reply', 'thump'])) { - // intermittent connection check even if nothing's happening - } else { - // console.log(address) + } else if (/reply\/cue_id\/.*\/valuesForKeys/.test(oscData.address)) { + const json = JSON.parse(oscData.args[0]); + const keyValues = json.data; + const cue = device.data.cueKeys[msgAddr[2]]; + + if (cue === undefined) { + // this cue is new! + console.log('HERE'); + // device.send(`/workspace/${json.workspace_id}/cueLists`); + // device.draw(); + } else { + // console.log(keyValues.number); + cue.uniqueID = keyValues.uniqueID; + cue.number = keyValues.number; + cue.listName = keyValues.listName; + cue.isBroken = keyValues.isBroken; + cue.isRunning = keyValues.isRunning; + cue.isLoaded = keyValues.isLoaded; + cue.isFlagged = keyValues.isFlagged; + cue.type = keyValues.type; + cue.cues = keyValues.children; + cue.preWait = keyValues.preWait; + cue.postWait = keyValues.postWait; + cue.duration = keyValues.duration; + cue.colorName = keyValues.colorName; + cue.continueMode = keyValues.continueMode; + cue.mode = keyValues.mode; + cue.parent = keyValues.parent; + cue.cartRows = keyValues.cartRows; + cue.cartColumns = keyValues.cartColumns; + cue.cartPosition = keyValues.cartPosition; + cue.displayName = keyValues.displayName; + cue.preWaitElapsed = keyValues.preWaitElapsed; + cue.actionElapsed = keyValues.actionElapsed; + cue.postWaitElapsed = keyValues.postWaitElapsed; + cue.isPaused = keyValues.isPaused; + cue.currentCueTarget = keyValues.currentCueTarget; + cue.armed = keyValues.armed; + + if (keyValues.isRunning) { + device.data.somethingPlaying = true; + } + + let testCue = device.data.cueKeys[msgAddr[2]]; + let nestedGroupModes; + + if (cue.type === 'Group') { + nestedGroupModes = [testCue.mode]; + } else if (cue.parent !== '[root group of cue lists]') { + nestedGroupModes = [device.data.cueKeys[cue.parent].mode]; + } + + while (testCue.parent !== '[root group of cue lists]') { + testCue = device.data.cueKeys[testCue.parent]; + if (testCue === undefined) { + break; + } + + nestedGroupModes.unshift(testCue.mode); + cue.nestedGroupModes = nestedGroupModes; + } + if (cue.type === 'Cue List' || cue.type === 'Group') { + device.data.cueKeys[msgAddr[2]].cueInWorkspace.cues = [...keyValues.children]; + } + if (cue.type !== 'Cue List') { + device.update('updateCueRow', { cue, workspace: device.data.workspaces[json.workspace_id] }); + } + } + // console.log(cue); + } else if (/reply\/cue_id\/active\/.*Elapsed/.test(oscData.address)) { + const json = JSON.parse(oscData.args[0]); + const addrParts = json.address.split('/'); + const cueID = addrParts[4]; + const keyName = addrParts[5]; + device.data.cueKeys[cueID][keyName] = json.data; + device.data.somethingPlaying = true; + device.draw(); + } else if (/reply\/workspace\/.*\/selectedCues/.test(oscData.address)) { + const json = JSON.parse(oscData.args[0]); + const workspace = device.data.workspaces[msgAddr[2]]; + workspace.selected = []; + for (let i = 0; i < json.data.length; i++) { + workspace.selected.push(json.data[i].uniqueID); + } + device.update('updatePlaybackAndSelected', { workspace: device.data.workspaces[json.workspace_id] }); + } else if (/update\/workspace\/.*\/cue_id\/.*/.test(oscData.address)) { + if (device.data.cueKeys[msgAddr[4]] && device.data.cueKeys[msgAddr[4]].type === 'Group') { + device.send(`/workspace/${msgAddr[2]}/cueLists`); + } else { + device.send(`/cue_id/${msgAddr[4]}/valuesForKeys`, [{ type: 's', value: valuesForKeysString }]); + } + } else if (/update\/workspace\/.*\/cueList\/.*\/playbackPosition/.test(oscData.address)) { + const workspace = device.data.workspaces[msgAddr[2]]; + // const cue = workspace.cues[oscData.args[0]]; + workspace.playbackPosition = oscData.args[0]; + // device.draw(); + device.update('updatePlaybackAndSelected', { workspace: device.data.workspaces[msgAddr[2]] }); + } else if (/update\/workspace\/.*\/dashboard/.test(oscData.address)) { + device.send(`/workspace/${msgAddr[2]}/selectedCues`); + device.send(`/cue_id/active/preWaitElapsed`); } }; +function processCueList(list, knownCueIDs, _device, _nestedIndex) { + const nestedIndex = _nestedIndex; + const device = _device; + + for (let i = 0; i < list.length; i++) { + const cue = list[i]; + nestedIndex[nestedIndex.length - 1] = list.length - i - 1; + + // if (!knownCueIDs.includes(cue.uniqueID)) { + device.send(`/cue_id/${cue.uniqueID}/valuesForKeys`, [{ type: 's', value: valuesForKeysString }]); + // } + + if (cue.cues.length > 0) { + const nest2 = [..._nestedIndex]; + nest2.push(cue.cues.length); + processCueList(cue.cues, knownCueIDs, device, nest2); + for (let j = 0; j < nestedIndex.length; j++) { + nestedIndex[j]++; + } + } + + if (!knownCueIDs.includes(cue.uniqueID)) { + device.data.cueKeys[cue.uniqueID] = { + nestedGroupPosition: [...nestedIndex], + }; + } else { + device.data.cueKeys[cue.uniqueID].nestedGroupPosition = [...nestedIndex]; + } + + device.data.cueKeys[cue.uniqueID].cueInWorkspace = list[i]; + } +} + exports.update = function update(device, doc, updateType, data) { - if (updateType === 'updateCueData') { + if (updateType === 'updateCueRow') { const $elem = doc.getElementById(data.cue.uniqueID); - - if ($elem) { - if (data.cue.type === 'Cue List') { - $elem.outerHTML = `

${data.workspace.displayName} — ${data.cue.name}

`; - } else if (data.cue.type === 'Cart') { - $elem.outerHTML = device.templates.cart({ - tileTemplate: device.templates.tile, - cueList: data.cue, - allCues: data.workspace.cues, - }); - } else if (data.cue.cartPosition && data.cue.cartPosition[0] !== 0) { - // checking that the parent cue is a cart cue - const parentCue = data.workspace.cues[data.cue.parent]; - if (parentCue && parentCue.type === 'Cart') { - $elem.outerHTML = device.templates.tile(data); - } else { - $elem.outerHTML = device.templates.cue(data); - } - } else { - $elem.outerHTML = device.templates.cue(data); - } - } - } else if (updateType === 'updatePlaybackPosition') { - Array.from(doc.getElementsByClassName('playback-position')).forEach(($elem, index, array) => { - $elem.classList.remove('playback-position'); + $elem.outerHTML = device.templates.cue({ allCues: device.data.cueKeys, cue: data.cue, workspace: data.workspace }); + } else if (updateType === 'updatePlaybackAndSelected') { + Array.from(doc.querySelectorAll('.selected')).forEach(($el) => { + $el.classList.remove('selected'); }); - - const $elem = doc.getElementById(data.cue.uniqueID); + Array.from(doc.querySelectorAll('.playback-position')).forEach(($el) => { + $el.classList.remove('playback-position'); + }); + for (let i = 0; i < data.workspace.selected.length; i++) { + const $elem = doc.getElementById(data.workspace.selected[i]); + $elem.classList.add('selected'); + } + const $elem = doc.getElementById(data.workspace.playbackPosition); $elem.classList.add('playback-position'); - $elem.scrollIntoView({ behavior: 'smooth', block: 'center' }); } }; -function addCueToWorkspace(_workspace, cue) { - const workspace = _workspace; - workspace.cues[cue.uniqueID] = cue; - workspace.cues[cue.uniqueID].nestedGroupModes = []; - workspace.cues[cue.uniqueID].nestedGroupPosition = []; - - if (cue.cues) { - // this cue has children so add them as well - cue.cues.forEach((childCue) => { - addCueToWorkspace(workspace, childCue); - }); - } -} - -function getValuesForKeys(device, workspaceID, cue) { - device.send(`/workspace/${workspaceID}/cue_id/${cue.uniqueID}/valuesForKeys`, [ - { type: 's', value: valuesForKeysString }, - ]); - if (cue.cues) { - cue.cues.forEach((childCue) => { - getValuesForKeys(device, workspaceID, childCue); - }); - } -} - -function match(testArray, patternArray) { - let out = true; - if (testArray.length !== patternArray.length) { - return false; - } - patternArray.forEach((patternPart, i) => { - if (testArray[i] !== patternPart && patternPart !== '*') { - out = false; - } - }); - return out; -} - +let ticks = 0; exports.heartbeat = function heartbeat(device) { - heartbeatCount++; - - if (Date.now() - lastElapsedUpdate > 300) { - interval = 24; - } else { - interval = 1; - } - - if (heartbeatCount % 20 === 0 && device.data.workspaces && Object.keys(device.data.workspaces).length === 0) { - device.send(`/version`); - device.send('/workspaces'); - } - - if (heartbeatCount % 16 === 0) { + ticks++; + if (ticks % 10 === 0) { device.send(`/thump`); } - - if (heartbeatCount % interval === 0 && device.data.workspaces && Object.keys(device.data.workspaces).length > 0) { + if (device.data.somethingPlaying) { device.send(`/cue_id/active/preWaitElapsed`); - device.send(`/cue_id/active/actionElapsed`); device.send(`/cue_id/active/postWaitElapsed`); + device.send(`/cue_id/active/actionElapsed`); } }; diff --git a/plugins/qlab/template.ejs b/plugins/qlab/template.ejs index e55bd0b..b50e6a2 100644 --- a/plugins/qlab/template.ejs +++ b/plugins/qlab/template.ejs @@ -3,26 +3,22 @@

QLab <%= data.version || "" %>

+<% const workspaceKeys = Object.keys(data.workspaces); %> - <% if(data.permission=="ok"){ - for(const workspace_id in data.workspaces){ - const workspace = data.workspaces[workspace_id]; - for(const cueList_id in workspace.cueLists){ - const ql = workspace.cueLists[cueList_id]; %> - - <% if(ql.type=="Cue List"){ %> - <%= templates.cuelist({cueList: ql, allCues: workspace.cues, rowTemplate: templates.cue, workspace: workspace}) %> - <% }else if(ql.type=="Cart"){ %> - <%= templates.cart({cueList: ql, allCues: workspace.cues, tileTemplate: templates.tile}) %> - <% } %> +<% for(let i=0; i + <% const workspace = data.workspaces[workspaceKeys[i]]; %> -<% }}}else if(data.permission=="no workspaces"){ %> +

<%= workspace.displayName %>

-

QLab is launched but there are no open Workspaces

+ <% for(let j=0; j + <% const cueList = workspace.cueLists[j]; %> -<% }else if(data.permission=="denied"){ %> + <% if(cueList.type=="Cue List"){ %> + <%= templates.cuelist({cueList: cueList, allCues: data.cueKeys, rowTemplate: templates.cue, workspace: workspace}) %> + <% }else if(cueList.type=="Cart"){ %> + <%= templates.cart({cueList: cueList, allCues: data.cueKeys, tileTemplate: templates.tile}) %> + <% } %> -

Incorrect Passcode or OSC Access Permissions

- + <% } %> -<% } %> +<% } %> \ No newline at end of file From 4cf68d3a07677ab0f614c258d3afb7e9ac031757 Mon Sep 17 00:00:00 2001 From: sparks-alec Date: Sun, 28 May 2023 07:14:44 -0400 Subject: [PATCH 03/21] bugfix error when no playbackPos --- plugins/qlab/main.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/qlab/main.js b/plugins/qlab/main.js index 8844740..9aefe69 100644 --- a/plugins/qlab/main.js +++ b/plugins/qlab/main.js @@ -236,8 +236,10 @@ exports.update = function update(device, doc, updateType, data) { const $elem = doc.getElementById(data.workspace.selected[i]); $elem.classList.add('selected'); } - const $elem = doc.getElementById(data.workspace.playbackPosition); - $elem.classList.add('playback-position'); + if (data.workspace.playbackPosition) { + const $elem = doc.getElementById(data.workspace.playbackPosition); + $elem.classList.add('playback-position'); + } } }; From d7508c1e225635e7dabf97b460885fbbe1617341 Mon Sep 17 00:00:00 2001 From: sparks-alec Date: Mon, 29 May 2023 01:13:43 -0400 Subject: [PATCH 04/21] add source img --- plugins/qlab/img/playhead.afdesign | Bin 22559 -> 22592 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/plugins/qlab/img/playhead.afdesign b/plugins/qlab/img/playhead.afdesign index 2fe34924cf8aba554884f46e7587675a15413414..d1e74145d797369332e9c0dacb7db2f8eda930ba 100644 GIT binary patch literal 22592 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 y38b7_nA5DLWuX`T+V{x12M#J2?#}~%{ From 0364924f2404eeeefd569f0186fe61fe7928801b Mon Sep 17 00:00:00 2001 From: sparks-alec Date: Mon, 29 May 2023 01:14:32 -0400 Subject: [PATCH 05/21] add carts! --- plugins/qlab/cart.ejs | 28 ++++---- plugins/qlab/main.js | 145 +++++++++++++++++++++++--------------- plugins/qlab/template.ejs | 4 +- plugins/qlab/tile.ejs | 22 +++--- 4 files changed, 115 insertions(+), 84 deletions(-) diff --git a/plugins/qlab/cart.ejs b/plugins/qlab/cart.ejs index 6b76b4f..05e7f5f 100644 --- a/plugins/qlab/cart.ejs +++ b/plugins/qlab/cart.ejs @@ -1,15 +1,17 @@ -
+<% let cueKeys = allCues[cueList.uniqueID]; %> -

<%= cueList.displayName %>

+
+ +

<%= workspace.displayName %> — <%= cueList.listName %>

<% - let width = 100/cueList.cartColumns; - let height = 600/(cueList.cartRows); + let width = 100/cueKeys.cartColumns; + let height = 600/(cueKeys.cartRows); %>
-<% for(let r=0; r - <% for(let c=0; c +<% for(let r=0; r + <% for(let c=0; c <% let style = `style="left:${width*c}%; top:${height*r}px; width:${width }%; height:${height}px;"`; %>
>
@@ -18,25 +20,27 @@ <% } %> -<% for(var c in cueList.cues){ - let cue = cueList.cues[c]; +<% for(let i=0; i=cueList.cartColumns || row>=cueList.cartRows){ + if(col>=cueKeys.cartColumns || row>=cueKeys.cartRows){ continue; - } + } + %> - -<%= tileTemplate({cue: allCues[cue.uniqueID], allCues: allCues}) %> + +<%= tileTemplate({cue: cueList.cues[i], allCues: allCues, workspace: workspace}) %> <% } %> diff --git a/plugins/qlab/main.js b/plugins/qlab/main.js index 9aefe69..a2a23b3 100644 --- a/plugins/qlab/main.js +++ b/plugins/qlab/main.js @@ -88,71 +88,78 @@ exports.data = function data(_device, oscData) { } else if (/reply\/cue_id\/.*\/valuesForKeys/.test(oscData.address)) { const json = JSON.parse(oscData.args[0]); const keyValues = json.data; - const cue = device.data.cueKeys[msgAddr[2]]; + let cue = device.data.cueKeys[msgAddr[2]]; if (cue === undefined) { // this cue is new! - console.log('HERE'); + // device.send(`/workspace/${json.workspace_id}/cueLists`); - // device.draw(); - } else { - // console.log(keyValues.number); - cue.uniqueID = keyValues.uniqueID; - cue.number = keyValues.number; - cue.listName = keyValues.listName; - cue.isBroken = keyValues.isBroken; - cue.isRunning = keyValues.isRunning; - cue.isLoaded = keyValues.isLoaded; - cue.isFlagged = keyValues.isFlagged; - cue.type = keyValues.type; - cue.cues = keyValues.children; - cue.preWait = keyValues.preWait; - cue.postWait = keyValues.postWait; - cue.duration = keyValues.duration; - cue.colorName = keyValues.colorName; - cue.continueMode = keyValues.continueMode; - cue.mode = keyValues.mode; - cue.parent = keyValues.parent; - cue.cartRows = keyValues.cartRows; - cue.cartColumns = keyValues.cartColumns; - cue.cartPosition = keyValues.cartPosition; - cue.displayName = keyValues.displayName; - cue.preWaitElapsed = keyValues.preWaitElapsed; - cue.actionElapsed = keyValues.actionElapsed; - cue.postWaitElapsed = keyValues.postWaitElapsed; - cue.isPaused = keyValues.isPaused; - cue.currentCueTarget = keyValues.currentCueTarget; - cue.armed = keyValues.armed; + device.draw(); + device.data.cueKeys[msgAddr[2]] = {}; + cue = device.data.cueKeys[msgAddr[2]]; + } + // console.log(keyValues.number); + cue.uniqueID = keyValues.uniqueID; + cue.number = keyValues.number; + cue.listName = keyValues.listName; + cue.isBroken = keyValues.isBroken; + cue.isRunning = keyValues.isRunning; + cue.isLoaded = keyValues.isLoaded; + cue.isFlagged = keyValues.isFlagged; + cue.type = keyValues.type; + cue.cues = keyValues.children; + cue.preWait = keyValues.preWait; + cue.postWait = keyValues.postWait; + cue.duration = keyValues.duration; + cue.colorName = keyValues.colorName; + cue.continueMode = keyValues.continueMode; + cue.mode = keyValues.mode; + cue.parent = keyValues.parent; + cue.cartRows = keyValues.cartRows; + cue.cartColumns = keyValues.cartColumns; + cue.cartPosition = keyValues.cartPosition; + cue.displayName = keyValues.displayName; + cue.preWaitElapsed = keyValues.preWaitElapsed; + cue.actionElapsed = keyValues.actionElapsed; + cue.postWaitElapsed = keyValues.postWaitElapsed; + cue.isPaused = keyValues.isPaused; + cue.currentCueTarget = keyValues.currentCueTarget; + cue.armed = keyValues.armed; - if (keyValues.isRunning) { - device.data.somethingPlaying = true; + if (keyValues.isRunning) { + device.data.somethingPlaying = true; + } + + let testCue = device.data.cueKeys[msgAddr[2]]; + let nestedGroupModes; + + if (cue.type === 'Group') { + nestedGroupModes = [testCue.mode]; + } else if (cue.parent !== '[root group of cue lists]') { + nestedGroupModes = [device.data.cueKeys[cue.parent].mode]; + } + + while (testCue.parent !== '[root group of cue lists]') { + testCue = device.data.cueKeys[testCue.parent]; + if (testCue === undefined) { + break; } - let testCue = device.data.cueKeys[msgAddr[2]]; - let nestedGroupModes; - - if (cue.type === 'Group') { - nestedGroupModes = [testCue.mode]; - } else if (cue.parent !== '[root group of cue lists]') { - nestedGroupModes = [device.data.cueKeys[cue.parent].mode]; - } - - while (testCue.parent !== '[root group of cue lists]') { - testCue = device.data.cueKeys[testCue.parent]; - if (testCue === undefined) { - break; - } - - nestedGroupModes.unshift(testCue.mode); - cue.nestedGroupModes = nestedGroupModes; - } - if (cue.type === 'Cue List' || cue.type === 'Group') { + nestedGroupModes.unshift(testCue.mode); + cue.nestedGroupModes = nestedGroupModes; + } + if (cue.type === 'Cue List' || cue.type === 'Group' || cue.type === 'Cart') { + if (device.data.cueKeys[msgAddr[2]].cueInWorkspace) { device.data.cueKeys[msgAddr[2]].cueInWorkspace.cues = [...keyValues.children]; } - if (cue.type !== 'Cue List') { - device.update('updateCueRow', { cue, workspace: device.data.workspaces[json.workspace_id] }); - } } + if (cue.type !== 'Cue List' && cue.type !== 'Cart') { + device.update('updateCueRow', { cue, workspace: device.data.workspaces[json.workspace_id] }); + } + if (cue.type === 'Cart') { + device.draw(); + } + // console.log(cue); } else if (/reply\/cue_id\/active\/.*Elapsed/.test(oscData.address)) { const json = JSON.parse(oscData.args[0]); @@ -173,6 +180,10 @@ exports.data = function data(_device, oscData) { } else if (/update\/workspace\/.*\/cue_id\/.*/.test(oscData.address)) { if (device.data.cueKeys[msgAddr[4]] && device.data.cueKeys[msgAddr[4]].type === 'Group') { device.send(`/workspace/${msgAddr[2]}/cueLists`); + } else if (device.data.cueKeys[msgAddr[4]] && device.data.cueKeys[msgAddr[4]].type === 'Cue List') { + device.send(`/workspace/${msgAddr[2]}/cueLists`); + } else if (device.data.cueKeys[msgAddr[4]] && device.data.cueKeys[msgAddr[4]].type === 'Cart') { + device.send(`/workspace/${msgAddr[2]}/cueLists`); } else { device.send(`/cue_id/${msgAddr[4]}/valuesForKeys`, [{ type: 's', value: valuesForKeysString }]); } @@ -224,7 +235,21 @@ function processCueList(list, knownCueIDs, _device, _nestedIndex) { exports.update = function update(device, doc, updateType, data) { if (updateType === 'updateCueRow') { const $elem = doc.getElementById(data.cue.uniqueID); - $elem.outerHTML = device.templates.cue({ allCues: device.data.cueKeys, cue: data.cue, workspace: data.workspace }); + if ($elem) { + if (device.data.cueKeys[data.cue.parent].type === 'Cart') { + $elem.outerHTML = device.templates.tile({ + allCues: device.data.cueKeys, + cue: data.cue, + workspace: data.workspace, + }); + } else { + $elem.outerHTML = device.templates.cue({ + allCues: device.data.cueKeys, + cue: data.cue, + workspace: data.workspace, + }); + } + } } else if (updateType === 'updatePlaybackAndSelected') { Array.from(doc.querySelectorAll('.selected')).forEach(($el) => { $el.classList.remove('selected'); @@ -234,11 +259,15 @@ exports.update = function update(device, doc, updateType, data) { }); for (let i = 0; i < data.workspace.selected.length; i++) { const $elem = doc.getElementById(data.workspace.selected[i]); - $elem.classList.add('selected'); + if ($elem) { + $elem.classList.add('selected'); + } } if (data.workspace.playbackPosition) { const $elem = doc.getElementById(data.workspace.playbackPosition); - $elem.classList.add('playback-position'); + if ($elem) { + $elem.classList.add('playback-position'); + } } } }; diff --git a/plugins/qlab/template.ejs b/plugins/qlab/template.ejs index b50e6a2..710d4c7 100644 --- a/plugins/qlab/template.ejs +++ b/plugins/qlab/template.ejs @@ -8,15 +8,13 @@ <% for(let i=0; i <% const workspace = data.workspaces[workspaceKeys[i]]; %> -

<%= workspace.displayName %>

- <% for(let j=0; j <% const cueList = workspace.cueLists[j]; %> <% if(cueList.type=="Cue List"){ %> <%= templates.cuelist({cueList: cueList, allCues: data.cueKeys, rowTemplate: templates.cue, workspace: workspace}) %> <% }else if(cueList.type=="Cart"){ %> - <%= templates.cart({cueList: cueList, allCues: data.cueKeys, tileTemplate: templates.tile}) %> + <%= templates.cart({cueList: cueList, allCues: data.cueKeys, tileTemplate: templates.tile, workspace: workspace}) %> <% } %> <% } %> diff --git a/plugins/qlab/tile.ejs b/plugins/qlab/tile.ejs index ca8e9e2..cb796e1 100644 --- a/plugins/qlab/tile.ejs +++ b/plugins/qlab/tile.ejs @@ -1,17 +1,17 @@ <% - let height = 0; let width = 0; let left = 0; let top = 0; + let cueKeys = allCues[cue.uniqueID]; + let parent = allCues[cue.parent]; + if(cueKeys.parent){ + width = 100/allCues[cueKeys.parent].cartColumns; + height = 600/allCues[cueKeys.parent].cartRows; - if(cue.parent){ - width = 100/allCues[cue.parent].cartColumns; - height = 600/allCues[cue.parent].cartRows; - - top = (cue.cartPosition[0]-1)*height; - left = (cue.cartPosition[1]-1)*width; + top = (cueKeys.cartPosition[0]-1)*height; + left = (cueKeys.cartPosition[1]-1)*width; } let style = `style="left:${left}%; top:${top}px; width:${width }%; height:${height}px;"`; @@ -21,17 +21,17 @@
>
- <% if(cue.broken){ %> + <% if(cueKeys.isBroken){ %> - <% }else if(cue.running){ %> + <% }else if(cueKeys.isRunning){ %> <% }else{ %> <% } %>
-
-

<%= cue.number %> • <%= cue.displayName %>

+
+

<%= cueKeys.number %> • <%= cueKeys.displayName %>

\ No newline at end of file From a237841a3a26596fb5e94ee8f20e742f7d109ffd Mon Sep 17 00:00:00 2001 From: sparks-alec Date: Mon, 29 May 2023 01:14:44 -0400 Subject: [PATCH 06/21] update cart styles for QLab 5 --- plugins/qlab/styles.css | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/plugins/qlab/styles.css b/plugins/qlab/styles.css index d7fc750..e4f1da3 100644 --- a/plugins/qlab/styles.css +++ b/plugins/qlab/styles.css @@ -204,6 +204,10 @@ tr.selected { box-sizing: border-box; padding: 3px; } +.cartCueWrapper.selected .cartCue { + border-color: #89b4db; + box-shadow: #89b4db 0px 0px 2px 3px; +} .cartCue { height: 100%; box-sizing: border-box; @@ -221,24 +225,24 @@ tr.selected { top: 13px; } .cartColor-red { - border-color: #ff4242; - background-color: #9b3726; + border-color: #e7443a; + background-color: #942f27; } .cartColor-orange { - border-color: #ffa500; - background-color: #ad6026; + border-color: #f18f28; + background-color: #a95023; } .cartColor-green { - border-color: #01d52f; - background-color: #397f27; + border-color: #4ebf32; + background-color: #397824; } .cartColor-blue { - border-color: #536de0; - background-color: #304893; + border-color: #3a54cf; + background-color: #25378a; } .cartColor-purple { - border-color: #a601c0; - background-color: #592d74; + border-color: #7e25a5; + background-color: #4c2269; } .cartColor-none { border-color: #95929f; From 77549aff562a339657baa754e966e11e510d94ee Mon Sep 17 00:00:00 2001 From: sparks-alec Date: Mon, 29 May 2023 03:38:25 -0400 Subject: [PATCH 07/21] improve resiliency when passcode is incorrect --- plugins/qlab/main.js | 46 ++++++++++++++++++++++++++++++--------- plugins/qlab/template.ejs | 26 ++++++++++++++++------ 2 files changed, 55 insertions(+), 17 deletions(-) diff --git a/plugins/qlab/main.js b/plugins/qlab/main.js index a2a23b3..dbee300 100644 --- a/plugins/qlab/main.js +++ b/plugins/qlab/main.js @@ -50,11 +50,30 @@ exports.ready = function ready(_device) { exports.data = function data(_device, oscData) { const device = _device; - // console.log(oscData); + console.log(oscData); const msgAddr = oscData.address.split('/'); msgAddr.shift(); + // try { + let json = []; + try { + json = JSON.parse(oscData.args[0]); + } catch (err) {} + + if (Object.keys(json).length > 0) { + if (json.status === 'denied') { + if (device.data.workspaces[json.workspace_id]) { + device.data.workspaces[json.workspace_id].permission = 'denied'; + } + return; + } + if (json.status === 'error') { + device.send('/workspaces'); + return; + } + } + if (oscData.address === '/reply/workspaces') { const json = JSON.parse(oscData.args[0]).data; @@ -67,15 +86,17 @@ exports.data = function data(_device, oscData) { cueLists: [], selected: [], }; + device.data.workspaces[json[i].uniqueID].permission = 'ok'; device.send(`/workspace/${json[i].uniqueID}/connect`, device.fields.passcode); } this.deviceInfoUpdate(device, 'status', 'ok'); } else if (/reply\/workspace\/.*\/connect/.test(oscData.address)) { - const json = JSON.parse(oscData.args[0]).data; - if (json === 'badpass') { - device.data.permission = 'denied'; + const json = JSON.parse(oscData.args[0]); + + if (json.data === 'badpass') { + device.data.workspaces[json.workspace_id].permission = 'badpass'; } else { - device.data.permission = 'ok'; + device.data.workspaces[json.workspace_id].permission = 'ok'; device.send(`/workspace/${msgAddr[2]}/cueLists`); device.send(`/workspace/${msgAddr[2]}/updates`, [{ type: 'i', value: 1 }]); } @@ -98,7 +119,7 @@ exports.data = function data(_device, oscData) { device.data.cueKeys[msgAddr[2]] = {}; cue = device.data.cueKeys[msgAddr[2]]; } - // console.log(keyValues.number); + // console.log(json.status); cue.uniqueID = keyValues.uniqueID; cue.number = keyValues.number; cue.listName = keyValues.listName; @@ -172,6 +193,9 @@ exports.data = function data(_device, oscData) { } else if (/reply\/workspace\/.*\/selectedCues/.test(oscData.address)) { const json = JSON.parse(oscData.args[0]); const workspace = device.data.workspaces[msgAddr[2]]; + if (!workspace) { + return; + } workspace.selected = []; for (let i = 0; i < json.data.length; i++) { workspace.selected.push(json.data[i].uniqueID); @@ -189,10 +213,12 @@ exports.data = function data(_device, oscData) { } } else if (/update\/workspace\/.*\/cueList\/.*\/playbackPosition/.test(oscData.address)) { const workspace = device.data.workspaces[msgAddr[2]]; - // const cue = workspace.cues[oscData.args[0]]; - workspace.playbackPosition = oscData.args[0]; - // device.draw(); - device.update('updatePlaybackAndSelected', { workspace: device.data.workspaces[msgAddr[2]] }); + if (workspace) { + // const cue = workspace.cues[oscData.args[0]]; + workspace.playbackPosition = oscData.args[0]; + // device.draw(); + device.update('updatePlaybackAndSelected', { workspace: device.data.workspaces[msgAddr[2]] }); + } } else if (/update\/workspace\/.*\/dashboard/.test(oscData.address)) { device.send(`/workspace/${msgAddr[2]}/selectedCues`); device.send(`/cue_id/active/preWaitElapsed`); diff --git a/plugins/qlab/template.ejs b/plugins/qlab/template.ejs index 710d4c7..47ba696 100644 --- a/plugins/qlab/template.ejs +++ b/plugins/qlab/template.ejs @@ -3,20 +3,32 @@

QLab <%= data.version || "" %>

+ + <% const workspaceKeys = Object.keys(data.workspaces); %> <% for(let i=0; i <% const workspace = data.workspaces[workspaceKeys[i]]; %> - <% for(let j=0; j - <% const cueList = workspace.cueLists[j]; %> + <% if(workspace.permission=="ok"){ %> + + <% for(let j=0; j + <% const cueList = workspace.cueLists[j]; %> + + <% if(cueList.type=="Cue List"){ %> + <%= templates.cuelist({cueList: cueList, allCues: data.cueKeys, rowTemplate: templates.cue, workspace: workspace}) %> + <% }else if(cueList.type=="Cart"){ %> + <%= templates.cart({cueList: cueList, allCues: data.cueKeys, tileTemplate: templates.tile, workspace: workspace}) %> + <% } %> - <% if(cueList.type=="Cue List"){ %> - <%= templates.cuelist({cueList: cueList, allCues: data.cueKeys, rowTemplate: templates.cue, workspace: workspace}) %> - <% }else if(cueList.type=="Cart"){ %> - <%= templates.cart({cueList: cueList, allCues: data.cueKeys, tileTemplate: templates.tile, workspace: workspace}) %> <% } %> + + <% }else{ %> +

<%= workspace.displayName %> — Incorrect Passcode or OSC Access Permissions

+ +

 

<% } %> -<% } %> \ No newline at end of file +<% } %> + From bd9894c5960732e37a9640786e56134d84d4d22d Mon Sep 17 00:00:00 2001 From: sparks-alec Date: Mon, 29 May 2023 03:56:45 -0400 Subject: [PATCH 08/21] prevent error when many cues are made in QLab --- plugins/qlab/cue.ejs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/qlab/cue.ejs b/plugins/qlab/cue.ejs index d6f78cf..b7809f6 100644 --- a/plugins/qlab/cue.ejs +++ b/plugins/qlab/cue.ejs @@ -2,7 +2,7 @@ <% let playbackPosition = workspace.playbackPosition==cue.uniqueID ? "playback-position" : "" %> <% let selected = workspace.selected.includes(cue.uniqueID) ? "selected": "" %> -<% if(cueKeys && cueKeys.parent){ %> +<% if(cueKeys && cueKeys.parent && cueKeys.nestedGroupPosition){ %>
@@ -48,7 +48,7 @@ - + <% for(let i=0; i From 76d8f5e736534a8d35140a2332325b271b491226 Mon Sep 17 00:00:00 2001 From: sparks-alec Date: Mon, 29 May 2023 03:57:04 -0400 Subject: [PATCH 09/21] tidy up --- plugins/qlab/main.js | 42 +++++++++++++----------------------------- 1 file changed, 13 insertions(+), 29 deletions(-) diff --git a/plugins/qlab/main.js b/plugins/qlab/main.js index dbee300..8c97634 100644 --- a/plugins/qlab/main.js +++ b/plugins/qlab/main.js @@ -50,16 +50,16 @@ exports.ready = function ready(_device) { exports.data = function data(_device, oscData) { const device = _device; - console.log(oscData); const msgAddr = oscData.address.split('/'); msgAddr.shift(); - // try { let json = []; try { json = JSON.parse(oscData.args[0]); - } catch (err) {} + } catch (err) { + // a handful of messages don't respond with JSON + } if (Object.keys(json).length > 0) { if (json.status === 'denied') { @@ -75,24 +75,20 @@ exports.data = function data(_device, oscData) { } if (oscData.address === '/reply/workspaces') { - const json = JSON.parse(oscData.args[0]).data; - - for (let i = 0; i < json.length; i++) { - device.data.workspaces[json[i].uniqueID] = { - version: json[i].version, - displayName: json[i].displayName, - port: json[i].port, - udpReplyPort: json[i].udpReplyPort, + for (let i = 0; i < json.data.length; i++) { + device.data.workspaces[json.data[i].uniqueID] = { + version: json.data[i].version, + displayName: json.data[i].displayName, + port: json.data[i].port, + udpReplyPort: json.data[i].udpReplyPort, cueLists: [], selected: [], }; - device.data.workspaces[json[i].uniqueID].permission = 'ok'; - device.send(`/workspace/${json[i].uniqueID}/connect`, device.fields.passcode); + device.data.workspaces[json.data[i].uniqueID].permission = 'ok'; + device.send(`/workspace/${json.data[i].uniqueID}/connect`, device.fields.passcode); } this.deviceInfoUpdate(device, 'status', 'ok'); } else if (/reply\/workspace\/.*\/connect/.test(oscData.address)) { - const json = JSON.parse(oscData.args[0]); - if (json.data === 'badpass') { device.data.workspaces[json.workspace_id].permission = 'badpass'; } else { @@ -101,25 +97,18 @@ exports.data = function data(_device, oscData) { device.send(`/workspace/${msgAddr[2]}/updates`, [{ type: 'i', value: 1 }]); } } else if (/reply\/workspace\/.*\/cueLists/.test(oscData.address)) { - const json = JSON.parse(oscData.args[0]).data; - device.data.workspaces[msgAddr[2]].cueLists = json; + device.data.workspaces[msgAddr[2]].cueLists = json.data; device.data.somethingPlaying = false; processCueList(device.data.workspaces[msgAddr[2]].cueLists, Object.keys(device.data.cueKeys), device, []); device.draw(); } else if (/reply\/cue_id\/.*\/valuesForKeys/.test(oscData.address)) { - const json = JSON.parse(oscData.args[0]); const keyValues = json.data; let cue = device.data.cueKeys[msgAddr[2]]; if (cue === undefined) { - // this cue is new! - - // device.send(`/workspace/${json.workspace_id}/cueLists`); - device.draw(); device.data.cueKeys[msgAddr[2]] = {}; cue = device.data.cueKeys[msgAddr[2]]; } - // console.log(json.status); cue.uniqueID = keyValues.uniqueID; cue.number = keyValues.number; cue.listName = keyValues.listName; @@ -180,10 +169,7 @@ exports.data = function data(_device, oscData) { if (cue.type === 'Cart') { device.draw(); } - - // console.log(cue); } else if (/reply\/cue_id\/active\/.*Elapsed/.test(oscData.address)) { - const json = JSON.parse(oscData.args[0]); const addrParts = json.address.split('/'); const cueID = addrParts[4]; const keyName = addrParts[5]; @@ -191,7 +177,6 @@ exports.data = function data(_device, oscData) { device.data.somethingPlaying = true; device.draw(); } else if (/reply\/workspace\/.*\/selectedCues/.test(oscData.address)) { - const json = JSON.parse(oscData.args[0]); const workspace = device.data.workspaces[msgAddr[2]]; if (!workspace) { return; @@ -214,9 +199,7 @@ exports.data = function data(_device, oscData) { } else if (/update\/workspace\/.*\/cueList\/.*\/playbackPosition/.test(oscData.address)) { const workspace = device.data.workspaces[msgAddr[2]]; if (workspace) { - // const cue = workspace.cues[oscData.args[0]]; workspace.playbackPosition = oscData.args[0]; - // device.draw(); device.update('updatePlaybackAndSelected', { workspace: device.data.workspaces[msgAddr[2]] }); } } else if (/update\/workspace\/.*\/dashboard/.test(oscData.address)) { @@ -293,6 +276,7 @@ exports.update = function update(device, doc, updateType, data) { const $elem = doc.getElementById(data.workspace.playbackPosition); if ($elem) { $elem.classList.add('playback-position'); + $elem.scrollIntoView({ behavior: 'smooth', block: 'center' }); } } } From 55ede73f22abd3dca15676aabfc234b7672e893d Mon Sep 17 00:00:00 2001 From: sparks-alec Date: Mon, 29 May 2023 04:07:17 -0400 Subject: [PATCH 10/21] remove bullet if tile doesn't have a cue number --- plugins/qlab/tile.ejs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/qlab/tile.ejs b/plugins/qlab/tile.ejs index cb796e1..82134c7 100644 --- a/plugins/qlab/tile.ejs +++ b/plugins/qlab/tile.ejs @@ -31,7 +31,11 @@
-

<%= cueKeys.number %> • <%= cueKeys.displayName %>

+

+ <%= cueKeys.number %> + <% if(cueKeys.number){ %> • <% } %> + <%= cueKeys.displayName %> +

\ No newline at end of file From 454e794d4c9e99947e64b53bc4eb7c0219777b4a Mon Sep 17 00:00:00 2001 From: sparks-alec Date: Mon, 29 May 2023 05:11:58 -0400 Subject: [PATCH 11/21] add playhead information overlay --- plugins/qlab/main.js | 46 ++++++++++++++++++++++++++---- plugins/qlab/styles.css | 59 +++++++++++++++++++++++++++++++++++---- plugins/qlab/template.ejs | 5 ++++ 3 files changed, 98 insertions(+), 12 deletions(-) diff --git a/plugins/qlab/main.js b/plugins/qlab/main.js index 8c97634..c34df6f 100644 --- a/plugins/qlab/main.js +++ b/plugins/qlab/main.js @@ -30,7 +30,7 @@ const valuesForKeysString = '["uniqueID","number","name","listName","isBroken","isRunning","isLoaded","isFlagged",' + '"type","children","preWait","postWait","duration","colorName","continueMode",' + '"mode","parent","cartRows","cartColumns","cartPosition","displayName","preWaitElapsed",' + - '"actionElapsed","postWaitElapsed","isPaused","currentCueTarget","isRunning","armed"]'; + '"actionElapsed","postWaitElapsed","isPaused","currentCueTarget","isRunning","armed","notes"]'; exports.ready = function ready(_device) { const device = _device; @@ -135,6 +135,7 @@ exports.data = function data(_device, oscData) { cue.isPaused = keyValues.isPaused; cue.currentCueTarget = keyValues.currentCueTarget; cue.armed = keyValues.armed; + cue.notes = keyValues.notes; if (keyValues.isRunning) { device.data.somethingPlaying = true; @@ -241,7 +242,8 @@ function processCueList(list, knownCueIDs, _device, _nestedIndex) { } } -exports.update = function update(device, doc, updateType, data) { +exports.update = function update(device, _doc, updateType, data) { + const doc = _doc; if (updateType === 'updateCueRow') { const $elem = doc.getElementById(data.cue.uniqueID); if ($elem) { @@ -266,18 +268,50 @@ exports.update = function update(device, doc, updateType, data) { Array.from(doc.querySelectorAll('.playback-position')).forEach(($el) => { $el.classList.remove('playback-position'); }); + for (let i = 0; i < data.workspace.selected.length; i++) { const $elem = doc.getElementById(data.workspace.selected[i]); if ($elem) { $elem.classList.add('selected'); } } + + const $playheadInfo = doc.getElementById('playhead-information'); + const $playheadName = doc.getElementById('playhead-name'); + const $playheadNotes = doc.getElementById('playhead-notes'); + if (data.workspace.playbackPosition) { - const $elem = doc.getElementById(data.workspace.playbackPosition); - if ($elem) { - $elem.classList.add('playback-position'); - $elem.scrollIntoView({ behavior: 'smooth', block: 'center' }); + const $cueRow = doc.getElementById(data.workspace.playbackPosition); + if ($cueRow) { + $cueRow.classList.add('playback-position'); + $cueRow.scrollIntoView({ behavior: 'smooth', block: 'center' }); } + + const cue = device.data.cueKeys[data.workspace.playbackPosition]; + if (cue) { + $playheadName.setAttribute('class', `playhead-name cartColor-${cue.colorName} playhead-disarmed`); + $playheadInfo.classList.add('playhead-active'); + if (cue.number) { + $playheadName.innerHTML = `${cue.number} • ${cue.displayName}`; + } else { + $playheadName.innerHTML = cue.displayName; + } + if (cue.notes) { + $playheadNotes.innerHTML = cue.notes; + } else { + $playheadNotes.innerHTML = 'Notes'; + } + if (!cue.armed) { + $playheadName.classList.add('playhead-disarmed'); + } else { + $playheadName.classList.remove('playhead-disarmed'); + } + } + } else { + $playheadName.setAttribute('class', `playhead-name`); + $playheadName.innerHTML = '[no cue on standby]'; + $playheadNotes.innerHTML = ''; + $playheadInfo.classList.remove('playhead-active'); } } }; diff --git a/plugins/qlab/styles.css b/plugins/qlab/styles.css index e4f1da3..fca0407 100644 --- a/plugins/qlab/styles.css +++ b/plugins/qlab/styles.css @@ -224,27 +224,27 @@ tr.selected { right: 13px; top: 13px; } -.cartColor-red { +div.cartColor-red { border-color: #e7443a; background-color: #942f27; } -.cartColor-orange { +div.cartColor-orange { border-color: #f18f28; background-color: #a95023; } -.cartColor-green { +div.cartColor-green { border-color: #4ebf32; background-color: #397824; } -.cartColor-blue { +div.cartColor-blue { border-color: #3a54cf; background-color: #25378a; } -.cartColor-purple { +div.cartColor-purple { border-color: #7e25a5; background-color: #4c2269; } -.cartColor-none { +div.cartColor-none { border-color: #95929f; background-color: #3b3b3b; } @@ -258,6 +258,53 @@ tr.selected { box-shadow: 0px 0px 3px 3px #88b3db, inset 0px 0px 5px #88b3db; } +#playhead-information { + width: 80%; + height: 140px; + position: fixed; + bottom: 30px; + left: 10%; + padding: 16px; + box-sizing: border-box; + + background-color: #2e2d2d; + border: #424242 3px solid; + border-radius: 4px; + box-shadow: black 0px 0px 20px 5px; +} +#playhead-information.playhead-active { + border-color: #4cbe34; +} +.playhead-name { + width: 100%; + padding: 6px 12px; + box-sizing: border-box; + margin-bottom: 12px; + + background-color: #434343; + border: #434343 1px solid; + font-size: 18px; + color: white; + border-radius: 3px; +} +.playhead-disarmed { + background: url('img/disarmed-pattern-light.png'); + background-attachment: fixed; +} +#playhead-notes { + width: 100%; + padding: 6px 12px; + box-sizing: border-box; + height: 50px; + + border: #5c5b5b 1px solid; + font-size: 18px; + color: #9a9a99; + border-radius: 3px; + overflow: hidden; + text-overflow: ellipsis; +} + @media screen and (min-width: 0px) and (max-width: 750px) { .hide-medium { display: none; diff --git a/plugins/qlab/template.ejs b/plugins/qlab/template.ejs index 47ba696..65d9437 100644 --- a/plugins/qlab/template.ejs +++ b/plugins/qlab/template.ejs @@ -32,3 +32,8 @@ <% } %> +
+
[no cue on standby]
+
+
+ From aec2effee16851bafb68b9014c7da7dc95b29c3b Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Mon, 29 May 2023 08:33:15 -0500 Subject: [PATCH 12/21] update playback/display for QLab 4 compatibility --- plugins/qlab/main.js | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/plugins/qlab/main.js b/plugins/qlab/main.js index 8c97634..9090c17 100644 --- a/plugins/qlab/main.js +++ b/plugins/qlab/main.js @@ -169,12 +169,25 @@ exports.data = function data(_device, oscData) { if (cue.type === 'Cart') { device.draw(); } - } else if (/reply\/cue_id\/active\/.*Elapsed/.test(oscData.address)) { - const addrParts = json.address.split('/'); - const cueID = addrParts[4]; - const keyName = addrParts[5]; + } else if (/reply\/cue_id\/(.*)\/(.*)Elapsed/.test(oscData.address)) { + const workspace = device.data.workspaces[json.workspace_id]; + + let cueID; + let keyName; + + if (workspace?.version.startsWith('5.')) { + const addrParts = json.address.split('/'); + cueID = addrParts[4]; + keyName = addrParts[5]; + } else { + const addrParts = json.address.split('/'); + cueID = addrParts[2]; + keyName = addrParts[3]; + } + device.data.cueKeys[cueID][keyName] = json.data; device.data.somethingPlaying = true; + device.draw(); } else if (/reply\/workspace\/.*\/selectedCues/.test(oscData.address)) { const workspace = device.data.workspaces[msgAddr[2]]; @@ -220,7 +233,7 @@ function processCueList(list, knownCueIDs, _device, _nestedIndex) { device.send(`/cue_id/${cue.uniqueID}/valuesForKeys`, [{ type: 's', value: valuesForKeysString }]); // } - if (cue.cues.length > 0) { + if (cue.cues?.length > 0) { const nest2 = [..._nestedIndex]; nest2.push(cue.cues.length); processCueList(cue.cues, knownCueIDs, device, nest2); From dbb70643857ceb54a89d117eae6b9073a78d992f Mon Sep 17 00:00:00 2001 From: sparks-alec Date: Mon, 29 May 2023 13:52:58 -0400 Subject: [PATCH 13/21] helpful text if no workspace is open --- plugins/qlab/template.ejs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/qlab/template.ejs b/plugins/qlab/template.ejs index 65d9437..1703091 100644 --- a/plugins/qlab/template.ejs +++ b/plugins/qlab/template.ejs @@ -6,6 +6,11 @@ <% const workspaceKeys = Object.keys(data.workspaces); %> + +<% if(workspaceKeys.length==0){ %> +

QLab is open but there isn't an open Workspace

+<% } %> + <% for(let i=0; i <% const workspace = data.workspaces[workspaceKeys[i]]; %> From 6d69bac63956c26161562c8e4b25496054e7abf6 Mon Sep 17 00:00:00 2001 From: sparks-alec Date: Mon, 29 May 2023 13:53:17 -0400 Subject: [PATCH 14/21] set version for all workspaces --- plugins/qlab/main.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/qlab/main.js b/plugins/qlab/main.js index c34df6f..c26ab5a 100644 --- a/plugins/qlab/main.js +++ b/plugins/qlab/main.js @@ -39,6 +39,7 @@ exports.ready = function ready(_device) { device.data.workspaces = {}; device.data.cueKeys = {}; device.data.somethingPlaying = false; + device.data.version = ''; device.templates = { cue: _.template(fs.readFileSync(path.join(__dirname, `cue.ejs`))), @@ -84,6 +85,7 @@ exports.data = function data(_device, oscData) { cueLists: [], selected: [], }; + device.data.version = json.data[i].version; device.data.workspaces[json.data[i].uniqueID].permission = 'ok'; device.send(`/workspace/${json.data[i].uniqueID}/connect`, device.fields.passcode); } From af1094d3cd9bb8e2f1f9dc37143a060c684e5bb1 Mon Sep 17 00:00:00 2001 From: sparks-alec Date: Mon, 29 May 2023 16:24:54 -0400 Subject: [PATCH 15/21] remove full draws when Elapsed progress updates --- plugins/qlab/main.js | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/plugins/qlab/main.js b/plugins/qlab/main.js index ded79e0..f228692 100644 --- a/plugins/qlab/main.js +++ b/plugins/qlab/main.js @@ -38,8 +38,9 @@ exports.ready = function ready(_device) { device.send('/workspaces'); device.data.workspaces = {}; device.data.cueKeys = {}; - device.data.somethingPlaying = false; device.data.version = ''; + device.data.lastElapsedMessage = 0; + device.data.ticks = 0; device.templates = { cue: _.template(fs.readFileSync(path.join(__dirname, `cue.ejs`))), @@ -100,7 +101,6 @@ exports.data = function data(_device, oscData) { } } else if (/reply\/workspace\/.*\/cueLists/.test(oscData.address)) { device.data.workspaces[msgAddr[2]].cueLists = json.data; - device.data.somethingPlaying = false; processCueList(device.data.workspaces[msgAddr[2]].cueLists, Object.keys(device.data.cueKeys), device, []); device.draw(); } else if (/reply\/cue_id\/.*\/valuesForKeys/.test(oscData.address)) { @@ -140,7 +140,7 @@ exports.data = function data(_device, oscData) { cue.notes = keyValues.notes; if (keyValues.isRunning) { - device.data.somethingPlaying = true; + device.data.lastElapsedMessage = device.data.ticks; } let testCue = device.data.cueKeys[msgAddr[2]]; @@ -174,9 +174,9 @@ exports.data = function data(_device, oscData) { } } else if (/reply\/cue_id\/(.*)\/(.*)Elapsed/.test(oscData.address)) { const workspace = device.data.workspaces[json.workspace_id]; - let cueID; let keyName; + device.data.lastElapsedMessage = device.data.ticks; if (workspace?.version.startsWith('5.')) { const addrParts = json.address.split('/'); @@ -189,9 +189,8 @@ exports.data = function data(_device, oscData) { } device.data.cueKeys[cueID][keyName] = json.data; - device.data.somethingPlaying = true; - device.draw(); + device.update('updateCueRow', { cue: device.data.cueKeys[cueID], workspace }); } else if (/reply\/workspace\/.*\/selectedCues/.test(oscData.address)) { const workspace = device.data.workspaces[msgAddr[2]]; if (!workspace) { @@ -331,13 +330,12 @@ exports.update = function update(device, _doc, updateType, data) { } }; -let ticks = 0; exports.heartbeat = function heartbeat(device) { - ticks++; - if (ticks % 10 === 0) { + device.data.ticks++; + if (device.data.ticks % 10 === 0) { device.send(`/thump`); } - if (device.data.somethingPlaying) { + if (device.data.ticks - device.data.lastElapsedMessage < 5) { device.send(`/cue_id/active/preWaitElapsed`); device.send(`/cue_id/active/postWaitElapsed`); device.send(`/cue_id/active/actionElapsed`); From 09ceb01963170a655087a7c934997a318dfe9994 Mon Sep 17 00:00:00 2001 From: sparks-alec Date: Mon, 29 May 2023 16:27:46 -0400 Subject: [PATCH 16/21] failing to remove a full /cueLists update ideally when an /update message is received for a Group cue, we should just get the new list of children cues and stick that into the data.workspaces tree in the right spot. --- plugins/qlab/main.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/plugins/qlab/main.js b/plugins/qlab/main.js index f228692..1272690 100644 --- a/plugins/qlab/main.js +++ b/plugins/qlab/main.js @@ -201,9 +201,15 @@ exports.data = function data(_device, oscData) { workspace.selected.push(json.data[i].uniqueID); } device.update('updatePlaybackAndSelected', { workspace: device.data.workspaces[json.workspace_id] }); + } else if (/reply\/workspace\/.*\/cue_id\/.*\/children/.test(oscData.address)) { + // trying to use .cueInWorkspace to reference the related cue in data.workspaces + device.data.cueKeys[msgAddr[4]].cueInWorkspace.cues = [...json.data]; + console.log(device.data.cueKeys[msgAddr[4]].cueInWorkspace.cues); + + device.draw(); } else if (/update\/workspace\/.*\/cue_id\/.*/.test(oscData.address)) { if (device.data.cueKeys[msgAddr[4]] && device.data.cueKeys[msgAddr[4]].type === 'Group') { - device.send(`/workspace/${msgAddr[2]}/cueLists`); + device.send(`/workspace/${msgAddr[2]}/cue_id/${msgAddr[4]}/children/`); } else if (device.data.cueKeys[msgAddr[4]] && device.data.cueKeys[msgAddr[4]].type === 'Cue List') { device.send(`/workspace/${msgAddr[2]}/cueLists`); } else if (device.data.cueKeys[msgAddr[4]] && device.data.cueKeys[msgAddr[4]].type === 'Cart') { @@ -231,9 +237,9 @@ function processCueList(list, knownCueIDs, _device, _nestedIndex) { const cue = list[i]; nestedIndex[nestedIndex.length - 1] = list.length - i - 1; - // if (!knownCueIDs.includes(cue.uniqueID)) { - device.send(`/cue_id/${cue.uniqueID}/valuesForKeys`, [{ type: 's', value: valuesForKeysString }]); - // } + if (!knownCueIDs.includes(cue.uniqueID)) { + device.send(`/cue_id/${cue.uniqueID}/valuesForKeys`, [{ type: 's', value: valuesForKeysString }]); + } if (cue.cues?.length > 0) { const nest2 = [..._nestedIndex]; @@ -252,6 +258,7 @@ function processCueList(list, knownCueIDs, _device, _nestedIndex) { device.data.cueKeys[cue.uniqueID].nestedGroupPosition = [...nestedIndex]; } + // working theory is that list[i] is a new object rather pointing to the object within data.workspaces device.data.cueKeys[cue.uniqueID].cueInWorkspace = list[i]; } } From c61b1896345ad140e2adab3f6071612e83cf8598 Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Tue, 30 May 2023 17:38:44 -0500 Subject: [PATCH 17/21] update /chidren endpoint for qlab 4 --- plugins/qlab/main.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/qlab/main.js b/plugins/qlab/main.js index 1272690..eaee39e 100644 --- a/plugins/qlab/main.js +++ b/plugins/qlab/main.js @@ -201,10 +201,12 @@ exports.data = function data(_device, oscData) { workspace.selected.push(json.data[i].uniqueID); } device.update('updatePlaybackAndSelected', { workspace: device.data.workspaces[json.workspace_id] }); - } else if (/reply\/workspace\/.*\/cue_id\/.*\/children/.test(oscData.address)) { + } else if (/reply\/(workspace\/.*\/)?cue_id\/(.*)\/children/.test(oscData.address)) { + // qlab 4 leaves off the workspace/ portion of the address this regex handles that + const addressMatch = oscData.address.match(/reply\/(workspace\/.*\/)?cue_id\/(.*)\/children/); // trying to use .cueInWorkspace to reference the related cue in data.workspaces - device.data.cueKeys[msgAddr[4]].cueInWorkspace.cues = [...json.data]; - console.log(device.data.cueKeys[msgAddr[4]].cueInWorkspace.cues); + device.data.cueKeys[addressMatch[2]].cueInWorkspace.cues = [...json.data]; + console.log(device.data.cueKeys[addressMatch[2]].cueInWorkspace.cues); device.draw(); } else if (/update\/workspace\/.*\/cue_id\/.*/.test(oscData.address)) { From 0444d3976e5d2063644a0ce45f0d3276962067ec Mon Sep 17 00:00:00 2001 From: sparks-alec Date: Sun, 11 Jun 2023 15:18:39 -0400 Subject: [PATCH 18/21] improved cueList data structure magic --- plugins/qlab/cue.ejs | 66 ++++++++------ plugins/qlab/cuelist.ejs | 2 +- plugins/qlab/main.js | 186 +++++++++++++++++++++++---------------- plugins/qlab/tile.ejs | 4 +- 4 files changed, 155 insertions(+), 103 deletions(-) diff --git a/plugins/qlab/cue.ejs b/plugins/qlab/cue.ejs index b7809f6..e3547f1 100644 --- a/plugins/qlab/cue.ejs +++ b/plugins/qlab/cue.ejs @@ -2,7 +2,7 @@ <% let playbackPosition = workspace.playbackPosition==cue.uniqueID ? "playback-position" : "" %> <% let selected = workspace.selected.includes(cue.uniqueID) ? "selected": "" %> -<% if(cueKeys && cueKeys.parent && cueKeys.nestedGroupPosition){ %> +<% if(cueKeys && cueKeys.parent){ %>
@@ -48,38 +48,54 @@ + <% let nextCue = cueKeys.cueInWorkspace.parent.cues[cueKeys.cueInWorkspace.sortIndex+1]; %> - <% for(let i=0; i + <% for(let i=0; i + <% let parent = cueKeys.parentKeys[i+1]; %> + <% %> - <% if(cue.type=="Group" && cue.cues.length==0 && i==cueKeys.nestedGroupModes.length-1){ %> - - - <% }else if(cue.type=="Group" && i==cueKeys.nestedGroupModes.length-1){ %> - + <% if(cueKeys.type=="Group" && cueKeys.cues.length==0 && i==cueKeys.parentKeys.length-1){ %> + - <% }else if(cueKeys.nestedGroupPosition[i]<=0){ %> - <% if(cueKeys.nestedGroupPosition.slice(i+1).every(v => v === 0)){ %> - + <% }else if(cueKeys.type=="Group" && i==cueKeys.parentKeys.length-1){ %> + + + <%}else if(parent){%> + + <% let parentCues = parent.cueInWorkspace.cues %> + <% let lastCueInMe = parentCues[parentCues.length-1]%> + + <% while(lastCueInMe.type=="Group" && lastCueInMe.cues.length){ + parentCues = lastCueInMe.cues; + lastCueInMe = parentCues[parentCues.length-1]; + } %> + + <% if(lastCueInMe.uniqueID == cueKeys.uniqueID){ %> + <% }else{ %> - + + <% } %> - <% }else if(i - - <% }else if(cueKeys.nestedGroupPosition[i]==undefined && cueKeys.nestedGroupPosition[i-1]==0){ %> - + <% }else if(cueKeys.parentKeys[i]){ %> + <% let nextCue2 = cueKeys.parentKeys[i].cues[cueKeys.cueInWorkspace.sortIndex+1]; %> + + <% if(!nextCue2){ %> + + + <% }else{ %> + + + <% } %> - <% }else{ %> - <% } %> - - <% } %> + <% } %> @@ -87,19 +103,19 @@ <% if(cue.type=="Group"){ %> - <% if(cue.cues.length==0){ %> - + <% if(cue.cues && cue.cues.length==0){ %> + <% }else{ %> - + <% } %> - <% }else if(cueKeys.nestedGroupPosition[cueKeys.nestedGroupModes.length-2]==0){ %> - + <% }else if(!nextCue){ %> + <% }else{ %> - + <% } %> diff --git a/plugins/qlab/cuelist.ejs b/plugins/qlab/cuelist.ejs index a3d64f8..558e420 100644 --- a/plugins/qlab/cuelist.ejs +++ b/plugins/qlab/cuelist.ejs @@ -6,8 +6,8 @@ - + diff --git a/plugins/qlab/main.js b/plugins/qlab/main.js index eaee39e..ad9037b 100644 --- a/plugins/qlab/main.js +++ b/plugins/qlab/main.js @@ -100,9 +100,7 @@ exports.data = function data(_device, oscData) { device.send(`/workspace/${msgAddr[2]}/updates`, [{ type: 'i', value: 1 }]); } } else if (/reply\/workspace\/.*\/cueLists/.test(oscData.address)) { - device.data.workspaces[msgAddr[2]].cueLists = json.data; - processCueList(device.data.workspaces[msgAddr[2]].cueLists, Object.keys(device.data.cueKeys), device, []); - device.draw(); + insertChildCues(device, device.data.workspaces[msgAddr[2]].cueLists, json.data, '[root group of cue lists]'); } else if (/reply\/cue_id\/.*\/valuesForKeys/.test(oscData.address)) { const keyValues = json.data; let cue = device.data.cueKeys[msgAddr[2]]; @@ -143,29 +141,22 @@ exports.data = function data(_device, oscData) { device.data.lastElapsedMessage = device.data.ticks; } - let testCue = device.data.cueKeys[msgAddr[2]]; - let nestedGroupModes; - - if (cue.type === 'Group') { - nestedGroupModes = [testCue.mode]; - } else if (cue.parent !== '[root group of cue lists]') { - nestedGroupModes = [device.data.cueKeys[cue.parent].mode]; + if (cue.parent !== '[root group of cue lists]') { + cue.parent = device.data.cueKeys[cue.parent]; } - while (testCue.parent !== '[root group of cue lists]') { - testCue = device.data.cueKeys[testCue.parent]; - if (testCue === undefined) { - break; - } - - nestedGroupModes.unshift(testCue.mode); - cue.nestedGroupModes = nestedGroupModes; - } if (cue.type === 'Cue List' || cue.type === 'Group' || cue.type === 'Cart') { - if (device.data.cueKeys[msgAddr[2]].cueInWorkspace) { - device.data.cueKeys[msgAddr[2]].cueInWorkspace.cues = [...keyValues.children]; + if (device.data.cueKeys[msgAddr[2]].cueInWorkspace && cue.cues) { + insertChildCues( + device, + device.data.cueKeys[msgAddr[2]].cueInWorkspace.cues, + [...keyValues.children], + device.data.cueKeys[msgAddr[2]].cueInWorkspace + ); } + device.draw(); } + if (cue.type !== 'Cue List' && cue.type !== 'Cart') { device.update('updateCueRow', { cue, workspace: device.data.workspaces[json.workspace_id] }); } @@ -201,24 +192,8 @@ exports.data = function data(_device, oscData) { workspace.selected.push(json.data[i].uniqueID); } device.update('updatePlaybackAndSelected', { workspace: device.data.workspaces[json.workspace_id] }); - } else if (/reply\/(workspace\/.*\/)?cue_id\/(.*)\/children/.test(oscData.address)) { - // qlab 4 leaves off the workspace/ portion of the address this regex handles that - const addressMatch = oscData.address.match(/reply\/(workspace\/.*\/)?cue_id\/(.*)\/children/); - // trying to use .cueInWorkspace to reference the related cue in data.workspaces - device.data.cueKeys[addressMatch[2]].cueInWorkspace.cues = [...json.data]; - console.log(device.data.cueKeys[addressMatch[2]].cueInWorkspace.cues); - - device.draw(); } else if (/update\/workspace\/.*\/cue_id\/.*/.test(oscData.address)) { - if (device.data.cueKeys[msgAddr[4]] && device.data.cueKeys[msgAddr[4]].type === 'Group') { - device.send(`/workspace/${msgAddr[2]}/cue_id/${msgAddr[4]}/children/`); - } else if (device.data.cueKeys[msgAddr[4]] && device.data.cueKeys[msgAddr[4]].type === 'Cue List') { - device.send(`/workspace/${msgAddr[2]}/cueLists`); - } else if (device.data.cueKeys[msgAddr[4]] && device.data.cueKeys[msgAddr[4]].type === 'Cart') { - device.send(`/workspace/${msgAddr[2]}/cueLists`); - } else { - device.send(`/cue_id/${msgAddr[4]}/valuesForKeys`, [{ type: 's', value: valuesForKeysString }]); - } + device.send(`/cue_id/${msgAddr[4]}/valuesForKeys`, [{ type: 's', value: valuesForKeysString }]); } else if (/update\/workspace\/.*\/cueList\/.*\/playbackPosition/.test(oscData.address)) { const workspace = device.data.workspaces[msgAddr[2]]; if (workspace) { @@ -231,46 +206,12 @@ exports.data = function data(_device, oscData) { } }; -function processCueList(list, knownCueIDs, _device, _nestedIndex) { - const nestedIndex = _nestedIndex; - const device = _device; - - for (let i = 0; i < list.length; i++) { - const cue = list[i]; - nestedIndex[nestedIndex.length - 1] = list.length - i - 1; - - if (!knownCueIDs.includes(cue.uniqueID)) { - device.send(`/cue_id/${cue.uniqueID}/valuesForKeys`, [{ type: 's', value: valuesForKeysString }]); - } - - if (cue.cues?.length > 0) { - const nest2 = [..._nestedIndex]; - nest2.push(cue.cues.length); - processCueList(cue.cues, knownCueIDs, device, nest2); - for (let j = 0; j < nestedIndex.length; j++) { - nestedIndex[j]++; - } - } - - if (!knownCueIDs.includes(cue.uniqueID)) { - device.data.cueKeys[cue.uniqueID] = { - nestedGroupPosition: [...nestedIndex], - }; - } else { - device.data.cueKeys[cue.uniqueID].nestedGroupPosition = [...nestedIndex]; - } - - // working theory is that list[i] is a new object rather pointing to the object within data.workspaces - device.data.cueKeys[cue.uniqueID].cueInWorkspace = list[i]; - } -} - exports.update = function update(device, _doc, updateType, data) { const doc = _doc; if (updateType === 'updateCueRow') { const $elem = doc.getElementById(data.cue.uniqueID); if ($elem) { - if (device.data.cueKeys[data.cue.parent].type === 'Cart') { + if (data.cue.parent.type === 'Cart') { $elem.outerHTML = device.templates.tile({ allCues: device.data.cueKeys, cue: data.cue, @@ -311,7 +252,7 @@ exports.update = function update(device, _doc, updateType, data) { } const cue = device.data.cueKeys[data.workspace.playbackPosition]; - if (cue) { + if (cue && $playheadName) { $playheadName.setAttribute('class', `playhead-name cartColor-${cue.colorName} playhead-disarmed`); $playheadInfo.classList.add('playhead-active'); if (cue.number) { @@ -339,7 +280,8 @@ exports.update = function update(device, _doc, updateType, data) { } }; -exports.heartbeat = function heartbeat(device) { +exports.heartbeat = function heartbeat(_device) { + const device = _device; device.data.ticks++; if (device.data.ticks % 10 === 0) { device.send(`/thump`); @@ -350,3 +292,97 @@ exports.heartbeat = function heartbeat(device) { device.send(`/cue_id/active/actionElapsed`); } }; + +function insertChildCues(_device, _oldCues, newCues, _parent) { + const device = _device; + const newIDs = _.keyBy(newCues, 'uniqueID'); + const oldCues = _oldCues; + const parent = _parent; + + if (!oldCues) { + return; + } + + for (let i = 0; i < oldCues.length; i++) { + const oldCue = oldCues[i]; + + if (!newIDs[oldCue.uniqueID]) { + oldCues.splice(i, 1); + i--; + } + + const indexOfOldCueInNew = _.findIndex(newCues, (o) => o.uniqueID === oldCue.uniqueID); + + if (indexOfOldCueInNew >= 0) { + const newCue = newCues[indexOfOldCueInNew]; + oldCues[i].armed = newCue.armed; + oldCues[i].colorName = newCue.colorName; + oldCues[i].colorNameLive = newCue['colorName/live']; + + oldCues[i].flagged = newCue.flagged; + oldCues[i].listName = newCue.listName; + oldCues[i].name = newCue.name; + oldCues[i].number = newCue.number; + oldCues[i].type = newCue.type; + oldCues[i].sortIndex = indexOfOldCueInNew; + oldCues[i].parent = parent; + + newCue.used = true; + + let keys = device.data.cueKeys[oldCues[i].uniqueID]; + if (!keys) { + device.data.cueKeys[oldCues[i].uniqueID] = {}; + keys = device.data.cueKeys[oldCues[i].uniqueID]; + } + + keys.cueInWorkspace = oldCues[i]; + + if (oldCues[i].type === 'Group' || oldCues[i].type === 'Cue List' || oldCues[i].type === 'Cart') { + insertChildCues(device, oldCues[i].cues, newCue.cues, oldCues[i]); + } + } + } + for (let i = 0; i < newCues.length; i++) { + const newCue = newCues[i]; + if (newCue.used !== true) { + const newIndex = oldCues.push({ + armed: newCue.armed, + colorName: newCue.colorName, + colorNameLive: newCue['colorName/live'], + flagged: newCue.flagged, + listName: newCue.listName, + name: newCue.name, + number: newCue.number, + type: newCue.type, + uniqueID: newCue.uniqueID, + sortIndex: i, + parent, + }); + + const freshCue = oldCues[newIndex - 1]; + + let keys = device.data.cueKeys[freshCue.uniqueID]; + if (!keys) { + device.data.cueKeys[freshCue.uniqueID] = {}; + keys = device.data.cueKeys[freshCue.uniqueID]; + } + + if (freshCue.parent === '[root group of cue lists]') { + keys.parentKeys = []; + } else { + keys.parentKeys = [...device.data.cueKeys[freshCue.parent.uniqueID].parentKeys]; + keys.parentKeys.push(device.data.cueKeys[freshCue.parent.uniqueID]); + } + + device.send(`/cue_id/${newCue.uniqueID}/valuesForKeys`, [{ type: 's', value: valuesForKeysString }]); + + keys.cueInWorkspace = freshCue; + + if (freshCue.type === 'Group' || freshCue.type === 'Cue List' || oldCues[i].type === 'Cart') { + freshCue.cues = []; + insertChildCues(device, freshCue.cues, newCue.cues, freshCue); + } + } + } + oldCues.sort((a, b) => a.sortIndex - b.sortIndex); +} diff --git a/plugins/qlab/tile.ejs b/plugins/qlab/tile.ejs index 82134c7..df8d5f6 100644 --- a/plugins/qlab/tile.ejs +++ b/plugins/qlab/tile.ejs @@ -7,8 +7,8 @@ let parent = allCues[cue.parent]; if(cueKeys.parent){ - width = 100/allCues[cueKeys.parent].cartColumns; - height = 600/allCues[cueKeys.parent].cartRows; + width = 100/cueKeys.parent.cartColumns; + height = 600/cueKeys.parent.cartRows; top = (cueKeys.cartPosition[0]-1)*height; left = (cueKeys.cartPosition[1]-1)*width; From 382dc8a0fed2eebcb90b8ac8d6fbae5099bf2475 Mon Sep 17 00:00:00 2001 From: sparks-alec Date: Sun, 11 Jun 2023 15:18:55 -0400 Subject: [PATCH 19/21] make playhead a tad smaller --- plugins/qlab/styles.css | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/plugins/qlab/styles.css b/plugins/qlab/styles.css index fca0407..48ebf39 100644 --- a/plugins/qlab/styles.css +++ b/plugins/qlab/styles.css @@ -1,3 +1,6 @@ +body { + margin-bottom: 200px !important; +} table { background-color: #323232; border: 0px; @@ -260,7 +263,7 @@ div.cartColor-none { #playhead-information { width: 80%; - height: 140px; + height: 120px; position: fixed; bottom: 30px; left: 10%; @@ -293,9 +296,9 @@ div.cartColor-none { } #playhead-notes { width: 100%; - padding: 6px 12px; + padding: 6px 8px; box-sizing: border-box; - height: 50px; + height: 36px; border: #5c5b5b 1px solid; font-size: 18px; From bf06ec7f3a4791013e5881903636ce98ad63e649 Mon Sep 17 00:00:00 2001 From: sparks-alec Date: Sun, 11 Jun 2023 15:20:32 -0400 Subject: [PATCH 20/21] remove debugging mark --- plugins/qlab/cue.ejs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/qlab/cue.ejs b/plugins/qlab/cue.ejs index e3547f1..c8342b9 100644 --- a/plugins/qlab/cue.ejs +++ b/plugins/qlab/cue.ejs @@ -72,7 +72,7 @@ } %> <% if(lastCueInMe.uniqueID == cueKeys.uniqueID){ %> - + <% }else{ %> From 57324c82e980c920aa24ce5a660b8b6d3775384d Mon Sep 17 00:00:00 2001 From: sparks-alec Date: Sun, 11 Jun 2023 15:31:34 -0400 Subject: [PATCH 21/21] add .draw() to make sure troubleshooting info is displayed --- plugins/qlab/main.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/qlab/main.js b/plugins/qlab/main.js index ad9037b..7a8d4e6 100644 --- a/plugins/qlab/main.js +++ b/plugins/qlab/main.js @@ -90,7 +90,9 @@ exports.data = function data(_device, oscData) { device.data.workspaces[json.data[i].uniqueID].permission = 'ok'; device.send(`/workspace/${json.data[i].uniqueID}/connect`, device.fields.passcode); } + this.deviceInfoUpdate(device, 'status', 'ok'); + device.draw(); } else if (/reply\/workspace\/.*\/connect/.test(oscData.address)) { if (json.data === 'badpass') { device.data.workspaces[json.workspace_id].permission = 'badpass'; @@ -160,9 +162,6 @@ exports.data = function data(_device, oscData) { if (cue.type !== 'Cue List' && cue.type !== 'Cart') { device.update('updateCueRow', { cue, workspace: device.data.workspaces[json.workspace_id] }); } - if (cue.type === 'Cart') { - device.draw(); - } } else if (/reply\/cue_id\/(.*)\/(.*)Elapsed/.test(oscData.address)) { const workspace = device.data.workspaces[json.workspace_id]; let cueID;
Number QName TargetPre WaitActionPost WaitPre-WaitDurationPost-Wait
<%= cueKeys.number || " " %>
<%= cueKeys.number || " " %>
x<%= cueKeys.listName %><%= cueKeys.listName %><%= cueKeys.listName %><%= cueKeys.listName %><%= cueKeys.listName %><%= cueKeys.listName %><%= cueKeys.listName %><%= cueKeys.listName %> Number Name x