Compare commits
No commits in common. "master" and "dev" have entirely different histories.
|
@ -3,36 +3,21 @@ jobs:
|
||||||
build:
|
build:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/node:latest
|
- image: circleci/node:latest
|
||||||
working_directory: ~/app
|
working_directory: ~/caspar-sup
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
|
- setup_remote_docker
|
||||||
- run:
|
- run:
|
||||||
name: Install npm deployment app
|
name: Build docker image
|
||||||
command: sudo npm install -g github-release-cli @babel/runtime
|
command: docker build -t nfpis/caspar-sup:build_${CIRCLE_BUILD_NUM} -t nfpis/caspar-sup:${CIRCLE_SHA1} -t nfpis/caspar-sup:latest .
|
||||||
- run:
|
|
||||||
name: Build client javascript
|
|
||||||
command: |
|
|
||||||
npm install
|
|
||||||
npm run build
|
|
||||||
- deploy:
|
- deploy:
|
||||||
name: Create a release
|
name: Push to docker
|
||||||
command: |
|
command: |
|
||||||
PACKAGE_VERSION=$(cat package.json | grep version | head -1 | awk -F: '{ print $2 }' | sed 's/[", ]//g')
|
docker login -u $DOCKER_USER -p $DOCKER_PASS
|
||||||
echo "Packaging to ${CIRCLE_PROJECT_REPONAME}_build-sc.zip"
|
docker push nfpis/caspar-sup
|
||||||
zip "${CIRCLE_PROJECT_REPONAME}_build-sc.zip" index.mjs package.json public/* api/**/* api/*
|
|
||||||
echo "Creating release '${PACKAGE_VERSION}.${CIRCLE_BUILD_NUM}'"
|
|
||||||
github-release upload \
|
|
||||||
--commitish $CIRCLE_SHA1 \
|
|
||||||
--token $GITHUB_TOKEN \
|
|
||||||
--owner $CIRCLE_PROJECT_USERNAME \
|
|
||||||
--repo $CIRCLE_PROJECT_REPONAME \
|
|
||||||
--tag "v${PACKAGE_VERSION}.${CIRCLE_BUILD_NUM}" \
|
|
||||||
--release-name "v${PACKAGE_VERSION}.${CIRCLE_BUILD_NUM}" \
|
|
||||||
--body "Automatic CircleCI Build of v${PACKAGE_VERSION}.${CIRCLE_BUILD_NUM} from ${CIRCLE_SHA1}" \
|
|
||||||
"${CIRCLE_PROJECT_REPONAME}_build-sc.zip"
|
|
||||||
workflows:
|
workflows:
|
||||||
version: 2
|
version: 2
|
||||||
build_deploy:
|
build_deploy:
|
||||||
jobs:
|
jobs:
|
||||||
- build:
|
- build:
|
||||||
context: github-thething
|
context: org-global
|
||||||
|
|
|
@ -22,7 +22,7 @@ FROM node:13-alpine
|
||||||
|
|
||||||
ENV HOME=/app
|
ENV HOME=/app
|
||||||
|
|
||||||
COPY index.mjs package.json $HOME/
|
COPY .babelrc config.js log.js index.js package.json $HOME/
|
||||||
|
|
||||||
WORKDIR $HOME
|
WORKDIR $HOME
|
||||||
|
|
||||||
|
|
|
@ -1,219 +1,69 @@
|
||||||
import net from 'net'
|
import CasparConnection from 'casparcg-connection'
|
||||||
import parser from 'p3x-xml2json'
|
|
||||||
|
const CasparCG = CasparConnection.CasparCG
|
||||||
|
const AMCP = CasparConnection.AMCP
|
||||||
|
|
||||||
|
const timeoutDuration = 5000
|
||||||
|
|
||||||
let io
|
let io
|
||||||
let logger
|
let logger
|
||||||
|
|
||||||
|
let connection
|
||||||
|
let casparIsPlaying
|
||||||
|
let casparIsConnected
|
||||||
let currentHost
|
let currentHost
|
||||||
let client
|
|
||||||
let db
|
|
||||||
|
|
||||||
let queue = []
|
export function initialise(log, db, socket) {
|
||||||
let reconnectInterval = 1000
|
io = socket
|
||||||
let isReconnecting = false
|
|
||||||
let connected = false
|
|
||||||
let playing = false
|
|
||||||
let lastError = ''
|
|
||||||
|
|
||||||
function startReconnecting() {
|
|
||||||
connected = false
|
|
||||||
playing = false
|
|
||||||
if (queue.length) {
|
|
||||||
queue.splice(0, queue.length)
|
|
||||||
}
|
|
||||||
if(isReconnecting !== false) return
|
|
||||||
reconnectInterval = Math.min(reconnectInterval * 1.5, 1000 * 60 * 5)
|
|
||||||
isReconnecting = setTimeout(connect, reconnectInterval)
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearReconnect() {
|
|
||||||
if(isReconnecting === false) return
|
|
||||||
clearTimeout(isReconnecting)
|
|
||||||
isReconnecting = false
|
|
||||||
}
|
|
||||||
|
|
||||||
export function queueCommand(command) {
|
|
||||||
return new Promise((res, rej) => {
|
|
||||||
if (isReconnecting) {
|
|
||||||
return rej(new Error('CasparCG is not connected, unable to play command'))
|
|
||||||
}
|
|
||||||
let request = {
|
|
||||||
command: command,
|
|
||||||
res: res,
|
|
||||||
rej: rej,
|
|
||||||
started: new Date(),
|
|
||||||
finished: null,
|
|
||||||
timeout: null,
|
|
||||||
}
|
|
||||||
queue.push(request)
|
|
||||||
|
|
||||||
request.timeout = setTimeout(function() {
|
|
||||||
if (request.finished) return
|
|
||||||
queue.splice(queue.indexOf(request), 1)
|
|
||||||
rej(new Error(`CasparCGCommand "${command}" timed out after 15 seconds`))
|
|
||||||
}, 15000)
|
|
||||||
|
|
||||||
logger.info('CasparCG Command:', command)
|
|
||||||
client.write(command + '\r\n')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function checkPlaying(db, io, wasSuccess) {
|
|
||||||
if (!connected) return
|
|
||||||
let path = `http://${db.get('settings.casparplayhost').value()}/client.html`
|
|
||||||
|
|
||||||
try {
|
|
||||||
logger.info('CasparCG: Checking if already playing')
|
|
||||||
let res = await queueCommand('INFO 1-100')
|
|
||||||
if (res.body.channel
|
|
||||||
&& res.body.channel.stage
|
|
||||||
&& res.body.channel.stage.layer
|
|
||||||
&& res.body.channel.stage.layer.layer_100
|
|
||||||
&& res.body.channel.stage.layer.layer_100.foreground
|
|
||||||
&& res.body.channel.stage.layer.layer_100.foreground.file
|
|
||||||
&& res.body.channel.stage.layer.layer_100.foreground.file.path === path) {
|
|
||||||
logger.info('CasparCG: Player is playing')
|
|
||||||
playing = true
|
|
||||||
lastError = ''
|
|
||||||
io.emit('casparcg.status', currentStatus())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (wasSuccess) {
|
|
||||||
logger.warn(res.body, 'CasparCG: Playing was marked as succeeded but could not verify it')
|
|
||||||
playing = true
|
|
||||||
lastError = 'Sending play command succeeded but was unable to verify'
|
|
||||||
io.emit('casparcg.status', currentStatus())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
playing = false
|
|
||||||
lastError = 'Sending play command'
|
|
||||||
io.emit('casparcg.status', currentStatus())
|
|
||||||
logger.info(res.body, 'CasparCG: Sending play command')
|
|
||||||
res = await queueCommand(`PLAY 1-100 [HTML] "${path}" CUT 1 LINEAR RIGHT`)
|
|
||||||
return setTimeout(function() {
|
|
||||||
checkPlaying(db, io, true).then()
|
|
||||||
}, 300)
|
|
||||||
} catch (err) {
|
|
||||||
playing = false
|
|
||||||
lastError = `CasparCG: Error checking if playing: ${err.message}. Checking again in 5seconds`
|
|
||||||
logger.error(err, 'CasparCG: Error checking if playing')
|
|
||||||
io.emit('casparcg.status', currentStatus())
|
|
||||||
}
|
|
||||||
|
|
||||||
return setTimeout(function() {
|
|
||||||
checkPlaying(db, io, false).then()
|
|
||||||
}, 5000)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function initialise(log, database, ioOrg) {
|
|
||||||
io = ioOrg
|
|
||||||
logger = log
|
logger = log
|
||||||
db = database
|
db = db
|
||||||
|
|
||||||
client = new net.Socket()
|
connect(db)
|
||||||
client.setEncoding('utf8')
|
|
||||||
|
|
||||||
client.on('connect', function () {
|
|
||||||
clearReconnect()
|
|
||||||
connected = true
|
|
||||||
lastError = ''
|
|
||||||
reconnectInterval = 1000
|
|
||||||
logger.info('CasparCG: Connected to server')
|
|
||||||
io.emit('casparcg.status', currentStatus())
|
|
||||||
checkPlaying(db, io, false).then()
|
|
||||||
// client.write('INFO 1-100\r\n');
|
|
||||||
})
|
|
||||||
|
|
||||||
client.on('data', function (data) {
|
|
||||||
let request = null
|
|
||||||
|
|
||||||
if (queue.length > 0) {
|
|
||||||
request = queue[0]
|
|
||||||
queue.splice(0, 1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!request) {
|
export function connect(db) {
|
||||||
return logger.warn({ data }, 'Received unknown response with no command')
|
|
||||||
}
|
|
||||||
|
|
||||||
let status
|
|
||||||
let splitted
|
|
||||||
let header
|
|
||||||
let body
|
|
||||||
let parsed
|
|
||||||
|
|
||||||
try {
|
|
||||||
splitted = data.split('\n')
|
|
||||||
header = splitted[0].replace('\r', '')
|
|
||||||
status = Number(header.split(' ')[0])
|
|
||||||
body = splitted.slice(1)
|
|
||||||
parsed = JSON.parse(parser.toJson(body.join('\n')))
|
|
||||||
} catch (err) {
|
|
||||||
return request.rej(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
request.finished = new Date()
|
|
||||||
clearTimeout(request.timeout)
|
|
||||||
if (status && status < 300) {
|
|
||||||
request.res({
|
|
||||||
status: status,
|
|
||||||
header: header,
|
|
||||||
body: parsed || {},
|
|
||||||
raw: data
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
request.err({
|
|
||||||
status: status,
|
|
||||||
header: header,
|
|
||||||
body: parsed || {},
|
|
||||||
raw: data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
client.on('error', function (err) {
|
|
||||||
lastError = 'CasparCG TCP Error: ' + err.code + ', retrying in ' + Math.round(reconnectInterval / 1000) + ' sec'
|
|
||||||
logger.warn(lastError)
|
|
||||||
io.emit('casparcg.status', currentStatus())
|
|
||||||
startReconnecting()
|
|
||||||
})
|
|
||||||
client.on('close', function() {
|
|
||||||
startReconnecting()
|
|
||||||
})
|
|
||||||
client.on('end', function() {
|
|
||||||
startReconnecting()
|
|
||||||
})
|
|
||||||
|
|
||||||
connect()
|
|
||||||
}
|
|
||||||
|
|
||||||
export function connect() {
|
|
||||||
clearReconnect()
|
|
||||||
currentHost = db.get('settings').value().casparhost
|
currentHost = db.get('settings').value().casparhost
|
||||||
lastError = 'CasparCG: Connecting to ' + currentHost + ':' + 5250
|
casparIsPlaying = false
|
||||||
logger.info(lastError)
|
casparIsConnected = false
|
||||||
io.emit('casparcg.status', currentStatus())
|
logger.info('CasparCG: Connectiong to', currentHost + ':' + 5250)
|
||||||
|
|
||||||
client.connect({
|
connection = new CasparCG({
|
||||||
|
host: currentHost,
|
||||||
port: 5250,
|
port: 5250,
|
||||||
host: currentHost
|
queueMode: 2,
|
||||||
|
autoReconnectInterval: timeoutDuration,
|
||||||
|
onError: err => {
|
||||||
|
logger.error(err, 'CasparCG: Error')
|
||||||
|
},
|
||||||
|
onConnectionStatus: data => {
|
||||||
|
if (casparIsPlaying) return
|
||||||
|
casparIsConnected = data.connected
|
||||||
|
|
||||||
|
if (!casparIsConnected) {
|
||||||
|
logger.warn(`CasparCG: connection down, retrying in ${timeoutDuration / 1000} seconds`)
|
||||||
|
io.emit('casparcg.status', currentStatus())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onConnected: async connected => {
|
||||||
|
if (casparIsPlaying) return
|
||||||
|
logger.info('CasparCG: connected', connected)
|
||||||
|
if (!casparIsPlaying) {
|
||||||
|
startPlaying(db).then()
|
||||||
|
} else {
|
||||||
|
logger.warn('CasparCG: Stopped from starting play again.')
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function currentStatus(e) {
|
export function currentStatus(e) {
|
||||||
return {
|
return {
|
||||||
connected: connected,
|
connected: casparIsConnected,
|
||||||
playing: playing,
|
playing: casparIsPlaying,
|
||||||
error: lastError,
|
error: e,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sendCommand(command) {
|
|
||||||
return new Promise(function(res, rej) {
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
export async function startPlaying(db) {
|
export async function startPlaying(db) {
|
||||||
let ip = db.get('settings').value().casparplayhost
|
let ip = db.get('settings').value().casparplayhost
|
||||||
|
|
||||||
|
@ -246,9 +96,9 @@ export async function startPlaying(db) {
|
||||||
io.emit('casparcg.status', currentStatus())
|
io.emit('casparcg.status', currentStatus())
|
||||||
logger.info('CasparCG: client is up and playing')
|
logger.info('CasparCG: client is up and playing')
|
||||||
/* console.log(connection)
|
/* console.log(connection)
|
||||||
for (let key in connection) {
|
for (var key in connection) {
|
||||||
console.log(key, '=', typeof(connection[key]))
|
console.log(key, '=', typeof(connection[key]))
|
||||||
}
|
} */
|
||||||
connection.autoConnect = false
|
connection.autoConnect = false
|
||||||
// connection.close()
|
// connection.close()
|
||||||
} else {
|
} else {
|
||||||
|
@ -256,4 +106,4 @@ export async function startPlaying(db) {
|
||||||
casparIsPlaying = false
|
casparIsPlaying = false
|
||||||
io.emit('casparcg.status', currentStatus(e))
|
io.emit('casparcg.status', currentStatus(e))
|
||||||
}
|
}
|
||||||
}*/
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { currentStatus } from './client.mjs'
|
import { currentStatus } from './client.mjs'
|
||||||
|
|
||||||
export async function casparStatus(ctx) {
|
export async function casparConnection(ctx) {
|
||||||
ctx.socket.emit('casparcg.status', currentStatus())
|
ctx.socket.emit('casparcg.status', currentStatus())
|
||||||
}
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
import nconf from 'nconf'
|
||||||
|
import { readFileSync } from 'fs'
|
||||||
|
|
||||||
|
// Helper method for global usage.
|
||||||
|
nconf.inTest = () => nconf.get('NODE_ENV') === 'test'
|
||||||
|
|
||||||
|
// Config follow the following priority check order:
|
||||||
|
// 1. Arguments
|
||||||
|
// 2. package.json
|
||||||
|
// 3. Enviroment variables
|
||||||
|
// 4. config/config.json
|
||||||
|
// 5. default settings
|
||||||
|
|
||||||
|
|
||||||
|
// Load arguments as highest priority
|
||||||
|
nconf.argv()
|
||||||
|
|
||||||
|
|
||||||
|
// Load package.json for name and such
|
||||||
|
let pckg = JSON.parse(readFileSync('./package.json'))
|
||||||
|
let project = {
|
||||||
|
name: pckg.name,
|
||||||
|
version: pckg.version,
|
||||||
|
description: pckg.description,
|
||||||
|
author: pckg.author,
|
||||||
|
license: pckg.license,
|
||||||
|
homepage: pckg.homepage,
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have global.it, there's a huge chance
|
||||||
|
// we're in test mode so we force node_env to be test.
|
||||||
|
if (typeof global.it === 'function') {
|
||||||
|
project.NODE_ENV = 'test'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Load overrides as second priority
|
||||||
|
nconf.overrides(project)
|
||||||
|
|
||||||
|
|
||||||
|
// Load enviroment variables as third priority
|
||||||
|
nconf.env()
|
||||||
|
|
||||||
|
|
||||||
|
// Load any overrides from the appropriate config file
|
||||||
|
let configFile = 'config/config.json'
|
||||||
|
|
||||||
|
if (nconf.get('NODE_ENV') === 'test') {
|
||||||
|
configFile = 'config/config.test.json'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
nconf.file('main', configFile)
|
||||||
|
|
||||||
|
// Load defaults
|
||||||
|
nconf.file('default', 'config/config.default.json')
|
||||||
|
|
||||||
|
|
||||||
|
// Final sanity checks
|
||||||
|
/* istanbul ignore if */
|
||||||
|
if (typeof global.it === 'function' & !nconf.inTest()) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('Critical: potentially running test on production enviroment. Shutting down.')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default nconf
|
|
@ -0,0 +1,162 @@
|
||||||
|
import lowdb from 'lowdb'
|
||||||
|
import FileAsync from 'lowdb/adapters/FileAsync.js'
|
||||||
|
import log from './log.mjs'
|
||||||
|
|
||||||
|
let lastId = -1
|
||||||
|
|
||||||
|
// Take from https://github.com/typicode/lodash-id/blob/master/src/index.js
|
||||||
|
// from package lodash-id
|
||||||
|
const lodashId = {
|
||||||
|
// Empties properties
|
||||||
|
__empty: function (doc) {
|
||||||
|
this.forEach(doc, function (value, key) {
|
||||||
|
delete doc[key]
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// Copies properties from an object to another
|
||||||
|
__update: function (dest, src) {
|
||||||
|
this.forEach(src, function (value, key) {
|
||||||
|
dest[key] = value
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// Removes an item from an array
|
||||||
|
__remove: function (array, item) {
|
||||||
|
var index = this.indexOf(array, item)
|
||||||
|
if (index !== -1) array.splice(index, 1)
|
||||||
|
},
|
||||||
|
|
||||||
|
__id: function () {
|
||||||
|
var id = this.id || 'id'
|
||||||
|
return id
|
||||||
|
},
|
||||||
|
|
||||||
|
getById: function (collection, id) {
|
||||||
|
var self = this
|
||||||
|
return this.find(collection, function (doc) {
|
||||||
|
if (self.has(doc, self.__id())) {
|
||||||
|
return doc[self.__id()] === id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
createId: function (collection, doc) {
|
||||||
|
let next = new Date().getTime()
|
||||||
|
if (next <= lastId) {
|
||||||
|
next = lastId + 1
|
||||||
|
}
|
||||||
|
lastId = next
|
||||||
|
return next
|
||||||
|
},
|
||||||
|
|
||||||
|
insert: function (collection, doc) {
|
||||||
|
doc[this.__id()] = doc[this.__id()] || this.createId(collection, doc)
|
||||||
|
var d = this.getById(collection, doc[this.__id()])
|
||||||
|
if (d) throw new Error('Insert failed, duplicate id')
|
||||||
|
collection.push(doc)
|
||||||
|
return doc
|
||||||
|
},
|
||||||
|
|
||||||
|
upsert: function (collection, doc) {
|
||||||
|
if (doc[this.__id()]) {
|
||||||
|
// id is set
|
||||||
|
var d = this.getById(collection, doc[this.__id()])
|
||||||
|
if (d) {
|
||||||
|
// replace properties of existing object
|
||||||
|
this.__empty(d)
|
||||||
|
this.assign(d, doc)
|
||||||
|
} else {
|
||||||
|
// push new object
|
||||||
|
collection.push(doc)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// create id and push new object
|
||||||
|
doc[this.__id()] = this.createId(collection, doc)
|
||||||
|
collection.push(doc)
|
||||||
|
}
|
||||||
|
|
||||||
|
return doc
|
||||||
|
},
|
||||||
|
|
||||||
|
updateById: function (collection, id, attrs) {
|
||||||
|
var doc = this.getById(collection, id)
|
||||||
|
|
||||||
|
if (doc) {
|
||||||
|
this.assign(doc, attrs, {id: doc.id})
|
||||||
|
}
|
||||||
|
|
||||||
|
return doc
|
||||||
|
},
|
||||||
|
|
||||||
|
updateWhere: function (collection, predicate, attrs) {
|
||||||
|
var self = this
|
||||||
|
var docs = this.filter(collection, predicate)
|
||||||
|
|
||||||
|
docs.forEach(function (doc) {
|
||||||
|
self.assign(doc, attrs, {id: doc.id})
|
||||||
|
})
|
||||||
|
|
||||||
|
return docs
|
||||||
|
},
|
||||||
|
|
||||||
|
replaceById: function (collection, id, attrs) {
|
||||||
|
var doc = this.getById(collection, id)
|
||||||
|
|
||||||
|
if (doc) {
|
||||||
|
var docId = doc.id
|
||||||
|
this.__empty(doc)
|
||||||
|
this.assign(doc, attrs, {id: docId})
|
||||||
|
}
|
||||||
|
|
||||||
|
return doc
|
||||||
|
},
|
||||||
|
|
||||||
|
removeById: function (collection, id) {
|
||||||
|
var doc = this.getById(collection, id)
|
||||||
|
|
||||||
|
this.__remove(collection, doc)
|
||||||
|
|
||||||
|
return doc
|
||||||
|
},
|
||||||
|
|
||||||
|
removeWhere: function (collection, predicate) {
|
||||||
|
var self = this
|
||||||
|
var docs = this.filter(collection, predicate)
|
||||||
|
|
||||||
|
docs.forEach(function (doc) {
|
||||||
|
self.__remove(collection, doc)
|
||||||
|
})
|
||||||
|
|
||||||
|
return docs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const adapter = new FileAsync('db.json')
|
||||||
|
|
||||||
|
export default function GetDB() {
|
||||||
|
return lowdb(adapter)
|
||||||
|
.then(function(db) {
|
||||||
|
db._.mixin(lodashId)
|
||||||
|
|
||||||
|
db.defaults({
|
||||||
|
graphics: [],
|
||||||
|
presets: [],
|
||||||
|
playing: [],
|
||||||
|
schedule: [],
|
||||||
|
settings: {
|
||||||
|
casparplayhost: 'localhost:3000',
|
||||||
|
casparhost: 'host.docker.internal',
|
||||||
|
},
|
||||||
|
version: 1,
|
||||||
|
trash: [],
|
||||||
|
})
|
||||||
|
.write()
|
||||||
|
.then(
|
||||||
|
function() { },
|
||||||
|
function(e) { log.error(e, 'Error writing defaults to lowdb') }
|
||||||
|
)
|
||||||
|
|
||||||
|
return db
|
||||||
|
})
|
||||||
|
}
|
|
@ -45,7 +45,7 @@ export async function create(ctx, data) {
|
||||||
await graphics.insert(data).write()
|
await graphics.insert(data).write()
|
||||||
let graphic = graphics.last().value()
|
let graphic = graphics.last().value()
|
||||||
|
|
||||||
ctx.io.emit('graphic.created', graphic)
|
ctx.io.emit('graphic.single', graphic)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -7,11 +7,7 @@ export function register(ctx, name, method) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.socket.on(name, async (data) => {
|
ctx.socket.on(name, async (data) => {
|
||||||
if (name.indexOf('list') > 0 || name.indexOf('all') || name.indexOf('total')) {
|
|
||||||
ctx.log.debug('Got event', name)
|
|
||||||
} else {
|
|
||||||
ctx.log.info('Got event', name)
|
ctx.log.info('Got event', name)
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await method(ctx, data)
|
await method(ctx, data)
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
import bunyan from 'bunyan-lite'
|
||||||
|
import defaults from './defaults.mjs'
|
||||||
|
import config from './config.mjs'
|
||||||
|
|
||||||
|
// Clone the settings as we will be touching
|
||||||
|
// on them slightly.
|
||||||
|
let settings = defaults(config.get('bunyan'), null)
|
||||||
|
|
||||||
|
// Replace any instance of 'process.stdout' with the
|
||||||
|
// actual reference to the process.stdout.
|
||||||
|
for (let i = 0; i < settings.streams.length; i++) {
|
||||||
|
if (settings.streams[i].stream === 'process.stdout') {
|
||||||
|
settings.streams[i].stream = process.stdout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create our logger.
|
||||||
|
const logger = bunyan.createLogger(settings)
|
||||||
|
|
||||||
|
export default logger
|
|
@ -1,6 +1,7 @@
|
||||||
|
import logger from './log.mjs'
|
||||||
import { register } from './io/helper.mjs'
|
import { register } from './io/helper.mjs'
|
||||||
import { contentConnection } from './content/connection.mjs'
|
import { contentConnection } from './content/connection.mjs'
|
||||||
import { casparStatus } from './casparcg/status.mjs'
|
import { casparConnection } from './casparcg/connection.mjs'
|
||||||
|
|
||||||
import * as content from './content/routes.mjs'
|
import * as content from './content/routes.mjs'
|
||||||
import * as engine from './engine/routes.mjs'
|
import * as engine from './engine/routes.mjs'
|
||||||
|
@ -9,7 +10,7 @@ import * as preset from './preset/routes.mjs'
|
||||||
import * as settings from './settings/routes.mjs'
|
import * as settings from './settings/routes.mjs'
|
||||||
import * as schedule from './schedule/routes.mjs'
|
import * as schedule from './schedule/routes.mjs'
|
||||||
|
|
||||||
function onConnection(server, db, logger, data) {
|
function onConnection(server, db, data) {
|
||||||
const io = server
|
const io = server
|
||||||
const socket = data
|
const socket = data
|
||||||
const log = logger.child({
|
const log = logger.child({
|
||||||
|
@ -19,7 +20,7 @@ function onConnection(server, db, logger, data) {
|
||||||
let ctx = { io, socket, log, db }
|
let ctx = { io, socket, log, db }
|
||||||
|
|
||||||
contentConnection(ctx)
|
contentConnection(ctx)
|
||||||
casparStatus(ctx)
|
casparConnection(ctx)
|
||||||
|
|
||||||
register(ctx, 'content', content)
|
register(ctx, 'content', content)
|
||||||
register(ctx, 'engine', engine)
|
register(ctx, 'engine', engine)
|
||||||
|
|
|
@ -1,53 +1,30 @@
|
||||||
import path from 'path'
|
|
||||||
import { fileURLToPath } from 'url'
|
|
||||||
import socket from 'socket.io-serveronly'
|
import socket from 'socket.io-serveronly'
|
||||||
import nStatic from 'node-static-lib'
|
import http from 'http'
|
||||||
|
import nStatic from 'node-static'
|
||||||
import * as casparcg from './casparcg/client.mjs'
|
import * as casparcg from './casparcg/client.mjs'
|
||||||
|
|
||||||
|
import lowdb from './db.mjs'
|
||||||
|
import config from './config.mjs'
|
||||||
|
import log from './log.mjs'
|
||||||
import onConnection from './routerio.mjs'
|
import onConnection from './routerio.mjs'
|
||||||
|
|
||||||
export function run(config, db, log, core, http, orgPort) {
|
|
||||||
log.info('Server: Opening database db.json')
|
log.info('Server: Opening database db.json')
|
||||||
|
|
||||||
db.defaults({
|
lowdb().then(function(db) {
|
||||||
graphics: [],
|
const fileServer = new nStatic.Server('./public')
|
||||||
presets: [],
|
|
||||||
playing: [],
|
|
||||||
schedule: [],
|
|
||||||
settings: {
|
|
||||||
casparplayhost: 'localhost:3000',
|
|
||||||
casparhost: 'localhost',
|
|
||||||
},
|
|
||||||
version: 1,
|
|
||||||
trash: [],
|
|
||||||
})
|
|
||||||
.write()
|
|
||||||
.then(
|
|
||||||
function() { },
|
|
||||||
function(e) { log.error(e, 'Error writing defaults to lowdb') }
|
|
||||||
)
|
|
||||||
|
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
||||||
const staticRoot = path.join(__dirname,'../public')
|
|
||||||
const fileServer = new nStatic.Server(staticRoot)
|
|
||||||
|
|
||||||
const server = http.createServer(function (req, res) {
|
const server = http.createServer(function (req, res) {
|
||||||
const child = log.child({})
|
const child = log.child({})
|
||||||
|
|
||||||
const d1 = new Date().getTime()
|
const d1 = new Date().getTime()
|
||||||
|
|
||||||
let isFinished = false
|
|
||||||
|
|
||||||
var done = function () {
|
var done = function () {
|
||||||
if (isFinished) return
|
|
||||||
isFinished = true
|
|
||||||
var requestTime = new Date().getTime() - d1
|
var requestTime = new Date().getTime() - d1
|
||||||
|
|
||||||
let level = 'debug'
|
let level = 'info'
|
||||||
if (res.statusCode >= 400) {
|
if (res.status >= 400) {
|
||||||
level = 'warn'
|
level = 'warn'
|
||||||
}
|
}
|
||||||
if (res.statusCode >= 500) {
|
if (res.status >= 500) {
|
||||||
level = 'error'
|
level = 'error'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,9 +45,7 @@ export function run(config, db, log, core, http, orgPort) {
|
||||||
|
|
||||||
fileServer.serve(req, res, function (err) {
|
fileServer.serve(req, res, function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
if (err.status !== 404) {
|
log.error(err);
|
||||||
log.error(err, req.url);
|
|
||||||
}
|
|
||||||
|
|
||||||
res.writeHead(err.status, err.headers);
|
res.writeHead(err.status, err.headers);
|
||||||
res.end(err.message);
|
res.end(err.message);
|
||||||
|
@ -80,20 +55,21 @@ export function run(config, db, log, core, http, orgPort) {
|
||||||
})
|
})
|
||||||
|
|
||||||
const io = new socket(server)
|
const io = new socket(server)
|
||||||
io.on('connection', onConnection.bind(this, io, db, log))
|
io.on('connection', onConnection.bind(this, io, db))
|
||||||
|
|
||||||
casparcg.initialise(log, db, io)
|
casparcg.initialise(log, db, io)
|
||||||
|
|
||||||
let port = orgPort || 3000
|
server.listen(config.get('server:port'), '0.0.0.0', function(err) {
|
||||||
|
|
||||||
return new Promise(function(resolve, reject) {
|
|
||||||
server.listen(port, '0.0.0.0', function(err) {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
return reject(err)
|
log.fatal(err)
|
||||||
|
return process.exit(2)
|
||||||
}
|
}
|
||||||
log.event.info(`Server is listening on ${port} serving files on ${staticRoot}`)
|
log.info(`Server is listening on ${config.get('server:port')}`)
|
||||||
log.info(`Server is listening on ${port} serving files on ${staticRoot}`)
|
|
||||||
resolve()
|
|
||||||
})
|
})
|
||||||
|
}, function(e) {
|
||||||
|
log.fatal(e, 'Critical error loading database')
|
||||||
|
process.exit(1)
|
||||||
|
}).catch(function(e) {
|
||||||
|
log.fatal(e, 'Critical error starting server')
|
||||||
|
process.exit(1)
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
|
@ -25,12 +25,12 @@ export async function update(ctx, data) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await ctx.db.set('settings.' + data.name, data.value).write()
|
await Settings.setValue(data.name, data.value)
|
||||||
|
|
||||||
let output = ctx.db.get('settings').value()
|
let output = await Settings.getSettings()
|
||||||
ctx.io.emit('settings.all', output)
|
ctx.io.emit('settings.all', output)
|
||||||
|
|
||||||
if (data.name.startsWith('caspar')) {
|
if (data.name === 'casparcg') {
|
||||||
connect()
|
connect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,18 @@
|
||||||
const m = require('mithril')
|
const m = require('mithril')
|
||||||
const createModule = require('../common/module')
|
const createModule = require('../common/module')
|
||||||
const Module = require('../module')
|
|
||||||
const components = require('../common/components')
|
const components = require('../common/components')
|
||||||
const socket = require('../../shared/socket')
|
const socket = require('../../shared/socket')
|
||||||
const store = require('../store')
|
const store = require('../store')
|
||||||
|
|
||||||
const Add = Module({
|
const Add = createModule({
|
||||||
init: function() {
|
init: function() {
|
||||||
this.engines = []
|
this.monitor('engines', 'engine.all', [])
|
||||||
this.graphic = { }
|
store.listen('graphic.single', data => {
|
||||||
this._socketOn(() => this.socketOpen())
|
if (data.name === this.graphic.name) {
|
||||||
},
|
m.route.set(`/graphic/${data.id}`)
|
||||||
|
|
||||||
socketOpen: function() {
|
|
||||||
socket.on('engine.all', (res) => {
|
|
||||||
this.engines = res
|
|
||||||
m.redraw()
|
|
||||||
})
|
|
||||||
|
|
||||||
socket.on('graphic.created', (res) => {
|
|
||||||
if (res.name === this.graphic.name) {
|
|
||||||
m.route.set(`/graphic/${res.id}`)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
this.graphic = { }
|
||||||
socket.emit('engine.all', {})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
updated: function(name, control) {
|
updated: function(name, control) {
|
||||||
|
@ -46,7 +34,7 @@ const Add = Module({
|
||||||
removing: function() {
|
removing: function() {
|
||||||
store.unlisten('graphic.single')
|
store.unlisten('graphic.single')
|
||||||
},
|
},
|
||||||
view: function() {
|
}, function() {
|
||||||
return [
|
return [
|
||||||
m('h4.header', 'Add graphic'),
|
m('h4.header', 'Add graphic'),
|
||||||
components.error(this.error),
|
components.error(this.error),
|
||||||
|
@ -65,6 +53,5 @@ const Add = Module({
|
||||||
onclick: () => this.create(),
|
onclick: () => this.create(),
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
},
|
|
||||||
})
|
})
|
||||||
module.exports = Add
|
module.exports = Add
|
||||||
|
|
|
@ -7,9 +7,10 @@ exports.error = function(error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.presetOnlyList = function(module, graphic, title, color = 'green', button = 'Display now', schedule = 'Schedule') {
|
exports.presetOnlyList = function(module, graphic, title, color = 'green', button = 'Display now', schedule = 'Schedule') {
|
||||||
return m('div', [
|
return [
|
||||||
m('label.graphic-label', title),
|
m('label.graphic-label', { key: 'first' }, title),
|
||||||
m('div.graphic-presetlist', {
|
m('div.graphic-presetlist', {
|
||||||
|
key: `second-${graphic.id}`,
|
||||||
oncreate: control => module.presetlistInit(control),
|
oncreate: control => module.presetlistInit(control),
|
||||||
},
|
},
|
||||||
module.presets.map(item =>
|
module.presets.map(item =>
|
||||||
|
@ -36,9 +37,10 @@ exports.presetOnlyList = function(module, graphic, title, color = 'green', butto
|
||||||
),
|
),
|
||||||
module.presets.length &&
|
module.presets.length &&
|
||||||
m('button.red.graphic-presetremove', {
|
m('button.red.graphic-presetremove', {
|
||||||
|
key: 'third',
|
||||||
onclick: () => (module.displayRemove = !module.displayRemove),
|
onclick: () => (module.displayRemove = !module.displayRemove),
|
||||||
}, 'Remove entries') || null,
|
}, 'Remove entries') || null,
|
||||||
])
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.presetButtons = function(module, green, blue) {
|
exports.presetButtons = function(module, green, blue) {
|
||||||
|
|
|
@ -105,7 +105,7 @@ exports.settings = function(module, graphic) {
|
||||||
m('div.graphic-property', [
|
m('div.graphic-property', [
|
||||||
m('input#graphic-newproperty[type=text]', {
|
m('input#graphic-newproperty[type=text]', {
|
||||||
value: module.newProperty,
|
value: module.newProperty,
|
||||||
oninput: (control) => { module.newProperty = control.target.value },
|
oninput: m.withAttr('value', val => (module.newProperty = val)),
|
||||||
}),
|
}),
|
||||||
m('button', {
|
m('button', {
|
||||||
onclick: module.addProperty.bind(module),
|
onclick: module.addProperty.bind(module),
|
||||||
|
|
|
@ -20,15 +20,13 @@ exports.view = function(module, graphic) {
|
||||||
return [
|
return [
|
||||||
m('div.graphic-presetadd', [
|
m('div.graphic-presetadd', [
|
||||||
m('h3.graphic-presetadd-header', 'Create preset/display graphic'),
|
m('h3.graphic-presetadd-header', 'Create preset/display graphic'),
|
||||||
m.fragment(
|
|
||||||
graphic.settings.properties.map((prop, index) => m.fragment({ key: `prop-${index}` }, [
|
graphic.settings.properties.map((prop, index) => m.fragment({ key: `prop-${index}` }, [
|
||||||
m('label', { for: `preset-add-${index}` }, prop),
|
m('label', { for: `preset-add-${index}` }, prop),
|
||||||
m(`input#preset-add-${index}[type=text]`, {
|
m(`input#preset-add-${index}[type=text]`, {
|
||||||
value: module.current[prop] || '',
|
value: module.current[prop] || '',
|
||||||
oninput: module.updated.bind(module, prop, 'current'),
|
oninput: module.updated.bind(module, prop, 'current'),
|
||||||
}),
|
}),
|
||||||
]))
|
])),
|
||||||
),
|
|
||||||
components.presetButtons(module, 'Display live now', 'Add to preset list'),
|
components.presetButtons(module, 'Display live now', 'Add to preset list'),
|
||||||
]),
|
]),
|
||||||
components.presetOnlyList(module, graphic, 'Presets'),
|
components.presetOnlyList(module, graphic, 'Presets'),
|
||||||
|
@ -107,7 +105,7 @@ exports.settings = function(module, graphic) {
|
||||||
m('div.graphic-property', [
|
m('div.graphic-property', [
|
||||||
m('input#graphic-newproperty[type=text]', {
|
m('input#graphic-newproperty[type=text]', {
|
||||||
value: module.newProperty,
|
value: module.newProperty,
|
||||||
oninput: (control) => { module.newProperty = control.target.value },
|
oninput: m.withAttr('value', val => (module.newProperty = val)),
|
||||||
}),
|
}),
|
||||||
m('button', {
|
m('button', {
|
||||||
onclick: module.addProperty.bind(module),
|
onclick: module.addProperty.bind(module),
|
||||||
|
|
|
@ -1,54 +1,20 @@
|
||||||
const m = require('mithril')
|
const m = require('mithril')
|
||||||
const Module = require('./module')
|
const createModule = require('./common/module')
|
||||||
// const createModule = require('./common/module')
|
|
||||||
const socket = require('../shared/socket')
|
const socket = require('../shared/socket')
|
||||||
|
|
||||||
const Menu = Module({
|
const Menu = createModule({
|
||||||
init: function() {
|
init: function() {
|
||||||
this.list = []
|
this.monitor('list', 'graphic.all', [])
|
||||||
this.settings = {}
|
this.monitor('settings', 'settings.all', {})
|
||||||
this.totalSchedule = 0
|
this.monitor('schedule', 'schedule.total', { total: 0 })
|
||||||
this.status = {
|
this.monitor('status', 'casparcg.status', {
|
||||||
connected: false,
|
connected: false,
|
||||||
playing: false,
|
playing: false,
|
||||||
error: '',
|
})
|
||||||
}
|
|
||||||
this._socketOn(() => this.socketOpen())
|
|
||||||
this.newHost = ''
|
this.newHost = ''
|
||||||
this.enableEdit = false
|
this.enableEdit = false
|
||||||
},
|
},
|
||||||
|
|
||||||
socketOpen: function() {
|
|
||||||
socket.on('graphic.all', (res) => {
|
|
||||||
this.list = res
|
|
||||||
m.redraw()
|
|
||||||
})
|
|
||||||
socket.on('graphic.created', (res) => {
|
|
||||||
this.list.push(res)
|
|
||||||
m.redraw()
|
|
||||||
})
|
|
||||||
|
|
||||||
this.on('settings.all', (res) => {
|
|
||||||
this.settings = res
|
|
||||||
m.redraw()
|
|
||||||
})
|
|
||||||
|
|
||||||
this.on('schedule.total', (res) => {
|
|
||||||
this.totalSchedule = res.total
|
|
||||||
m.redraw()
|
|
||||||
})
|
|
||||||
|
|
||||||
this.on('casparcg.status', (res) => {
|
|
||||||
this.status = res
|
|
||||||
m.redraw()
|
|
||||||
})
|
|
||||||
|
|
||||||
socket.emit('graphic.all', {})
|
|
||||||
socket.emit('settings.all', {})
|
|
||||||
socket.emit('schedule.total', {})
|
|
||||||
socket.emit('casparcg.status', {})
|
|
||||||
},
|
|
||||||
|
|
||||||
setHost(value) {
|
setHost(value) {
|
||||||
this.newHost = value
|
this.newHost = value
|
||||||
this.enableEdit = true
|
this.enableEdit = true
|
||||||
|
@ -63,22 +29,25 @@ const Menu = Module({
|
||||||
this.newHost = ''
|
this.newHost = ''
|
||||||
this.enableEdit = false
|
this.enableEdit = false
|
||||||
},
|
},
|
||||||
view: function() {
|
}, function() {
|
||||||
return [
|
return [
|
||||||
m(m.route.Link, {
|
m('a', {
|
||||||
href: '/',
|
href: '/',
|
||||||
|
oncreate: m.route.link,
|
||||||
class: m.route.get() === '/' && 'active' || '',
|
class: m.route.get() === '/' && 'active' || '',
|
||||||
}, `Schedule (${this.totalSchedule})` ),
|
}, `Schedule (${this.schedule.total})` ),
|
||||||
m('h4.header.header--space', 'Graphics'),
|
m('h4.header.header--space', 'Graphics'),
|
||||||
this.list.map((item) =>
|
this.list.map((item) =>
|
||||||
m(m.route.Link, {
|
m('a', {
|
||||||
href: `/graphic/${item.id}`,
|
href: `/graphic/${item.id}`,
|
||||||
|
oncreate: m.route.link,
|
||||||
class: m.route.get() === `/graphic/${item.id}` && 'active' || '',
|
class: m.route.get() === `/graphic/${item.id}` && 'active' || '',
|
||||||
}, item.name)
|
}, item.name)
|
||||||
),
|
),
|
||||||
m('h5.header.header--space', 'Other'),
|
m('h5.header.header--space', 'Other'),
|
||||||
m(m.route.Link, {
|
m('a', {
|
||||||
href: '/add',
|
href: '/add',
|
||||||
|
oncreate: m.route.link,
|
||||||
class: m.route.get() === '/add' && 'active' || '',
|
class: m.route.get() === '/add' && 'active' || '',
|
||||||
}, 'Add graphic' ),
|
}, 'Add graphic' ),
|
||||||
m('h5.header.header--space', 'CasparCG Status'),
|
m('h5.header.header--space', 'CasparCG Status'),
|
||||||
|
@ -96,8 +65,6 @@ const Menu = Module({
|
||||||
m('div.status', {
|
m('div.status', {
|
||||||
class: this.status.playing && 'green',
|
class: this.status.playing && 'green',
|
||||||
}, 'playing'),
|
}, 'playing'),
|
||||||
m('div.status-error', { hidden: !this.status.error }, this.status.error)
|
|
||||||
]
|
]
|
||||||
}
|
|
||||||
})
|
})
|
||||||
module.exports = Menu
|
module.exports = Menu
|
||||||
|
|
|
@ -1,81 +0,0 @@
|
||||||
const defaults = require('../shared/defaults')
|
|
||||||
const socket = require('../shared/socket')
|
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/4817029/whats-the-best-way-to-detect-a-touch-screen-device-using-javascript/4819886#4819886
|
|
||||||
// LOL
|
|
||||||
function is_touch_device() {
|
|
||||||
var prefixes = ' -webkit- -moz- -o- -ms- '.split(' ')
|
|
||||||
var mq = function(query) {
|
|
||||||
return window.matchMedia(query).matches
|
|
||||||
}
|
|
||||||
|
|
||||||
if (('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// include the 'heartz' as a way to have a non matching MQ to help terminate the join
|
|
||||||
// https://git.io/vznFH
|
|
||||||
var query = ['(', prefixes.join('touch-enabled),('), 'heartz', ')'].join('')
|
|
||||||
return mq(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = function Module(module) {
|
|
||||||
return defaults(module, {
|
|
||||||
init: function() {},
|
|
||||||
|
|
||||||
oninit: function(vnode) {
|
|
||||||
this._listeners = []
|
|
||||||
this.init(vnode)
|
|
||||||
},
|
|
||||||
|
|
||||||
_listeners: null,
|
|
||||||
|
|
||||||
_socketOn: function(cb) {
|
|
||||||
socket.on('connect', () => cb())
|
|
||||||
|
|
||||||
if (socket.connected) {
|
|
||||||
cb()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_initDragula: function(control, cb) {
|
|
||||||
let dragContainer = document.getElementById('dragcontainer')
|
|
||||||
let out = dragula([control.dom], {
|
|
||||||
mirrorContainer: dragContainer,
|
|
||||||
invalid: el => el.className !== 'graphic-preset-reorder'
|
|
||||||
&& el.className !== 'graphic-preset',
|
|
||||||
})
|
|
||||||
out.on('dragend', () => {
|
|
||||||
if (is_touch_device()) {
|
|
||||||
document.body.style.cssText = ''
|
|
||||||
window.scroll(0, document.body.data)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
out.on('drag', () => {
|
|
||||||
if (is_touch_device()) {
|
|
||||||
document.body.data = window.scrollY
|
|
||||||
document.body.style.cssText = `position: fixed; left: 0; right: 0; overflow: hidden; top: -${window.scrollY}px;`
|
|
||||||
dragContainer.style.marginTop = `${document.body.data}px`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
out.on('drop', (a, b, c, d) => {
|
|
||||||
cb(a, d)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
on: function(name, cb) {
|
|
||||||
this._listeners.push([name, cb])
|
|
||||||
socket.on(name, cb)
|
|
||||||
},
|
|
||||||
|
|
||||||
remove: function() {},
|
|
||||||
|
|
||||||
onremove: function() {
|
|
||||||
this.remove()
|
|
||||||
if (!this._listeners) return
|
|
||||||
for (let i = 0; i < this._listeners.length; i++) {
|
|
||||||
socket.removeListener(this._listeners[0], this._listeners[1])
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
|
|
||||||
// taken from isobject npm library
|
|
||||||
function isObject(val) {
|
|
||||||
return val != null && typeof val === 'object' && Array.isArray(val) === false
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = function defaults(options, def) {
|
|
||||||
let out = { }
|
|
||||||
|
|
||||||
if (options) {
|
|
||||||
Object.keys(options || {}).forEach(key => {
|
|
||||||
out[key] = options[key]
|
|
||||||
|
|
||||||
if (Array.isArray(out[key])) {
|
|
||||||
out[key] = out[key].map(item => {
|
|
||||||
if (isObject(item)) return defaults(item)
|
|
||||||
return item
|
|
||||||
})
|
|
||||||
} else if (out[key] && typeof out[key] === 'object') {
|
|
||||||
out[key] = defaults(options[key], def && def[key])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (def) {
|
|
||||||
Object.keys(def).forEach(function(key) {
|
|
||||||
if (typeof out[key] === 'undefined') {
|
|
||||||
out[key] = def[key]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return out
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
{
|
|
||||||
"name": "caspar-sup",
|
|
||||||
"serviceName": "Caspar Sup",
|
|
||||||
"description": "Caspar Sup manager",
|
|
||||||
"port": 3000,
|
|
||||||
"managePort": 3001,
|
|
||||||
"appRepository": null,
|
|
||||||
"manageRepository": null
|
|
||||||
}
|
|
12
index.mjs
12
index.mjs
|
@ -1,5 +1,9 @@
|
||||||
export function start(config, db, log, core, http, port) {
|
import log from './api/log.mjs'
|
||||||
return import('./api/server.mjs').then(function(module) {
|
|
||||||
return module.run(config, db, log, core, http, port)
|
// Run the database script automatically.
|
||||||
|
log.info('Starting server.')
|
||||||
|
|
||||||
|
import('./api/server.mjs').catch((error) => {
|
||||||
|
log.error(error, 'Error while starting server')
|
||||||
|
process.exit(1)
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
23
package.json
23
package.json
|
@ -9,10 +9,14 @@
|
||||||
"js:build:client": "asbundle app/client/index.js public/client.js",
|
"js:build:client": "asbundle app/client/index.js public/client.js",
|
||||||
"js:build:status": "asbundle app/status/index.js public/status.js",
|
"js:build:status": "asbundle app/status/index.js public/status.js",
|
||||||
"js:watch": "nodemon --watch app --exec \"npm run build\"",
|
"js:watch": "nodemon --watch app --exec \"npm run build\"",
|
||||||
"start:watch": "nodemon --watch api --watch runner.mjs --watch index.mjs runner.mjs | bunyan",
|
"start:watch": "nodemon --experimental-modules --watch api index.mjs | bunyan -o short",
|
||||||
"start": "node --experimental-modules index.mjs | bunyan -o short",
|
"start": "node --experimental-modules index.mjs | bunyan -o short",
|
||||||
|
"start:win": "node --experimental-modules index.mjs | bunyan -o short",
|
||||||
"dev": "run-p js:watch start:watch",
|
"dev": "run-p js:watch start:watch",
|
||||||
"build": "npm run js:build:main && npm run js:build:client && npm run js:build:status"
|
"build": "run-p js:build:main js:build:client js:build:status",
|
||||||
|
"docker": "docker run -it --rm --name my-running-script -p 3000:3000 -v \"%cd%\":/usr/src/app -w /usr/src/app node:alpine",
|
||||||
|
"docker:install": "npm run docker -- npm install",
|
||||||
|
"docker:dev": "npm run docker -- npm run dev"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -30,16 +34,21 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/nfp-projects/caspar-sup#readme",
|
"homepage": "https://github.com/nfp-projects/caspar-sup#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"bunyan-lite": "^1.0.1",
|
||||||
|
"casparcg-connection": "4.9.0",
|
||||||
"lodash": "^4.5.0",
|
"lodash": "^4.5.0",
|
||||||
"node-static-lib": "^1.0.0",
|
"lowdb": "^1.0.0",
|
||||||
"p3x-xml2json": "^2020.10.131",
|
"nconf": "^0.9.1",
|
||||||
"socket.io-serveronly": "^2.3.0"
|
"node-static": "^0.7.11",
|
||||||
|
"socket.io-serveronly": "^2.3.0",
|
||||||
|
"tslib": "^1.11.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"asbundle": "^2.6.1",
|
"asbundle": "^2.6.1",
|
||||||
"dragula": "^3.7.2",
|
"dragula": "^3.7.2",
|
||||||
"mithril": "^2.0.4",
|
"mithril": "^1.1.5",
|
||||||
|
"nodemon": "^2.0.2",
|
||||||
"npm-run-all": "^4.1.2",
|
"npm-run-all": "^4.1.2",
|
||||||
"service-core": "^2.0.0"
|
"run-p": "0.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,12 +149,6 @@ nav .status {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-left: 1.8em;
|
margin-left: 1.8em;
|
||||||
}
|
}
|
||||||
nav .status-error {
|
|
||||||
color: red;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
nav .status::after {
|
nav .status::after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|
10
runner.mjs
10
runner.mjs
|
@ -1,10 +0,0 @@
|
||||||
import ServiceCore from 'service-core'
|
|
||||||
import * as server from './index.mjs'
|
|
||||||
|
|
||||||
const serviceCore = new ServiceCore('sc-manager', import.meta.url)
|
|
||||||
|
|
||||||
serviceCore.init(server)
|
|
||||||
.then(function() {})
|
|
||||||
.catch(function(err) {
|
|
||||||
serviceCore.log.error(err, 'Unknown error starting server')
|
|
||||||
})
|
|
Loading…
Reference in New Issue