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/cue.ejs b/plugins/qlab/cue.ejs index e2b97ef..c8342b9 100644 --- a/plugins/qlab/cue.ejs +++ b/plugins/qlab/cue.ejs @@ -1,145 +1,206 @@ +<% 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 || " " %>
+ + + <% let nextCue = cueKeys.cueInWorkspace.parent.cues[cueKeys.cueInWorkspace.sortIndex+1]; %> + + + <% for(let i=0; i + <% let parent = cueKeys.parentKeys[i+1]; %> + <% %> + + <% if(cueKeys.type=="Group" && cueKeys.cues.length==0 && i==cueKeys.parentKeys.length-1){ %> + + + <% }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(cueKeys.parentKeys[i]){ %> + <% let nextCue2 = cueKeys.parentKeys[i].cues[cueKeys.cueInWorkspace.sortIndex+1]; %> + + <% if(!nextCue2){ %> + + + <% }else{ %> + + + <% } %> + + + <% } %> + + <% } %> + + + + + + <% if(cue.type=="Group"){ %> + + <% if(cue.cues && cue.cues.length==0){ %> + <%= cueKeys.listName %> + + <% }else{ %> + <%= cueKeys.listName %> + + <% } %> + + <% }else if(!nextCue){ %> + <%= 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 +212,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 +232,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 +240,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..558e420 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: {}, - }; - device.send(`/workspace/${wksp.uniqueID}/connect`, device.fields.passcode); - }); - - if (Object.keys(device.data.workspaces).length === 0) { - device.data.permission = 'no workspaces'; - device.draw(); - } - } 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 = {}; + let json = []; + try { + json = JSON.parse(oscData.args[0]); + } catch (err) { + // a handful of messages don't respond with JSON + } + if (Object.keys(json).length > 0) { if (json.status === 'denied') { - 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; + if (device.data.workspaces[json.workspace_id]) { + device.data.workspaces[json.workspace_id].permission = 'denied'; } - - 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]; + return; } - - 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) { + if (json.status === 'error') { device.send('/workspaces'); + return; } - } 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 (oscData.address === '/reply/workspaces') { + 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.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); + } - 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'; - } + this.deviceInfoUpdate(device, 'status', 'ok'); device.draw(); - } else if (match(oscAddressParts, ['reply', 'thump'])) { - // intermittent connection check even if nothing's happening - } else { - // console.log(address) + } else if (/reply\/workspace\/.*\/connect/.test(oscData.address)) { + if (json.data === 'badpass') { + device.data.workspaces[json.workspace_id].permission = 'badpass'; + } else { + device.data.workspaces[json.workspace_id].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)) { + 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]]; + + if (cue === undefined) { + device.data.cueKeys[msgAddr[2]] = {}; + cue = device.data.cueKeys[msgAddr[2]]; + } + 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; + cue.notes = keyValues.notes; + + if (keyValues.isRunning) { + device.data.lastElapsedMessage = device.data.ticks; + } + + if (cue.parent !== '[root group of cue lists]') { + cue.parent = device.data.cueKeys[cue.parent]; + } + + if (cue.type === 'Cue List' || cue.type === 'Group' || cue.type === 'Cart') { + 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] }); + } + } 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('/'); + 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.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) { + return; + } + 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)) { + 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) { + workspace.playbackPosition = oscData.args[0]; + 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`); } }; -exports.update = function update(device, doc, updateType, data) { - if (updateType === 'updateCueData') { +exports.update = function update(device, _doc, updateType, data) { + const doc = _doc; + 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, + if (data.cue.parent.type === 'Cart') { + $elem.outerHTML = device.templates.tile({ + allCues: device.data.cueKeys, + cue: data.cue, + workspace: data.workspace, }); - } 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); + $elem.outerHTML = device.templates.cue({ + allCues: device.data.cueKeys, + cue: data.cue, + workspace: data.workspace, + }); } } - } else if (updateType === 'updatePlaybackPosition') { - Array.from(doc.getElementsByClassName('playback-position')).forEach(($elem, index, array) => { - $elem.classList.remove('playback-position'); + } else if (updateType === 'updatePlaybackAndSelected') { + Array.from(doc.querySelectorAll('.selected')).forEach(($el) => { + $el.classList.remove('selected'); + }); + Array.from(doc.querySelectorAll('.playback-position')).forEach(($el) => { + $el.classList.remove('playback-position'); }); - const $elem = doc.getElementById(data.cue.uniqueID); - $elem.classList.add('playback-position'); - $elem.scrollIntoView({ behavior: 'smooth', block: 'center' }); + 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 $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) { + $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'); + } } }; -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; -} - -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) { +exports.heartbeat = function heartbeat(_device) { + const device = _device; + device.data.ticks++; + if (device.data.ticks % 10 === 0) { device.send(`/thump`); } - - if (heartbeatCount % interval === 0 && device.data.workspaces && Object.keys(device.data.workspaces).length > 0) { + if (device.data.ticks - device.data.lastElapsedMessage < 5) { device.send(`/cue_id/active/preWaitElapsed`); - device.send(`/cue_id/active/actionElapsed`); device.send(`/cue_id/active/postWaitElapsed`); + 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/styles.css b/plugins/qlab/styles.css index 65d297e..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; @@ -33,61 +36,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 +98,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 +151,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 +180,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; } @@ -193,6 +207,10 @@ tr.playback-position .q-gray-text { 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; @@ -209,27 +227,27 @@ tr.playback-position .q-gray-text { right: 13px; top: 13px; } -.cartColor-red { - border-color: #ff4242; - background-color: #9b3726; +div.cartColor-red { + border-color: #e7443a; + background-color: #942f27; } -.cartColor-orange { - border-color: #ffa500; - background-color: #ad6026; +div.cartColor-orange { + border-color: #f18f28; + background-color: #a95023; } -.cartColor-green { - border-color: #01d52f; - background-color: #397f27; +div.cartColor-green { + border-color: #4ebf32; + background-color: #397824; } -.cartColor-blue { - border-color: #536de0; - background-color: #304893; +div.cartColor-blue { + border-color: #3a54cf; + background-color: #25378a; } -.cartColor-purple { - border-color: #a601c0; - background-color: #592d74; +div.cartColor-purple { + border-color: #7e25a5; + background-color: #4c2269; } -.cartColor-none { +div.cartColor-none { border-color: #95929f; background-color: #3b3b3b; } @@ -243,6 +261,53 @@ tr.playback-position .q-gray-text { box-shadow: 0px 0px 3px 3px #88b3db, inset 0px 0px 5px #88b3db; } +#playhead-information { + width: 80%; + height: 120px; + 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 8px; + box-sizing: border-box; + height: 36px; + + 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 e55bd0b..1703091 100644 --- a/plugins/qlab/template.ejs +++ b/plugins/qlab/template.ejs @@ -3,26 +3,42 @@

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

+ + +<% const workspaceKeys = Object.keys(data.workspaces); %> + +<% if(workspaceKeys.length==0){ %> +

QLab is open but there isn't an open Workspace

+<% } %> + - <% 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"){ %> + <% if(workspace.permission=="ok"){ %> -

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, workspace: workspace}) %> + <% } %> -

Incorrect Passcode or OSC Access Permissions

- + <% } %> + + <% }else{ %> + +

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

+ +

 

+ <% } %> <% } %> + +
+
[no cue on standby]
+
+
+ diff --git a/plugins/qlab/tile.ejs b/plugins/qlab/tile.ejs index ca8e9e2..df8d5f6 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/cueKeys.parent.cartColumns; + height = 600/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,21 @@
>
- <% if(cue.broken){ %> + <% if(cueKeys.isBroken){ %> - <% }else if(cue.running){ %> + <% }else if(cueKeys.isRunning){ %> <% }else{ %> <% } %>
-
-

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

+
+

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

\ No newline at end of file
NumberName Q TargetPre WaitActionPost WaitPre-WaitDurationPost-Wait