const _ = require('lodash'); const fs = require('fs'); const path = require('path'); exports.config = { defaultName: 'QLab', connectionType: 'osc', defaultPort: 53000, mayChangePort: true, heartbeatInterval: 100, heartbeatTimeout: 5000, searchOptions: { type: 'Bonjour', bonjourName: 'qlab', }, fields: [ { key: 'passcode', label: 'Pass', type: 'textinput', value: '', action(device) { device.send('/workspaces'); }, }, ], }; let lastElapsedUpdate = Date.now(); let interval = 5; let heartbeatCount = 0; const valuesForKeysString = '["uniqueID","number","name","listName","isBroken","isRunning","isLoaded","isFlagged",' + '"type","children","preWait","postWait","currentDuration","colorName","continueMode",' + '"mode","parent","cartRows","cartColumns","cartPosition","displayName","preWaitElapsed",' + '"actionElapsed","postWaitElapsed","isPaused"]'; exports.ready = function ready(_device) { const device = _device; device.templates = { cue: _.template(fs.readFileSync(path.join(__dirname, `cue.ejs`))), tile: _.template(fs.readFileSync(path.join(__dirname, `tile.ejs`))), cart: _.template(fs.readFileSync(path.join(__dirname, `cart.ejs`))), cuelist: _.template(fs.readFileSync(path.join(__dirname, `cuelist.ejs`))), }; device.send(`/version`); device.send('/workspaces'); }; exports.data = function data(_device, oscData) { const device = _device; const oscAddressParts = oscData.address.split('/'); oscAddressParts.shift(); if (match(oscAddressParts, ['reply', 'version'])) { const json = JSON.parse(oscData.args[0]); device.data.version = json.data; this.deviceInfoUpdate(device, 'status', 'ok'); } else if (match(oscAddressParts, ['reply', 'workspaces'])) { const json = JSON.parse(oscData.args[0]); device.data.workspaces = {}; json.data.forEach((wksp) => { 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 = {}; 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; } 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.draw(); } else if (match(oscAddressParts, ['reply', 'thump'])) { // intermittent connection check even if nothing's happening } else { // console.log(address) } }; exports.update = function update(device, doc, updateType, data) { if (updateType === 'updateCueData') { const $elem = doc.getElementById(data.cue.uniqueID); if ($elem) { if (data.cue.type === 'Cue List') { $elem.outerHTML = `