Refactor to be service-core compatible
This commit is contained in:
parent
1ac61de439
commit
671c2d177f
23 changed files with 570 additions and 451 deletions
|
@ -3,21 +3,36 @@ jobs:
|
||||||
build:
|
build:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/node:latest
|
- image: circleci/node:latest
|
||||||
working_directory: ~/caspar-sup
|
working_directory: ~/app
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- setup_remote_docker
|
|
||||||
- run:
|
- run:
|
||||||
name: Build docker image
|
name: Install npm deployment app
|
||||||
command: docker build -t nfpis/caspar-sup:build_${CIRCLE_BUILD_NUM} -t nfpis/caspar-sup:${CIRCLE_SHA1} -t nfpis/caspar-sup:latest .
|
command: sudo npm install -g github-release-cli
|
||||||
- deploy:
|
- run:
|
||||||
name: Push to docker
|
name: Build client javascript
|
||||||
command: |
|
command: |
|
||||||
docker login -u $DOCKER_USER -p $DOCKER_PASS
|
npm install
|
||||||
docker push nfpis/caspar-sup
|
npm run build
|
||||||
|
- deploy:
|
||||||
|
name: Create a release
|
||||||
|
command: |
|
||||||
|
PACKAGE_VERSION=$(cat package.json | grep version | head -1 | awk -F: '{ print $2 }' | sed 's/[", ]//g')
|
||||||
|
echo "Packaging to ${CIRCLE_PROJECT_REPONAME}_build-sc.zip"
|
||||||
|
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}" \
|
||||||
|
--name "v${PACKAGE_VERSION}.${CIRCLE_BUILD_NUM}" \
|
||||||
|
--body "Automatic CircleCI Build of v${PACKAGE_VERSION}.${CIRCLE_BUILD_NUM} from ${CIRCLE_SHA1}" \
|
||||||
|
caspar_sup-sc.zip
|
||||||
workflows:
|
workflows:
|
||||||
version: 2
|
version: 2
|
||||||
build_deploy:
|
build_deploy:
|
||||||
jobs:
|
jobs:
|
||||||
- build:
|
- build:
|
||||||
context: org-global
|
context: github-thething
|
||||||
|
|
|
@ -1,69 +1,219 @@
|
||||||
import CasparConnection from 'casparcg-connection'
|
import net from 'net'
|
||||||
|
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
|
||||||
|
|
||||||
export function initialise(log, db, socket) {
|
let queue = []
|
||||||
io = socket
|
let reconnectInterval = 1000
|
||||||
logger = log
|
let isReconnecting = false
|
||||||
db = db
|
let connected = false
|
||||||
|
let playing = false
|
||||||
|
let lastError = ''
|
||||||
|
|
||||||
connect(db)
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function connect(db) {
|
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
|
||||||
|
db = database
|
||||||
|
|
||||||
|
client = new net.Socket()
|
||||||
|
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) {
|
||||||
|
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
|
||||||
casparIsPlaying = false
|
lastError = 'CasparCG: Connecting to ' + currentHost + ':' + 5250
|
||||||
casparIsConnected = false
|
logger.info(lastError)
|
||||||
logger.info('CasparCG: Connectiong to', currentHost + ':' + 5250)
|
io.emit('casparcg.status', currentStatus())
|
||||||
|
|
||||||
connection = new CasparCG({
|
client.connect({
|
||||||
host: currentHost,
|
|
||||||
port: 5250,
|
port: 5250,
|
||||||
queueMode: 2,
|
host: currentHost
|
||||||
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: casparIsConnected,
|
connected: connected,
|
||||||
playing: casparIsPlaying,
|
playing: playing,
|
||||||
error: e,
|
error: lastError,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
@ -96,9 +246,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 (var key in connection) {
|
for (let 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 {
|
||||||
|
@ -106,4 +256,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 casparConnection(ctx) {
|
export async function casparStatus(ctx) {
|
||||||
ctx.socket.emit('casparcg.status', currentStatus())
|
ctx.socket.emit('casparcg.status', currentStatus())
|
||||||
}
|
}
|
|
@ -1,67 +0,0 @@
|
||||||
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
|
|
162
api/db.mjs
162
api/db.mjs
|
@ -1,162 +0,0 @@
|
||||||
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.single', graphic)
|
ctx.io.emit('graphic.created', graphic)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -7,7 +7,11 @@ export function register(ctx, name, method) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.socket.on(name, async (data) => {
|
ctx.socket.on(name, async (data) => {
|
||||||
ctx.log.info('Got event', name)
|
if (name.indexOf('list') > 0 || name.indexOf('all') || name.indexOf('total')) {
|
||||||
|
ctx.log.debug('Got event', name)
|
||||||
|
} else {
|
||||||
|
ctx.log.info('Got event', name)
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await method(ctx, data)
|
await method(ctx, data)
|
||||||
|
|
20
api/log.mjs
20
api/log.mjs
|
@ -1,20 +0,0 @@
|
||||||
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,7 +1,6 @@
|
||||||
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 { casparConnection } from './casparcg/connection.mjs'
|
import { casparStatus } from './casparcg/status.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'
|
||||||
|
@ -10,7 +9,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, data) {
|
function onConnection(server, db, logger, data) {
|
||||||
const io = server
|
const io = server
|
||||||
const socket = data
|
const socket = data
|
||||||
const log = logger.child({
|
const log = logger.child({
|
||||||
|
@ -20,7 +19,7 @@ function onConnection(server, db, data) {
|
||||||
let ctx = { io, socket, log, db }
|
let ctx = { io, socket, log, db }
|
||||||
|
|
||||||
contentConnection(ctx)
|
contentConnection(ctx)
|
||||||
casparConnection(ctx)
|
casparStatus(ctx)
|
||||||
|
|
||||||
register(ctx, 'content', content)
|
register(ctx, 'content', content)
|
||||||
register(ctx, 'engine', engine)
|
register(ctx, 'engine', engine)
|
||||||
|
|
|
@ -1,30 +1,53 @@
|
||||||
|
import path from 'path'
|
||||||
|
import { fileURLToPath } from 'url'
|
||||||
import socket from 'socket.io-serveronly'
|
import socket from 'socket.io-serveronly'
|
||||||
import http from 'http'
|
import nStatic from 'node-static-lib'
|
||||||
import nStatic from 'node-static'
|
|
||||||
import * as casparcg from './casparcg/client.mjs'
|
|
||||||
|
|
||||||
import lowdb from './db.mjs'
|
import * as casparcg from './casparcg/client.mjs'
|
||||||
import config from './config.mjs'
|
|
||||||
import log from './log.mjs'
|
|
||||||
import onConnection from './routerio.mjs'
|
import onConnection from './routerio.mjs'
|
||||||
|
|
||||||
log.info('Server: Opening database db.json')
|
export function run(config, db, log, core, http, orgPort) {
|
||||||
|
log.info('Server: Opening database db.json')
|
||||||
|
|
||||||
|
db.defaults({
|
||||||
|
graphics: [],
|
||||||
|
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)
|
||||||
|
|
||||||
lowdb().then(function(db) {
|
|
||||||
const fileServer = new nStatic.Server('./public')
|
|
||||||
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 = 'info'
|
let level = 'debug'
|
||||||
if (res.status >= 400) {
|
if (res.statusCode >= 400) {
|
||||||
level = 'warn'
|
level = 'warn'
|
||||||
}
|
}
|
||||||
if (res.status >= 500) {
|
if (res.statusCode >= 500) {
|
||||||
level = 'error'
|
level = 'error'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +68,9 @@ lowdb().then(function(db) {
|
||||||
|
|
||||||
fileServer.serve(req, res, function (err) {
|
fileServer.serve(req, res, function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
log.error(err);
|
if (err.status !== 404) {
|
||||||
|
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);
|
||||||
|
@ -55,21 +80,20 @@ lowdb().then(function(db) {
|
||||||
})
|
})
|
||||||
|
|
||||||
const io = new socket(server)
|
const io = new socket(server)
|
||||||
io.on('connection', onConnection.bind(this, io, db))
|
io.on('connection', onConnection.bind(this, io, db, log))
|
||||||
|
|
||||||
casparcg.initialise(log, db, io)
|
casparcg.initialise(log, db, io)
|
||||||
|
|
||||||
server.listen(config.get('server:port'), '0.0.0.0', function(err) {
|
let port = orgPort || 3000
|
||||||
if (err) {
|
|
||||||
log.fatal(err)
|
return new Promise(function(resolve, reject) {
|
||||||
return process.exit(2)
|
server.listen(port, '0.0.0.0', function(err) {
|
||||||
}
|
if (err) {
|
||||||
log.info(`Server is listening on ${config.get('server:port')}`)
|
return reject(err)
|
||||||
|
}
|
||||||
|
log.event.info(`Server is listening on ${port} serving files on ${staticRoot}`)
|
||||||
|
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 Settings.setValue(data.name, data.value)
|
await ctx.db.set('settings.' + data.name, data.value).write()
|
||||||
|
|
||||||
let output = await Settings.getSettings()
|
let output = ctx.db.get('settings').value()
|
||||||
ctx.io.emit('settings.all', output)
|
ctx.io.emit('settings.all', output)
|
||||||
|
|
||||||
if (data.name === 'casparcg') {
|
if (data.name.startsWith('caspar')) {
|
||||||
connect()
|
connect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,30 @@
|
||||||
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 = createModule({
|
const Add = Module({
|
||||||
init: function() {
|
init: function() {
|
||||||
this.monitor('engines', 'engine.all', [])
|
this.engines = []
|
||||||
store.listen('graphic.single', data => {
|
this.graphic = { }
|
||||||
if (data.name === this.graphic.name) {
|
this._socketOn(() => this.socketOpen())
|
||||||
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) {
|
||||||
|
@ -34,24 +46,25 @@ const Add = createModule({
|
||||||
removing: function() {
|
removing: function() {
|
||||||
store.unlisten('graphic.single')
|
store.unlisten('graphic.single')
|
||||||
},
|
},
|
||||||
}, function() {
|
view: function() {
|
||||||
return [
|
return [
|
||||||
m('h4.header', 'Add graphic'),
|
m('h4.header', 'Add graphic'),
|
||||||
components.error(this.error),
|
components.error(this.error),
|
||||||
m('label', { for: 'create-name' }, 'Name'),
|
m('label', { for: 'create-name' }, 'Name'),
|
||||||
m('input#create-name[type=text]', {
|
m('input#create-name[type=text]', {
|
||||||
oninput: (control) => this.updated('name', control),
|
oninput: (control) => this.updated('name', control),
|
||||||
}),
|
}),
|
||||||
m('label', { for: 'create-engine' }, 'Engine'),
|
m('label', { for: 'create-engine' }, 'Engine'),
|
||||||
m('select', {
|
m('select', {
|
||||||
onchange: (control) => this.updated('engine', control),
|
onchange: (control) => this.updated('engine', control),
|
||||||
}, this.engines.map(engine =>
|
}, this.engines.map(engine =>
|
||||||
m('option', { key: engine, value: engine }, engine)
|
m('option', { key: engine, value: engine }, engine)
|
||||||
)),
|
)),
|
||||||
m('input[type=submit]', {
|
m('input[type=submit]', {
|
||||||
value: 'Create',
|
value: 'Create',
|
||||||
onclick: () => this.create(),
|
onclick: () => this.create(),
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
|
},
|
||||||
})
|
})
|
||||||
module.exports = Add
|
module.exports = Add
|
||||||
|
|
|
@ -7,10 +7,9 @@ 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 [
|
return m('div', [
|
||||||
m('label.graphic-label', { key: 'first' }, title),
|
m('label.graphic-label', 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 =>
|
||||||
|
@ -37,10 +36,9 @@ 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: m.withAttr('value', val => (module.newProperty = val)),
|
oninput: (control) => { module.newProperty = control.target.value },
|
||||||
}),
|
}),
|
||||||
m('button', {
|
m('button', {
|
||||||
onclick: module.addProperty.bind(module),
|
onclick: module.addProperty.bind(module),
|
||||||
|
|
|
@ -20,13 +20,15 @@ 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'),
|
||||||
graphic.settings.properties.map((prop, index) => m.fragment({ key: `prop-${index}` }, [
|
m.fragment(
|
||||||
m('label', { for: `preset-add-${index}` }, prop),
|
graphic.settings.properties.map((prop, index) => m.fragment({ key: `prop-${index}` }, [
|
||||||
m(`input#preset-add-${index}[type=text]`, {
|
m('label', { for: `preset-add-${index}` }, prop),
|
||||||
value: module.current[prop] || '',
|
m(`input#preset-add-${index}[type=text]`, {
|
||||||
oninput: module.updated.bind(module, prop, 'current'),
|
value: module.current[prop] || '',
|
||||||
}),
|
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'),
|
||||||
|
@ -105,7 +107,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: m.withAttr('value', val => (module.newProperty = val)),
|
oninput: (control) => { module.newProperty = control.target.value },
|
||||||
}),
|
}),
|
||||||
m('button', {
|
m('button', {
|
||||||
onclick: module.addProperty.bind(module),
|
onclick: module.addProperty.bind(module),
|
||||||
|
|
121
app/main/menu.js
121
app/main/menu.js
|
@ -1,20 +1,54 @@
|
||||||
const m = require('mithril')
|
const m = require('mithril')
|
||||||
const createModule = require('./common/module')
|
const Module = require('./module')
|
||||||
|
// const createModule = require('./common/module')
|
||||||
const socket = require('../shared/socket')
|
const socket = require('../shared/socket')
|
||||||
|
|
||||||
const Menu = createModule({
|
const Menu = Module({
|
||||||
init: function() {
|
init: function() {
|
||||||
this.monitor('list', 'graphic.all', [])
|
this.list = []
|
||||||
this.monitor('settings', 'settings.all', {})
|
this.settings = {}
|
||||||
this.monitor('schedule', 'schedule.total', { total: 0 })
|
this.totalSchedule = 0
|
||||||
this.monitor('status', 'casparcg.status', {
|
this.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
|
||||||
|
@ -29,42 +63,41 @@ const Menu = createModule({
|
||||||
this.newHost = ''
|
this.newHost = ''
|
||||||
this.enableEdit = false
|
this.enableEdit = false
|
||||||
},
|
},
|
||||||
}, function() {
|
view: function() {
|
||||||
return [
|
return [
|
||||||
m('a', {
|
m(m.route.Link, {
|
||||||
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}`,
|
class: m.route.get() === `/graphic/${item.id}` && 'active' || '',
|
||||||
oncreate: m.route.link,
|
}, item.name)
|
||||||
class: m.route.get() === `/graphic/${item.id}` && 'active' || '',
|
),
|
||||||
}, item.name)
|
m('h5.header.header--space', 'Other'),
|
||||||
),
|
m(m.route.Link, {
|
||||||
m('h5.header.header--space', 'Other'),
|
href: '/add',
|
||||||
m('a', {
|
class: m.route.get() === '/add' && 'active' || '',
|
||||||
href: '/add',
|
}, 'Add graphic' ),
|
||||||
oncreate: m.route.link,
|
m('h5.header.header--space', 'CasparCG Status'),
|
||||||
class: m.route.get() === '/add' && 'active' || '',
|
m('input[type=text]', {
|
||||||
}, 'Add graphic' ),
|
placeholder: 'Host IP',
|
||||||
m('h5.header.header--space', 'CasparCG Status'),
|
value: this.newHost || this.settings.casparhost || '',
|
||||||
m('input[type=text]', {
|
oninput: control => this.setHost(control.target.value),
|
||||||
placeholder: 'Host IP',
|
}),
|
||||||
value: this.newHost || this.settings.casparhost || '',
|
this.enableEdit && m('button', {
|
||||||
oninput: control => this.setHost(control.target.value),
|
onclick: () => this.saveNewHost(),
|
||||||
}),
|
}, 'Connect'),
|
||||||
this.enableEdit && m('button', {
|
m('div.status', {
|
||||||
onclick: () => this.saveNewHost(),
|
class: this.status.connected && 'green',
|
||||||
}, 'Connect'),
|
}, 'connected'),
|
||||||
m('div.status', {
|
m('div.status', {
|
||||||
class: this.status.connected && 'green',
|
class: this.status.playing && 'green',
|
||||||
}, 'connected'),
|
}, 'playing'),
|
||||||
m('div.status', {
|
m('div.status-error', { hidden: !this.status.error }, this.status.error)
|
||||||
class: this.status.playing && 'green',
|
]
|
||||||
}, 'playing'),
|
}
|
||||||
]
|
|
||||||
})
|
})
|
||||||
module.exports = Menu
|
module.exports = Menu
|
||||||
|
|
81
app/main/module.js
Normal file
81
app/main/module.js
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
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])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
34
app/shared/defaults.js
Normal file
34
app/shared/defaults.js
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
9
config.json
Normal file
9
config.json
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"name": "caspar-sup",
|
||||||
|
"serviceName": "Caspar Sup",
|
||||||
|
"description": "Caspar Sup manager",
|
||||||
|
"port": 3000,
|
||||||
|
"managePort": 3001,
|
||||||
|
"appRepository": null,
|
||||||
|
"manageRepository": null
|
||||||
|
}
|
14
index.mjs
14
index.mjs
|
@ -1,9 +1,5 @@
|
||||||
import log from './api/log.mjs'
|
export function start(config, db, log, core, http, port) {
|
||||||
|
return import('./api/server.mjs').then(function(module) {
|
||||||
// Run the database script automatically.
|
return module.run(config, db, log, core, http, port)
|
||||||
log.info('Starting server.')
|
})
|
||||||
|
}
|
||||||
import('./api/server.mjs').catch((error) => {
|
|
||||||
log.error(error, 'Error while starting server')
|
|
||||||
process.exit(1)
|
|
||||||
})
|
|
||||||
|
|
22
package.json
22
package.json
|
@ -9,13 +9,10 @@
|
||||||
"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 --experimental-modules --watch api index.mjs | bunyan -o short",
|
"start:watch": "nodemon --watch api --watch runner.mjs --watch index.mjs runner.mjs | bunyan",
|
||||||
"start": "node --experimental-modules index.mjs | bunyan -o short",
|
"start": "node --experimental-modules index.mjs | bunyan -o short",
|
||||||
"dev": "run-p js:watch start:watch",
|
"dev": "run-p js:watch start:watch",
|
||||||
"build": "run-p js:build:main js:build:client js:build:status",
|
"build": "npm run js:build:main && npm run js:build:client && npm run 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",
|
||||||
|
@ -33,19 +30,16 @@
|
||||||
},
|
},
|
||||||
"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",
|
||||||
"lowdb": "^1.0.0",
|
"node-static-lib": "^1.0.0",
|
||||||
"nconf": "^0.9.1",
|
"p3x-xml2json": "^2020.10.131",
|
||||||
"node-static": "^0.7.11",
|
"socket.io-serveronly": "^2.3.0"
|
||||||
"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": "^1.1.5",
|
"mithril": "^2.0.4",
|
||||||
"npm-run-all": "^4.1.2"
|
"npm-run-all": "^4.1.2",
|
||||||
|
"service-core": "^2.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,6 +149,12 @@ 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
Normal file
10
runner.mjs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
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 a new issue