refactored everything to use lowdb instead of sqlite
This commit is contained in:
parent
c1f87628d4
commit
27871c9ed4
53 changed files with 5339 additions and 1163 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -33,12 +33,6 @@ node_modules
|
|||
.node_repl_history
|
||||
|
||||
db.sqlite
|
||||
public/client.css
|
||||
public/client.css.map
|
||||
public/client.js
|
||||
public/main.css
|
||||
public/main.css.map
|
||||
public/main.js
|
||||
public/status.css
|
||||
public/status.css.map
|
||||
public/status.js
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import _ from 'lodash'
|
||||
import knex from 'knex'
|
||||
import bookshelf from 'bookshelf'
|
||||
|
||||
import config from '../config'
|
||||
import log from '../log'
|
||||
import defaults from './defaults.mjs'
|
||||
import config from './config.mjs'
|
||||
import log from './log.mjs'
|
||||
|
||||
let host = config.get('knex:connection')
|
||||
|
||||
|
@ -28,7 +28,7 @@ let shelf = bookshelf(client)
|
|||
// Helper method to create models
|
||||
shelf.createModel = (attr, opts) => {
|
||||
// Create default attributes to all models
|
||||
let attributes = _.defaults(attr, {
|
||||
let attributes = defaults(attr, {
|
||||
/**
|
||||
* Initialize a new instance of model. This does not get called when
|
||||
* relations to this model is being fetched though.
|
||||
|
@ -55,7 +55,7 @@ shelf.createModel = (attr, opts) => {
|
|||
})
|
||||
|
||||
// Create default options for all models
|
||||
let options = _.defaults(opts, {
|
||||
let options = defaults(opts, {
|
||||
/**
|
||||
* Create new model object in database.
|
||||
*
|
|
@ -1,5 +1,7 @@
|
|||
import Settings from '../settings/model'
|
||||
import { CasparCG, AMCP } from 'casparcg-connection'
|
||||
import CasparConnection from 'casparcg-connection'
|
||||
|
||||
const CasparCG = CasparConnection.CasparCG
|
||||
const AMCP = CasparConnection.AMCP
|
||||
|
||||
const timeoutDuration = 5000
|
||||
|
||||
|
@ -11,23 +13,20 @@ let casparIsPlaying
|
|||
let casparIsConnected
|
||||
let currentHost
|
||||
|
||||
export async function initialise(log, socket) {
|
||||
export function initialise(log, db, socket) {
|
||||
io = socket.socket
|
||||
logger = log
|
||||
db = db
|
||||
|
||||
return connect()
|
||||
connect(db)
|
||||
}
|
||||
|
||||
export async function connect() {
|
||||
currentHost = await Settings.getValue('casparcg')
|
||||
export function connect(db) {
|
||||
currentHost = db.get('settings').value().casparhost
|
||||
casparIsPlaying = false
|
||||
casparIsConnected = false
|
||||
logger.info('CasparCG: Connectiong to', currentHost + ':' + 5250)
|
||||
|
||||
if (connection && connection.close) {
|
||||
await connection.close()
|
||||
}
|
||||
|
||||
connection = new CasparCG({
|
||||
host: currentHost,
|
||||
port: 5250,
|
||||
|
@ -37,6 +36,7 @@ export async function connect() {
|
|||
logger.error(err, 'CasparCG: Error')
|
||||
},
|
||||
onConnectionStatus: data => {
|
||||
if (casparIsPlaying) return
|
||||
casparIsConnected = data.connected
|
||||
|
||||
if (!casparIsConnected) {
|
||||
|
@ -45,9 +45,10 @@ export async function connect() {
|
|||
}
|
||||
},
|
||||
onConnected: async connected => {
|
||||
if (casparIsPlaying) return
|
||||
logger.info('CasparCG: connected', connected)
|
||||
if (!casparIsPlaying) {
|
||||
startPlaying().then()
|
||||
startPlaying(db).then()
|
||||
} else {
|
||||
logger.warn('CasparCG: Stopped from starting play again.')
|
||||
}
|
||||
|
@ -63,8 +64,8 @@ export function currentStatus(e) {
|
|||
}
|
||||
}
|
||||
|
||||
export async function startPlaying() {
|
||||
let ip = 'localhost'
|
||||
export async function startPlaying(db) {
|
||||
let ip = db.get('settings').value().casparplayhost
|
||||
|
||||
// Check if we lost connection while attempting to start playing
|
||||
if (!connection.connected) {
|
||||
|
@ -75,7 +76,7 @@ export async function startPlaying() {
|
|||
|
||||
try {
|
||||
// Send a play command
|
||||
let command = `PLAY 1-100 [HTML] "http://${ip}:3000/client.html" CUT 1 LINEAR RIGHT`
|
||||
let command = `PLAY 1-100 [HTML] "http://${ip}/client.html" CUT 1 LINEAR RIGHT`
|
||||
logger.info(`CasparCG Command: ${command}`)
|
||||
await connection.do(new AMCP.CustomCommand(command))
|
||||
success = true
|
||||
|
@ -94,6 +95,12 @@ export async function startPlaying() {
|
|||
// We are playing, notify all clients
|
||||
io.emit('casparcg.status', currentStatus())
|
||||
logger.info('CasparCG: client is up and playing')
|
||||
/* console.log(connection)
|
||||
for (var key in connection) {
|
||||
console.log(key, '=', typeof(connection[key]))
|
||||
} */
|
||||
connection.autoConnect = false
|
||||
// connection.close()
|
||||
} else {
|
||||
// Unknown error occured
|
||||
casparIsPlaying = false
|
|
@ -1,4 +1,4 @@
|
|||
import { currentStatus } from './client'
|
||||
import { currentStatus } from './client.mjs'
|
||||
|
||||
export async function casparConnection(ctx) {
|
||||
ctx.socket.emit('casparcg.status', currentStatus())
|
|
@ -1,6 +1,5 @@
|
|||
import _ from 'lodash'
|
||||
import nconf from 'nconf'
|
||||
const pckg = require('./package.json')
|
||||
import { readFileSync } from 'fs'
|
||||
|
||||
// Helper method for global usage.
|
||||
nconf.inTest = () => nconf.get('NODE_ENV') === 'test'
|
||||
|
@ -18,8 +17,15 @@ nconf.argv()
|
|||
|
||||
|
||||
// Load package.json for name and such
|
||||
let project = _.pick(pckg, ['name', 'version', 'description', 'author', 'license', 'homepage'])
|
||||
|
||||
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.
|
||||
|
@ -57,4 +63,5 @@ if (typeof global.it === 'function' & !nconf.inTest()) {
|
|||
console.log('Critical: potentially running test on production enviroment. Shutting down.')
|
||||
process.exit(1)
|
||||
}
|
||||
module.exports = nconf
|
||||
|
||||
export default nconf
|
|
@ -1,4 +1,4 @@
|
|||
import { reset, list } from './routes'
|
||||
import { reset, list } from './routes.mjs'
|
||||
|
||||
export async function contentConnection(ctx) {
|
||||
ctx.log.info('Got new socket connection')
|
|
@ -1,4 +1,4 @@
|
|||
import bookshelf from '../bookshelf'
|
||||
import bookshelf from '../bookshelf.mjs'
|
||||
|
||||
/* Content model:
|
||||
{
|
|
@ -1,5 +1,4 @@
|
|||
import _ from 'lodash'
|
||||
import Content from './model'
|
||||
import template from 'lodash.template'
|
||||
|
||||
export const active = { }
|
||||
|
||||
|
@ -14,13 +13,17 @@ function getSocket(ctx, all) {
|
|||
* Display a specific graphic content
|
||||
*/
|
||||
export async function display(ctx, data) {
|
||||
let compiled = _.template(data.graphic.settings.html)
|
||||
let compiled = template(data.graphic.settings.html)
|
||||
let html = compiled(data.data)
|
||||
|
||||
let old = await Content.getSingle(data.graphic.name)
|
||||
// let old = await Content.getSingle(data.graphic.name)
|
||||
|
||||
let playing = ctx.db.get('playing')
|
||||
|
||||
let old = playing.find({ name: data.graphic.name }).value()
|
||||
|
||||
if (old) {
|
||||
await old.destroy()
|
||||
await playing.removeById(old.id).write()
|
||||
}
|
||||
|
||||
let payload = {
|
||||
|
@ -32,9 +35,9 @@ export async function display(ctx, data) {
|
|||
is_deleted: false,
|
||||
}
|
||||
|
||||
let content = await Content.create(payload)
|
||||
await playing.insert(payload).write()
|
||||
|
||||
ctx.io.emit('client.display', content.toJSON())
|
||||
ctx.io.emit('client.display', playing.find({ name: data.graphic.name }).value())
|
||||
|
||||
list(ctx, true)
|
||||
}
|
||||
|
@ -45,11 +48,13 @@ export async function display(ctx, data) {
|
|||
* Hide a specific graphic content
|
||||
*/
|
||||
export async function hide(ctx, data) {
|
||||
let content = await Content.getSingle(data.name)
|
||||
let playing = ctx.db.get('playing')
|
||||
|
||||
if (!content) return
|
||||
let old = playing.find({ name: data.name }).value()
|
||||
|
||||
await content.destroy()
|
||||
if (!old) return
|
||||
|
||||
await playing.removeById(old.id).write()
|
||||
|
||||
ctx.io.emit('client.hide', {
|
||||
name: data.name,
|
||||
|
@ -63,7 +68,7 @@ function generateDisplayText(item) {
|
|||
// return `${item.data.text} - ${item.data.finished}`
|
||||
// }
|
||||
try {
|
||||
return _.template(item.graphic.settings.main)(item.data)
|
||||
return template(item.graphic.settings.main)(item.data)
|
||||
} catch (e) {
|
||||
return `Error creating display: ${e.message}`
|
||||
}
|
||||
|
@ -76,12 +81,14 @@ function generateDisplayText(item) {
|
|||
* Send a name list of all active graphics
|
||||
*/
|
||||
export async function list(ctx, all) {
|
||||
let allContent = await Content.getAll()
|
||||
let allContent = ctx.db.get('playing').value()
|
||||
|
||||
let payload = await Promise.all(allContent.map(item => ({
|
||||
name: item.get('name'),
|
||||
display: generateDisplayText(item.toJSON()),
|
||||
})))
|
||||
let payload = await Promise.all(allContent.map(function(item) {
|
||||
return {
|
||||
name: item.name,
|
||||
display: generateDisplayText(item),
|
||||
}
|
||||
}))
|
||||
|
||||
getSocket(ctx, all).emit('content.list', payload)
|
||||
}
|
||||
|
@ -93,7 +100,7 @@ export async function list(ctx, all) {
|
|||
* Send actual graphics of all active graphics
|
||||
*/
|
||||
export async function reset(ctx) {
|
||||
let allContent = await Content.getAll()
|
||||
let allContent = ctx.db.get('playing').value()
|
||||
|
||||
ctx.socket.emit('client.reset', allContent.toJSON())
|
||||
ctx.socket.emit('client.reset', allContent)
|
||||
}
|
162
api/db.mjs
Normal file
162
api/db.mjs
Normal file
|
@ -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
|
||||
})
|
||||
}
|
34
api/defaults.mjs
Normal file
34
api/defaults.mjs
Normal file
|
@ -0,0 +1,34 @@
|
|||
|
||||
// taken from isobject npm library
|
||||
function isObject(val) {
|
||||
return val != null && typeof val === 'object' && Array.isArray(val) === false
|
||||
}
|
||||
|
||||
export default 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,4 +1,4 @@
|
|||
import bookshelf from '../bookshelf'
|
||||
import bookshelf from '../bookshelf.mjs'
|
||||
|
||||
/* Graphic model:
|
||||
{
|
|
@ -1,14 +1,12 @@
|
|||
import Graphic from './model'
|
||||
|
||||
/*
|
||||
* Event: 'graphic.all'
|
||||
*
|
||||
* Request all graphics in store
|
||||
*/
|
||||
export async function all(ctx) {
|
||||
let data = await Graphic.getAll()
|
||||
let data = ctx.db.get('graphics').value()
|
||||
|
||||
ctx.socket.emit('graphic.all', data.toJSON())
|
||||
ctx.socket.emit('graphic.all', data)
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -22,9 +20,9 @@ export async function single(ctx, data) {
|
|||
return
|
||||
}
|
||||
|
||||
let graphic = await Graphic.getSingle(data.id)
|
||||
let graphic = ctx.db.get('graphics').getById(Number(data.id)).value()
|
||||
|
||||
ctx.socket.emit('graphic.single', graphic.toJSON())
|
||||
ctx.socket.emit('graphic.single', graphic)
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -37,16 +35,17 @@ export async function single(ctx, data) {
|
|||
*/
|
||||
export async function create(ctx, data) {
|
||||
data.settings = {}
|
||||
data.is_deleted = false
|
||||
|
||||
if (data.engine === 'countdown') {
|
||||
data.settings.html = `<span id="${data.name}-countdown-timer">countdown appears here</span>`
|
||||
data.settings.main = '<%- text %> - <%- finished %>'
|
||||
}
|
||||
|
||||
let graphic = await Graphic.create(data)
|
||||
|
||||
ctx.io.emit('graphic.single', graphic.toJSON())
|
||||
let graphics = ctx.db.get('graphics').insert(data)
|
||||
let graphic = graphics.last().value()
|
||||
await graphics.write()
|
||||
|
||||
ctx.io.emit('graphic.single', graphic)
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -62,12 +61,16 @@ export async function remove(ctx, data) {
|
|||
return
|
||||
}
|
||||
|
||||
let graphic = await Graphic.getSingle(data.id)
|
||||
graphic.set({ is_deleted: true })
|
||||
await graphic.save()
|
||||
let graphics = ctx.db.get('graphics')
|
||||
let graphic = graphics.removeById(Number(data.id)).value()
|
||||
await graphics.write()
|
||||
|
||||
let output = await Graphic.getAll()
|
||||
ctx.io.emit('graphic.all', output.toJSON())
|
||||
graphic.deleted_at = new Date().getTime()
|
||||
graphic.type = 'graphic'
|
||||
|
||||
await ctx.db.get('trash').insert(graphic).write()
|
||||
|
||||
ctx.io.emit('graphic.all', graphics)
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -86,11 +89,9 @@ export async function update(ctx, data) {
|
|||
return
|
||||
}
|
||||
|
||||
let graphic = await Graphic.getSingle(data.id)
|
||||
await ctx.db.get('graphics').updateById(Number(data.id), data).write()
|
||||
|
||||
graphic.set(data)
|
||||
let graphic = ctx.db.get('graphics').getById(Number(data.id)).value()
|
||||
|
||||
await graphic.save()
|
||||
|
||||
ctx.io.emit('graphic.single', graphic.toJSON())
|
||||
ctx.io.emit('graphic.single', graphic)
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
import _ from 'lodash'
|
||||
|
||||
export function register(ctx, name, method) {
|
||||
if (_.isPlainObject(method)) {
|
||||
if (typeof(method) === 'object') {
|
||||
Object.keys(method).forEach(key => {
|
||||
register(ctx, [name, key].join('.'), method[key])
|
||||
})
|
|
@ -1,10 +1,10 @@
|
|||
import _ from 'lodash'
|
||||
import bunyan from 'bunyan'
|
||||
import config from './config'
|
||||
import defaults from './defaults.mjs'
|
||||
import config from './config.mjs'
|
||||
|
||||
// Clone the settings as we will be touching
|
||||
// on them slightly.
|
||||
let settings = _.cloneDeep(config.get('bunyan'))
|
||||
let settings = defaults(config.get('bunyan'), null)
|
||||
|
||||
// Replace any instance of 'process.stdout' with the
|
||||
// actual reference to the process.stdout.
|
|
@ -1,4 +1,4 @@
|
|||
import bookshelf from '../bookshelf'
|
||||
import bookshelf from '../bookshelf.mjs'
|
||||
|
||||
/* Preset model:
|
||||
{
|
|
@ -1,50 +0,0 @@
|
|||
import Preset from './model'
|
||||
|
||||
export async function all(ctx, payload) {
|
||||
let id = Number(payload.graphic_id || payload.id)
|
||||
|
||||
let data = await Preset.getAll({ graphic_id: id }, [], 'sort')
|
||||
|
||||
ctx.io.emit(`preset.all:${id}`, data.toJSON())
|
||||
}
|
||||
|
||||
export async function add(ctx, payload) {
|
||||
payload.is_deleted = false
|
||||
payload.sort = 1
|
||||
|
||||
let last = await Preset.query(q => {
|
||||
q.where({ graphic_id: payload.graphic_id })
|
||||
q.orderBy('sort', 'desc')
|
||||
q.limit(1)
|
||||
}).fetch({ require: false })
|
||||
|
||||
if (last) {
|
||||
payload.sort = last.get('sort') + 1
|
||||
}
|
||||
|
||||
await Preset.create(payload)
|
||||
|
||||
await all(ctx, payload)
|
||||
}
|
||||
|
||||
export async function patch(ctx, payload) {
|
||||
await Promise.all(payload.map(async item => {
|
||||
let preset = await Preset.getSingle(item.id)
|
||||
|
||||
preset.set({ sort: item.sort })
|
||||
|
||||
await preset.save()
|
||||
}))
|
||||
|
||||
await all(ctx, payload[0])
|
||||
}
|
||||
|
||||
export async function remove(ctx, payload) {
|
||||
let preset = await Preset.getSingle(payload.id)
|
||||
|
||||
preset.set('is_deleted', true)
|
||||
|
||||
await preset.save()
|
||||
|
||||
await all(ctx, payload)
|
||||
}
|
50
api/preset/routes.mjs
Normal file
50
api/preset/routes.mjs
Normal file
|
@ -0,0 +1,50 @@
|
|||
export async function all(ctx, payload) {
|
||||
let id = Number(payload.graphic_id || payload.id)
|
||||
|
||||
let data = ctx.db.get('presets').filter({ graphic_id: id }).value()
|
||||
|
||||
ctx.io.emit(`preset.all:${id}`, data || [])
|
||||
}
|
||||
|
||||
export async function add(ctx, payload) {
|
||||
payload.sort = 1
|
||||
|
||||
let presets = ctx.db.get('presets')
|
||||
|
||||
let last = presets.sortBy('sort').last().value()
|
||||
|
||||
if (last) {
|
||||
payload.sort = last.sort + 1
|
||||
}
|
||||
|
||||
payload.graphic_id = Number(payload.graphic_id)
|
||||
|
||||
await presets.insert(payload).write()
|
||||
|
||||
await all(ctx, payload)
|
||||
}
|
||||
|
||||
export async function patch(ctx, payload) {
|
||||
let presets = ctx.db.get('presets')
|
||||
|
||||
payload.forEach(function(item) {
|
||||
presets.updateById(Number(item.id), { sort: item.sort })
|
||||
})
|
||||
|
||||
await presets.write()
|
||||
|
||||
await all(ctx, payload[0])
|
||||
}
|
||||
|
||||
export async function remove(ctx, payload) {
|
||||
let presets = ctx.db.get('presets')
|
||||
let preset = presets.removeById(Number(payload.id)).value()
|
||||
await presets.write()
|
||||
|
||||
preset.deleted_at = new Date().getTime()
|
||||
preset.type = 'preset'
|
||||
|
||||
await ctx.db.get('trash').insert(preset).write()
|
||||
|
||||
await all(ctx, payload)
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
import logger from '../log'
|
||||
import { register } from './io/helper'
|
||||
import { contentConnection } from './content/connection'
|
||||
import { casparConnection } from './casparcg/connection'
|
||||
|
||||
import * as content from './content/routes'
|
||||
import * as engine from './engine/routes'
|
||||
import * as graphic from './graphic/routes'
|
||||
import * as preset from './preset/routes'
|
||||
import * as settings from './settings/routes'
|
||||
import * as schedule from './schedule/routes'
|
||||
|
||||
function onConnection(server, data) {
|
||||
const io = server.socket
|
||||
const socket = data.socket
|
||||
const log = logger.child({
|
||||
id: socket.id,
|
||||
})
|
||||
|
||||
let ctx = { io, socket, log }
|
||||
|
||||
contentConnection(ctx)
|
||||
casparConnection(ctx)
|
||||
|
||||
register(ctx, 'content', content)
|
||||
register(ctx, 'engine', engine)
|
||||
register(ctx, 'graphic', graphic)
|
||||
register(ctx, 'preset', preset)
|
||||
register(ctx, 'settings', settings)
|
||||
register(ctx, 'schedule', schedule)
|
||||
}
|
||||
|
||||
export default onConnection
|
33
api/routerio.mjs
Normal file
33
api/routerio.mjs
Normal file
|
@ -0,0 +1,33 @@
|
|||
import logger from './log.mjs'
|
||||
import { register } from './io/helper.mjs'
|
||||
import { contentConnection } from './content/connection.mjs'
|
||||
import { casparConnection } from './casparcg/connection.mjs'
|
||||
|
||||
import * as content from './content/routes.mjs'
|
||||
import * as engine from './engine/routes.mjs'
|
||||
import * as graphic from './graphic/routes.mjs'
|
||||
import * as preset from './preset/routes.mjs'
|
||||
import * as settings from './settings/routes.mjs'
|
||||
import * as schedule from './schedule/routes.mjs'
|
||||
|
||||
function onConnection(server, db, data) {
|
||||
const io = server.socket
|
||||
const socket = data.socket
|
||||
const log = logger.child({
|
||||
id: socket.id,
|
||||
})
|
||||
|
||||
let ctx = { io, socket, log, db }
|
||||
|
||||
contentConnection(ctx)
|
||||
casparConnection(ctx)
|
||||
|
||||
register(ctx, 'content', content)
|
||||
register(ctx, 'engine', engine)
|
||||
register(ctx, 'graphic', graphic)
|
||||
register(ctx, 'preset', preset)
|
||||
register(ctx, 'settings', settings)
|
||||
register(ctx, 'schedule', schedule)
|
||||
}
|
||||
|
||||
export default onConnection
|
|
@ -1,5 +1,5 @@
|
|||
import bookshelf from '../bookshelf'
|
||||
import Graphic from '../graphic/model'
|
||||
import bookshelf from '../bookshelf.mjs'
|
||||
import Graphic from '../graphic/model.mjs'
|
||||
|
||||
/* Schedule model:
|
||||
{
|
|
@ -1,32 +1,34 @@
|
|||
import Schedule from './model'
|
||||
import Schedule from './model.mjs'
|
||||
|
||||
export async function all(ctx) {
|
||||
let data = await Schedule.getAll({ }, ['graphic'], 'sort')
|
||||
let graphics = ctx.db.get('graphics')
|
||||
let data = ctx.db.get('schedule').forEach(function(s) {
|
||||
s.graphic = graphics.getById(s.graphic_id).value()
|
||||
}).sortBy('sort').value()
|
||||
// let data = await Schedule.getAll({ }, ['graphic'], 'sort')
|
||||
|
||||
ctx.io.emit('schedule.all', data.toJSON())
|
||||
ctx.io.emit('schedule.all', data)
|
||||
total(ctx)
|
||||
}
|
||||
|
||||
export async function total(ctx) {
|
||||
let data = await Schedule.getAll({ }, ['graphic'], 'sort')
|
||||
let data = ctx.db.get('schedule').size()
|
||||
|
||||
ctx.io.emit('schedule.total', { total: data.length })
|
||||
ctx.io.emit('schedule.total', { total: data })
|
||||
}
|
||||
|
||||
export async function add(ctx, payload) {
|
||||
payload.is_deleted = false
|
||||
payload.sort = 1
|
||||
|
||||
let last = await Schedule.query(q => {
|
||||
q.orderBy('sort', 'desc')
|
||||
q.limit(1)
|
||||
}).fetch({ require: false })
|
||||
let schedule = ctx.db.get('schedule')
|
||||
|
||||
let last = schedule.sortBy('sort').last().value()
|
||||
|
||||
if (last) {
|
||||
payload.sort = last.get('sort') + 1
|
||||
payload.sort = last.sort + 1
|
||||
}
|
||||
|
||||
await Schedule.create(payload)
|
||||
await schedule.insert(payload).write()
|
||||
|
||||
await all(ctx)
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
import Koa from 'koa'
|
||||
import serve from 'koa-better-serve'
|
||||
import socket from 'koa-socket'
|
||||
import * as casparcg from './casparcg/client'
|
||||
|
||||
import config from '../config'
|
||||
import log from '../log'
|
||||
import onConnection from './routerio'
|
||||
import { bunyanLogger, errorHandler } from './middlewares'
|
||||
|
||||
const app = new Koa()
|
||||
const io = new socket()
|
||||
|
||||
io.attach(app)
|
||||
|
||||
io.on('connection', onConnection.bind(this, io))
|
||||
|
||||
casparcg.initialise(log, io).catch(e => {
|
||||
log.error(e, 'Critical error initialising casparcg')
|
||||
})
|
||||
|
||||
app.use(bunyanLogger(log))
|
||||
app.use(errorHandler())
|
||||
app.use(async (ctx, next) => {
|
||||
if (ctx.url === '/') {
|
||||
return ctx.redirect('/index.html')
|
||||
}
|
||||
await next()
|
||||
})
|
||||
app.use(serve('./public', ''))
|
||||
|
||||
app.listen(config.get('server:port'), err => {
|
||||
if (err) return log.critical(err)
|
||||
log.info(`Server is listening on ${config.get('server:port')}`)
|
||||
})
|
44
api/server.mjs
Normal file
44
api/server.mjs
Normal file
|
@ -0,0 +1,44 @@
|
|||
import Koa from 'koa'
|
||||
import serve from 'koa-better-serve'
|
||||
import socket from 'koa-socket'
|
||||
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 { bunyanLogger, errorHandler } from './middlewares.mjs'
|
||||
|
||||
log.info('Server: Opening database db.json')
|
||||
|
||||
lowdb().then(function(db) {
|
||||
const app = new Koa()
|
||||
const io = new socket()
|
||||
|
||||
io.attach(app)
|
||||
|
||||
io.on('connection', onConnection.bind(this, io, db))
|
||||
|
||||
casparcg.initialise(log, db, io)
|
||||
|
||||
app.use(bunyanLogger(log))
|
||||
app.use(errorHandler())
|
||||
app.use(async (ctx, next) => {
|
||||
if (ctx.url === '/') {
|
||||
return ctx.redirect('/index.html')
|
||||
}
|
||||
await next()
|
||||
})
|
||||
app.use(serve('./public', ''))
|
||||
|
||||
app.listen(config.get('server:port'), err => {
|
||||
if (err) return log.fatal(err)
|
||||
log.info(`Server is listening on ${config.get('server:port')}`)
|
||||
})
|
||||
}, 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)
|
||||
})
|
|
@ -1,51 +0,0 @@
|
|||
import bookshelf from '../bookshelf'
|
||||
|
||||
/* Settings model:
|
||||
{
|
||||
id,
|
||||
name,
|
||||
value,
|
||||
}
|
||||
*/
|
||||
|
||||
const Settings = bookshelf.createModel({
|
||||
tableName: 'settings',
|
||||
}, {
|
||||
getValue(name) {
|
||||
return this.query({ where: { name: name } })
|
||||
.fetch({ require: false })
|
||||
.then(item => item && item.get('value') || item)
|
||||
},
|
||||
|
||||
setValue(name, value) {
|
||||
return this.query({ where: { name } })
|
||||
.fetch({ require: false })
|
||||
.then(item => {
|
||||
if (item) {
|
||||
item.set({ value })
|
||||
return item.save()
|
||||
}
|
||||
return this.create({
|
||||
name,
|
||||
value,
|
||||
is_deleted: false,
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
getSettings() {
|
||||
return this.query({ where: { }})
|
||||
.fetchAll({ })
|
||||
.then(data => {
|
||||
let out = { }
|
||||
|
||||
data.forEach(item => {
|
||||
out[item.get('name')] = item.get('value')
|
||||
})
|
||||
|
||||
return out
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
export default Settings
|
|
@ -1,5 +1,4 @@
|
|||
import Settings from './model'
|
||||
import { connect } from '../casparcg/client'
|
||||
import { connect } from '../casparcg/client.mjs'
|
||||
|
||||
/*
|
||||
* Event: 'settings.all'
|
||||
|
@ -7,7 +6,7 @@ import { connect } from '../casparcg/client'
|
|||
* Request all settings in store
|
||||
*/
|
||||
export async function all(ctx) {
|
||||
let data = await Settings.getSettings()
|
||||
let data = ctx.db.get('settings').value()
|
||||
|
||||
ctx.socket.emit('settings.all', data)
|
||||
}
|
27
api/setup.mjs
Normal file
27
api/setup.mjs
Normal file
|
@ -0,0 +1,27 @@
|
|||
import knex from 'knex'
|
||||
import defaults from './defaults.mjs'
|
||||
import config from './config.mjs'
|
||||
import log from './log.mjs'
|
||||
|
||||
// This is important for setup to run cleanly.
|
||||
let knexConfig = defaults(config.get('knex'), null) // Clone
|
||||
knexConfig.pool = { min: 1, max: 1 }
|
||||
|
||||
let knexSetup = knex(knexConfig)
|
||||
|
||||
export default function setup() {
|
||||
log.info(knexConfig, 'Running database integrity scan.')
|
||||
|
||||
return knexSetup.migrate.latest({
|
||||
directory: './migrations',
|
||||
})
|
||||
.then((result) => {
|
||||
if (result[1].length === 0) {
|
||||
return log.info('Database is up to date')
|
||||
}
|
||||
for (let i = 0; i < result[1].length; i++) {
|
||||
log.info('Applied migration from', result[1][i].substr(result[1][i].lastIndexOf('\\') + 1))
|
||||
}
|
||||
return knexSetup.destroy()
|
||||
})
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
var socket = require('../socket')
|
||||
var socket = require('../shared/socket')
|
||||
|
||||
var engines = {
|
||||
text: require('./text'),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
const m = require('mithril')
|
||||
const createModule = require('../common/module')
|
||||
const components = require('../common/components')
|
||||
const socket = require('../../socket')
|
||||
const socket = require('../../shared/socket')
|
||||
const store = require('../store')
|
||||
|
||||
const Add = createModule({
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
const m = require('mithril')
|
||||
const _ = require('lodash')
|
||||
const store = require('../store')
|
||||
const socket = require('../../socket')
|
||||
const socket = require('../../shared/socket')
|
||||
const dragula = require('dragula')
|
||||
|
||||
function createModule(component, view) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
const _ = require('lodash')
|
||||
const m = require('mithril')
|
||||
const createModule = require('../common/module')
|
||||
const socket = require('../../socket')
|
||||
const socket = require('../../shared/socket')
|
||||
|
||||
const Dagskra = createModule({
|
||||
init: function() {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
const _ = require('lodash')
|
||||
const m = require('mithril')
|
||||
const createModule = require('../common/module')
|
||||
const socket = require('../../socket')
|
||||
const socket = require('../../shared/socket')
|
||||
const view = require('./view')
|
||||
const dragula = require('dragula')
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const m = require('mithril')
|
||||
const createModule = require('./common/module')
|
||||
const socket = require('../socket')
|
||||
const socket = require('../shared/socket')
|
||||
|
||||
const Header = createModule({
|
||||
init: function() {
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
//in the console.
|
||||
window.components = {}
|
||||
|
||||
require('../socket')
|
||||
require('../shared/socket')
|
||||
require('./store')
|
||||
|
||||
const m = require('mithril')
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const m = require('mithril')
|
||||
const createModule = require('./common/module')
|
||||
const socket = require('../socket')
|
||||
const socket = require('../shared/socket')
|
||||
|
||||
const Menu = createModule({
|
||||
init: function() {
|
||||
|
@ -22,7 +22,7 @@ const Menu = createModule({
|
|||
|
||||
saveNewHost() {
|
||||
socket.emit('settings.update', {
|
||||
name: 'casparcg',
|
||||
name: 'casparhost',
|
||||
value: this.newHost,
|
||||
})
|
||||
|
||||
|
@ -53,7 +53,7 @@ const Menu = createModule({
|
|||
m('h5.header.header--space', 'CasparCG Status'),
|
||||
m('input[type=text]', {
|
||||
placeholder: 'Host IP',
|
||||
value: this.newHost || this.settings.casparcg || '',
|
||||
value: this.newHost || this.settings.casparhost || '',
|
||||
oninput: control => this.setHost(control.target.value),
|
||||
}),
|
||||
this.enableEdit && m('button', {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const _ = require('lodash')
|
||||
const socket = require('../socket')
|
||||
const socket = require('../shared/socket')
|
||||
const storage = {}
|
||||
const events = {}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const socket = require('../socket')
|
||||
const socket = require('../shared/socket')
|
||||
const m = require('mithril')
|
||||
|
||||
const Status = {
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
* {
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing:border-box; /* This sets all elements to be the actual set dimensions, disregarding padding and borders */
|
||||
/* -webkit-backface-visibility: hidden; */ /* Hide the backface of elements - useful for 3d effects */
|
||||
-webkit-transition: translate3d(0,0,0); /* Turns on hardware acceleration - not known to be of benefit in CCG, but won't hurt */
|
||||
}
|
||||
html, body {
|
||||
width:1920px; /* Set to your channel's resolution */
|
||||
height:1080px; /* Set to your channel's resolution */
|
||||
margin:0; /* Use all available space */
|
||||
padding:0; /* Use all available space */
|
||||
background:transparent; /* The HTML consumer actually makes your background transparent by default, unless a color or image is specified - but this might be usefull when debugging in browsers */
|
||||
overflow:hidden; /* Hide any overflowing elements - to disable scollbars */
|
||||
-webkit-font-smoothing:antialiased !important; /* Set aliasing of fonts - possible options: none, antialiased, subpixel-antialiased */
|
||||
}
|
||||
body {
|
||||
font-family: Calibri,Arial;
|
||||
font-size: 40px;
|
||||
color: #FFFFFF;
|
||||
/* -webkit-text-stroke-width: 0.5px;
|
||||
-webkit-text-stroke-color: #888888;
|
||||
text-shadow: 2px 2px 1px #000000; */
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial;
|
||||
font-weight: normal;
|
||||
/* text-shadow: 0px 0px 0px #000000; */
|
||||
font-size: 22pt;
|
||||
}
|
||||
html {
|
||||
overflow: auto;
|
||||
}
|
||||
body > div
|
||||
{
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.root-element {
|
||||
opacity: 0;
|
||||
transition: opacity 1s;
|
||||
}
|
||||
|
||||
.root-element-display {
|
||||
opacity: 1;
|
||||
transition: opacity 1s;
|
||||
}
|
|
@ -1,566 +0,0 @@
|
|||
/* http://meyerweb.com/eric/tools/css/reset/
|
||||
v2.0 | 20110126
|
||||
License: none (public domain)
|
||||
*/
|
||||
|
||||
html, body, div, span, applet, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
a, abbr, acronym, address, big, cite, code,
|
||||
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||
small, strike, strong, sub, sup, tt, var,
|
||||
b, u, i, center,
|
||||
dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||
article, aside, canvas, details, embed,
|
||||
figure, figcaption, footer, header, hgroup,
|
||||
menu, nav, output, ruby, section, summary,
|
||||
time, mark, audio, video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article, aside, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section {
|
||||
display: block;
|
||||
}
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
ol, ul {
|
||||
list-style: none;
|
||||
}
|
||||
blockquote, q {
|
||||
quotes: none;
|
||||
}
|
||||
blockquote:before, blockquote:after,
|
||||
q:before, q:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
input {
|
||||
font-size: 16px;
|
||||
-webkit-appearance: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
outline: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #3f3f41;
|
||||
color: #f1f1f1;
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
flex-direction: column;
|
||||
font-family: Helvetica, sans-serif, Arial;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
|
||||
/* Components */
|
||||
|
||||
button {
|
||||
border: none;
|
||||
color: #f1f1f1;
|
||||
background: #2199e8;
|
||||
font-size: 0.6em;
|
||||
height: 3em;
|
||||
|
||||
&.green {
|
||||
background: #3adb78;
|
||||
}
|
||||
|
||||
&.red {
|
||||
background: #ec5840;
|
||||
}
|
||||
}
|
||||
|
||||
.error-box {
|
||||
margin: 1rem 0rem 2rem 0;
|
||||
padding: 1rem;
|
||||
background: #FF0000;
|
||||
color: white;
|
||||
font-size: 0.7em;
|
||||
line-height: 1em;
|
||||
}
|
||||
|
||||
$header-size = 0.8em;
|
||||
$header-color = #777777;
|
||||
|
||||
/* Container */
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
flex-grow: 2;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
section.current {
|
||||
padding: 0 13px;
|
||||
background: black;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
z-index: 10;
|
||||
|
||||
h4 {
|
||||
color: $header-color;
|
||||
font-size: 0.7em;
|
||||
padding: 0.2em;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1em;
|
||||
line-height: 2em;
|
||||
color: #eb6e00;
|
||||
flex-grow: 2;
|
||||
height: 2em;
|
||||
padding-right: 0.5em;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 80px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.disconnected {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0,0,0,0.8);
|
||||
color: white;
|
||||
font-size: 1em;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Menu */
|
||||
|
||||
nav {
|
||||
width: 200px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
padding: 10px;
|
||||
background: #2d2d30;
|
||||
text-align: center;
|
||||
|
||||
.header {
|
||||
color: $header-color;
|
||||
font-size: $header-size;
|
||||
margin-bottom: 10px;
|
||||
|
||||
&--space {
|
||||
margin-top: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
font-size: $header-size;
|
||||
line-height: 2.6em;
|
||||
display: block;
|
||||
border: 4px solid #2d2d30;
|
||||
background: #007acc;
|
||||
color: white;
|
||||
|
||||
&.active {
|
||||
background: transparent;
|
||||
border: 4px solid #007acc;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border: 4px solid #007acc;
|
||||
}
|
||||
}
|
||||
|
||||
input[type=text] {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.status {
|
||||
padding: 5px 20px;
|
||||
font-size: 0.8em;
|
||||
color: $header-color;
|
||||
text-align: left;
|
||||
position: relative;
|
||||
margin-left: 1.8em;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: calc(50% - 5px);
|
||||
content: '';
|
||||
border: 6px solid #ec5840;
|
||||
}
|
||||
|
||||
&.green::after {
|
||||
border-color: #008000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Main */
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
padding: 10px 1em;
|
||||
flex-grow: 2;
|
||||
width: 300px;
|
||||
|
||||
.header {
|
||||
color: $header-color;
|
||||
font-size: $header-size;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Inputs */
|
||||
|
||||
label {
|
||||
margin-top: 0.6em;
|
||||
color: #f1f1f1;
|
||||
font-size: 0.7em;
|
||||
|
||||
& a,
|
||||
& a:hover,
|
||||
& a:visited {
|
||||
color: #aaa;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
input[type='text'],
|
||||
textarea,
|
||||
select {
|
||||
font-size: 0.6em;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
background: #333337;
|
||||
border: 1px solid #2d2d30;
|
||||
color: #999999;
|
||||
transition-property: none !important;
|
||||
outline: none;
|
||||
|
||||
&:hover {
|
||||
color: #f1f1f1;
|
||||
border-color: #007acc;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background: #333337;
|
||||
color: #f1f1f1;
|
||||
border-color: #007acc;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
textarea#graphic-html {
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
textarea#graphic-css {
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
input[type=submit] {
|
||||
margin-top: 0.6em;
|
||||
border: none;
|
||||
color: #f1f1f1;
|
||||
background: #2199e8;
|
||||
font-size: 0.6em;
|
||||
line-height: 3em;
|
||||
}
|
||||
|
||||
input[readonly],
|
||||
input[readonly]:hover {
|
||||
background: #2d2d30 !important;
|
||||
border-color: #3f3f3f;
|
||||
}
|
||||
|
||||
select {
|
||||
height: 2.5em;
|
||||
-webkit-appearance: none;
|
||||
border-radius: 0;
|
||||
background-position: right center;
|
||||
background-size: 9px 6px;
|
||||
background-origin: content-box;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='32' height='24' viewBox='0 0 32 24'><polygon points='0,0 32,0 16,24' style='fill: rgb%28138, 138, 138%29'></polygon></svg>")
|
||||
}
|
||||
|
||||
select:hover {
|
||||
color: #f1f1f1;
|
||||
border-color: #007acc;
|
||||
}
|
||||
|
||||
select:focus {
|
||||
background: #333337;
|
||||
color: #f1f1f1;
|
||||
border-color: #007acc;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
a.button {
|
||||
margin: 0 1rem 0 0;
|
||||
width: 7rem;
|
||||
}
|
||||
|
||||
/* Graphic */
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
|
||||
h3 {
|
||||
font-size: 1em;
|
||||
flex-grow: 2;
|
||||
border-bottom: 1px solid #2d2d30;
|
||||
padding-top: 10px;
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
button {
|
||||
border: 0;
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
.graphic {
|
||||
&-presetlist {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
&-presetadd {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
border: 1px solid #2d2d30;
|
||||
margin: 30px 0 10px;
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
|
||||
&-header {
|
||||
background: #3f3f41;
|
||||
position: absolute;
|
||||
top: -1.3em;
|
||||
left: 10px;
|
||||
font-size: 0.8em;
|
||||
padding: 0.8em 10px;
|
||||
}
|
||||
|
||||
&-buttons {
|
||||
display: flex;
|
||||
margin-top: 10px;
|
||||
|
||||
& button {
|
||||
margin-right: 10px;
|
||||
width: 150px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-presetremove {
|
||||
align-self: center;
|
||||
margin-top: 50px;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
&-empty {
|
||||
font-size: 0.7em;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
&-delete {
|
||||
align-self: center;
|
||||
margin-top: 30px;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
&-label {
|
||||
margin-top: 30px;
|
||||
padding-bottom: 0.5em;
|
||||
}
|
||||
|
||||
&-helper {
|
||||
font-size: 0.7em;
|
||||
color: #999;
|
||||
margin: 5px 0 0;
|
||||
|
||||
&.bottom {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-property,
|
||||
&-preset {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
|
||||
&-reorder {
|
||||
width: 62px;
|
||||
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NTc3MiwgMjAxNC8wMS8xMy0xOTo0NDowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODQ5NDcyMzY3MTU5MTFFOEEwQjVFQUFCMEYzRDE2QjciIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODQ5NDcyMzc3MTU5MTFFOEEwQjVFQUFCMEYzRDE2QjciPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4NDk0NzIzNDcxNTkxMUU4QTBCNUVBQUIwRjNEMTZCNyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4NDk0NzIzNTcxNTkxMUU4QTBCNUVBQUIwRjNEMTZCNyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pla8e2wAAABFUExURXd3d////3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d0brzhMAAAAWdFJOUwAAN0NEVXJzdHV2d3h/iImKmaq73e5w9SzPAAAAlElEQVRIx+XT2wqDMBRE0Witt2piW53//9SCoETMwJkXkWY/CgtNBp0jDa/086JgwgNeIwEghpFVAMFOJoCaNNkFMNlIJBImRcrPodJ8ybQ/I285B7mLSCWX+/qL3EVTznK5T/mUu++P7OVyX/9U9T30sLyliUeobR8WmcZ6lnYTrf34HRX8xlbTaZfcA726yziyXX5Joj0NgTfsUQAAAABJRU5ErkJggg==') no-repeat transparent;
|
||||
background-size: 25px;
|
||||
background-position: center;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
& input {
|
||||
flex-grow: 2;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
& button {
|
||||
width: 100px;
|
||||
border: 1px solid #3f3f41;
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
|
||||
&-preset {
|
||||
& input {
|
||||
padding-top: 1.5em;
|
||||
padding-bottom: 1.5em;
|
||||
}
|
||||
|
||||
& button {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.schedule {
|
||||
&-empty {
|
||||
margin-top: 2em;
|
||||
font-size: 1em;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.settings {
|
||||
&-empty {
|
||||
text-align: center;
|
||||
margin: 50px 0 30px;
|
||||
font-size: 0.8em;
|
||||
color: #999;
|
||||
|
||||
&-button {
|
||||
align-self: center;
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Dragula */
|
||||
@css {
|
||||
#dragcontainer {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.gu-mirror {
|
||||
position: absolute !important;
|
||||
margin: 0 !important;
|
||||
z-index: 9999 !important;
|
||||
opacity: 0.8;
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)";
|
||||
filter: alpha(opacity=80);
|
||||
}
|
||||
.gu-hide {
|
||||
display: none !important;
|
||||
}
|
||||
.gu-unselectable {
|
||||
-webkit-user-select: none !important;
|
||||
-moz-user-select: none !important;
|
||||
-ms-user-select: none !important;
|
||||
user-select: none !important;
|
||||
}
|
||||
.gu-transit {
|
||||
opacity: 0.2;
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=20)";
|
||||
filter: alpha(opacity=20);
|
||||
}
|
||||
}
|
||||
|
||||
/* Media queries */
|
||||
|
||||
body {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
@media only screen and (max-device-width: 600px) {
|
||||
#container {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
nav {
|
||||
width: auto;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
|
||||
.header {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
a {
|
||||
width: calc(50% - 8px);
|
||||
}
|
||||
|
||||
input[type=text] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.status {
|
||||
align-self: center;
|
||||
width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
#content {
|
||||
width: auto;
|
||||
}
|
||||
}
|
|
@ -1,168 +0,0 @@
|
|||
/* http://meyerweb.com/eric/tools/css/reset/
|
||||
v2.0 | 20110126
|
||||
License: none (public domain)
|
||||
*/
|
||||
|
||||
html, body, div, span, applet, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
a, abbr, acronym, address, big, cite, code,
|
||||
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||
small, strike, strong, sub, sup, tt, var,
|
||||
b, u, i, center,
|
||||
dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||
article, aside, canvas, details, embed,
|
||||
figure, figcaption, footer, header, hgroup,
|
||||
menu, nav, output, ruby, section, summary,
|
||||
time, mark, audio, video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article, aside, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section {
|
||||
display: block;
|
||||
}
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
ol, ul {
|
||||
list-style: none;
|
||||
}
|
||||
blockquote, q {
|
||||
quotes: none;
|
||||
}
|
||||
blockquote:before, blockquote:after,
|
||||
q:before, q:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
input {
|
||||
font-size: 16px;
|
||||
-webkit-appearance: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
outline: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #3f3f41;
|
||||
color: #eb6e00;
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
flex-direction: column;
|
||||
font-family: Helvetica, sans-serif, Arial;
|
||||
}
|
||||
|
||||
$header-size = 2.3em;
|
||||
|
||||
#container {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
flex-grow: 2;
|
||||
|
||||
h3 {
|
||||
background: black;
|
||||
font-size: $header-size;
|
||||
line-height: ($header-size);
|
||||
color: #eb6e00;
|
||||
height: ($header-size);
|
||||
overflow: hidden;
|
||||
padding: 0 0.3em;
|
||||
flex-grow: 2;
|
||||
border-radius: 5px 0 0 5px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
button {
|
||||
border: none;
|
||||
color: black;
|
||||
background: #eb6e00;
|
||||
font-size: 2em;
|
||||
width: 80px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 0 5px 5px 0;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.empty {
|
||||
flex-grow: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #ccc;
|
||||
font-size: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
margin-bottom: 5px;
|
||||
color: #ccc;
|
||||
|
||||
h2 {
|
||||
font-size: 3em;
|
||||
line-height: 64px;
|
||||
padding: 0 0.3em;
|
||||
flex-grow: 2;
|
||||
}
|
||||
|
||||
.status {
|
||||
padding: 0 20px 0 30px;
|
||||
line-height: 64px;
|
||||
font-size: 2em;
|
||||
color: $header-color;
|
||||
text-align: left;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: calc(50% - 7px);
|
||||
content: '';
|
||||
border: 10px solid #ec5840;
|
||||
}
|
||||
|
||||
&.green::after {
|
||||
border-color: #00FF00;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.disconnected {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0,0,0,0.8);
|
||||
color: white;
|
||||
font-size: 3em;
|
||||
display: flex;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
187
db.json
Normal file
187
db.json
Normal file
|
@ -0,0 +1,187 @@
|
|||
{
|
||||
"graphics": [
|
||||
{
|
||||
"name": "Nidurteljari",
|
||||
"engine": "countdown",
|
||||
"settings": {
|
||||
"html": "<%- text %><br><span id=\"Nidurteljari-countdown-timer\">countdown appears here</span>",
|
||||
"main": "text",
|
||||
"text": "Sunnudagssamkoman hefst klukkan 11:00",
|
||||
"countdown": "2018-07-01 11:00",
|
||||
"finished": "Skamma stund",
|
||||
"css": "#Nidurteljari {\n position: absolute;\n font: 2em \"Berthold Akzidenz Grotesk BE\";\n top: 830px;\n left: 230px;\n width: 1450px;\n padding-top: 20px;\n border-top: 1px solid #095376;\n text-align: center;\n color: #095376;\n font-size: 60pt;\n line-height: 70pt;\n font-weight: bold;\n}\n#Nidurteljari-countdown-timer {\n font-size: 80pt;\n}"
|
||||
},
|
||||
"id": 1586112780891
|
||||
},
|
||||
{
|
||||
"name": "Nafn",
|
||||
"engine": "text",
|
||||
"settings": {
|
||||
"properties": [
|
||||
"nafn",
|
||||
"titill"
|
||||
],
|
||||
"main": "<%- nafn %>",
|
||||
"html": "<div class=\"outer\">\n<div class=\"inside\">\n<h2><%- nafn %></h2>\n<h4><%- titill %></h4>\n</div>\n</div>",
|
||||
"css": "#Nafn {\nposition: absolute;\nbottom: 50px;\nleft: 0px;\nwidth: 100%;\nright: 0;\nbackground: transparent;\ncolor: black;\nfont-family: Raleway;\ndisplay: flex;\njustify-content: center;\n}\n\n#Nafn .outer {\nborder: 4px solid white;\npadding: 6px;\n}\n\n#Nafn .inside {\npadding: 8px 24px;\nbackground: white;\ndisplay: flex;\nflex-direction: column;\n}\n\n#Nafn .inside h2 {\nfont-weight: bold;\nfont-size: 40px;\ntext-align: center;\ntext-transform: uppercase;\npadding: 0;\nmargin: 0;\nline-height: 120%;\n}\n\n#Nafn .inside h4 {\nfont-weight: normal;\nfont-size: 18px;\npadding: 0;\nmargin: 0;\ntext-align: center;\nline-height: 125%;\ntext-transform: uppercase;\n}"
|
||||
},
|
||||
"id": 1586112780892
|
||||
},
|
||||
{
|
||||
"name": "Minnisvers",
|
||||
"engine": "text",
|
||||
"settings": {
|
||||
"properties": [
|
||||
"bok",
|
||||
"vers"
|
||||
],
|
||||
"main": "bok",
|
||||
"css": "#Minnisvers {\n position: absolute;\n font: 2em \"Berthold Akzidenz Grotesk BE\";\n top: 850px;\n right: 0px;\n}\n.bok-holder {\n background: rgba(0,68,105,0.6);\n line-height: 60px;\n font-size: 60px;\n color: white;\n padding: 10px 60px 0px 30px;\n}\n.vers-holder {\n background: rgba(33,29,29,0.6);\n font-size: 30px;\n line-height: 30px;\n color: white;\n padding: 10px 60px 0px 40px;\n}",
|
||||
"html": "<div class=\"bok-holder\"><%- bok %></div>\n<div class=\"vers-holder\"><%- vers %></div>"
|
||||
},
|
||||
"id": 1586112780893
|
||||
},
|
||||
{
|
||||
"name": "Forn",
|
||||
"engine": "text",
|
||||
"settings": {
|
||||
"properties": [
|
||||
"Titill",
|
||||
"Text"
|
||||
],
|
||||
"main": "<%- Titill %> - <%- Text %>",
|
||||
"css": "#Forn {\nposition: absolute;\ntop: 50px;\nleft: 95px;\nborder: 4px solid white;\npadding: 6px;\nbackground: transparent;\ncolor: black;\nfont-family: Raleway;\n}\n\n#Forn .inside {\npadding: 6px 24px;\nbackground: white;\ndisplay: flex;\nflex-direction: column;\n}\n\n#Forn .inside h2 {\nfont-weight: bold;\nfont-size: 24px;\ntext-align: center;\ntext-transform: uppercase;\npadding: 0;\nmargin: 0;\n}\n\n#Forn .inside h4 {\nfont-weight: normal;\nfont-size: 18px;\npadding: 0;\nmargin: 0;\ntext-align: center;\nline-height: 125%;\n}",
|
||||
"html": "<div class=\"inside\">\n<h2><%- Titill %></h2>\n<h4><%= Text.replace('\\n', '<br>') %></h4>\n</div>"
|
||||
},
|
||||
"id": 1586112780894
|
||||
},
|
||||
{
|
||||
"name": "Lög",
|
||||
"engine": "text",
|
||||
"settings": {
|
||||
"properties": [
|
||||
"nafn"
|
||||
],
|
||||
"html": "<div class=\"box-before\"></div>\n<div class=\"box-logo\"></div>\n<div class=\"box-after\"></div>\n<div class=\"box-name\">\n <div class=\"holder\">\n <div class=\"name-holder\"><%- nafn %>\n <div class=\"name-triangle\"></div><div class=\"titill-triangle\"></div>\n </div>\n </div>\n</div>",
|
||||
"css": ".box-before {\n margin-bottom: 15px;\n width: 160px;\n height: 112px;\n margin-right: -37px;\n /* ---------- THEME COLOR ---------- */\n background-image: -webkit-radial-gradient(200px 50%, circle, rgba(0, 0, 0, 0) 0, rgba(0, 0, 0, 0) 75px, rgba(0,68,105,1) 77px);\n /* ---------- END THEME COLOR ---------- */\n}\n.box-after {\n /* ---------- THEME COLOR ---------- */\n background-image: -webkit-radial-gradient(-40px 50%, circle, rgba(0, 0, 0, 0) 0, rgba(0, 0, 0, 0) 75px, rgba(0,68,105,1) 77px);\n /* ---------- END THEME COLOR ---------- */\n margin-bottom: 15px;\n width: 100px;\n height: 112px;\n margin-left: -37px;\n}\n\n#Lög {\n position: absolute;\n color: white;\n font: 2em \"Berthold Akzidenz Grotesk BE\";\n top: 100px;\n left: 0px;\n}\n\n#Lög > div { display: inline-block; }\n\n.box-logo {\n width: 141px;\n height: 141px;\n background: url('uploads/logo.png') 0px 0px no-repeat;\n background-size: contain;\n}\n\n.box-name {\n margin-bottom: 15px;\n height: 112px;\n position: relative;\n margin-left: -6px;\n width: 700px;\n}\n.holder { position: absolute; }\n.name-holder {\n position: relative;\n background: linear-gradient(to right, rgba(207,209,210,0.6) 0%,rgba(235,235,237,0.6) 50%,rgba(183,185,185,0.6) 100%);\n line-height: 102px;\n font-size: 60px;\n color: rgba(0,68,105,1);\n padding: 10px 30px 0px 20px;\n}\n.name-triangle {\n position: absolute;\n right: -112px;\n border-width: 0 112px 112px 0px;\n top: 0;\n border-style: solid;\n border-color: transparent transparent rgba(183,185,185,0.6) transparent;\n}",
|
||||
"main": "nafn"
|
||||
},
|
||||
"id": 1586112780895
|
||||
},
|
||||
{
|
||||
"name": "Dagskra",
|
||||
"engine": "schedule",
|
||||
"settings": {
|
||||
"properties": [
|
||||
"temp"
|
||||
],
|
||||
"textfields": [
|
||||
"item1"
|
||||
],
|
||||
"main": "temp"
|
||||
},
|
||||
"id": 1586112780896
|
||||
}
|
||||
],
|
||||
"presets": [
|
||||
{
|
||||
"graphic_id": 1586112780892,
|
||||
"values": {
|
||||
"nafn": "Bla",
|
||||
"titill": "test"
|
||||
},
|
||||
"sort": 1,
|
||||
"id": 1586205588587
|
||||
},
|
||||
{
|
||||
"graphic_id": 1586112780892,
|
||||
"values": {
|
||||
"nafn": "Bla 2",
|
||||
"titill": "test 2"
|
||||
},
|
||||
"sort": 2,
|
||||
"id": 1586205644890
|
||||
}
|
||||
],
|
||||
"playing": [
|
||||
{
|
||||
"graphic": {
|
||||
"name": "Nafn",
|
||||
"engine": "text",
|
||||
"settings": {
|
||||
"properties": [
|
||||
"nafn",
|
||||
"titill"
|
||||
],
|
||||
"main": "<%- nafn %>",
|
||||
"html": "<div class=\"outer\">\n<div class=\"inside\">\n<h2><%- nafn %></h2>\n<h4><%- titill %></h4>\n</div>\n</div>",
|
||||
"css": "#Nafn {\nposition: absolute;\nbottom: 50px;\nleft: 0px;\nwidth: 100%;\nright: 0;\nbackground: transparent;\ncolor: black;\nfont-family: Raleway;\ndisplay: flex;\njustify-content: center;\n}\n\n#Nafn .outer {\nborder: 4px solid white;\npadding: 6px;\n}\n\n#Nafn .inside {\npadding: 8px 24px;\nbackground: white;\ndisplay: flex;\nflex-direction: column;\n}\n\n#Nafn .inside h2 {\nfont-weight: bold;\nfont-size: 40px;\ntext-align: center;\ntext-transform: uppercase;\npadding: 0;\nmargin: 0;\nline-height: 120%;\n}\n\n#Nafn .inside h4 {\nfont-weight: normal;\nfont-size: 18px;\npadding: 0;\nmargin: 0;\ntext-align: center;\nline-height: 125%;\ntext-transform: uppercase;\n}"
|
||||
},
|
||||
"id": 1586112780892
|
||||
},
|
||||
"name": "Nafn",
|
||||
"html": "<div class=\"outer\">\n<div class=\"inside\">\n<h2>Bla 2</h2>\n<h4>test 2</h4>\n</div>\n</div>",
|
||||
"css": "#Nafn {\nposition: absolute;\nbottom: 50px;\nleft: 0px;\nwidth: 100%;\nright: 0;\nbackground: transparent;\ncolor: black;\nfont-family: Raleway;\ndisplay: flex;\njustify-content: center;\n}\n\n#Nafn .outer {\nborder: 4px solid white;\npadding: 6px;\n}\n\n#Nafn .inside {\npadding: 8px 24px;\nbackground: white;\ndisplay: flex;\nflex-direction: column;\n}\n\n#Nafn .inside h2 {\nfont-weight: bold;\nfont-size: 40px;\ntext-align: center;\ntext-transform: uppercase;\npadding: 0;\nmargin: 0;\nline-height: 120%;\n}\n\n#Nafn .inside h4 {\nfont-weight: normal;\nfont-size: 18px;\npadding: 0;\nmargin: 0;\ntext-align: center;\nline-height: 125%;\ntext-transform: uppercase;\n}",
|
||||
"data": {
|
||||
"nafn": "Bla 2",
|
||||
"titill": "test 2"
|
||||
},
|
||||
"is_deleted": false,
|
||||
"id": 1586213076185
|
||||
}
|
||||
],
|
||||
"schedule": [
|
||||
{
|
||||
"graphic_id": 1586112780892,
|
||||
"values": {
|
||||
"nafn": "Bla 2",
|
||||
"titill": "test 2"
|
||||
},
|
||||
"sort": 1,
|
||||
"id": 1586213073361,
|
||||
"graphic": {
|
||||
"name": "Nafn",
|
||||
"engine": "text",
|
||||
"settings": {
|
||||
"properties": [
|
||||
"nafn",
|
||||
"titill"
|
||||
],
|
||||
"main": "<%- nafn %>",
|
||||
"html": "<div class=\"outer\">\n<div class=\"inside\">\n<h2><%- nafn %></h2>\n<h4><%- titill %></h4>\n</div>\n</div>",
|
||||
"css": "#Nafn {\nposition: absolute;\nbottom: 50px;\nleft: 0px;\nwidth: 100%;\nright: 0;\nbackground: transparent;\ncolor: black;\nfont-family: Raleway;\ndisplay: flex;\njustify-content: center;\n}\n\n#Nafn .outer {\nborder: 4px solid white;\npadding: 6px;\n}\n\n#Nafn .inside {\npadding: 8px 24px;\nbackground: white;\ndisplay: flex;\nflex-direction: column;\n}\n\n#Nafn .inside h2 {\nfont-weight: bold;\nfont-size: 40px;\ntext-align: center;\ntext-transform: uppercase;\npadding: 0;\nmargin: 0;\nline-height: 120%;\n}\n\n#Nafn .inside h4 {\nfont-weight: normal;\nfont-size: 18px;\npadding: 0;\nmargin: 0;\ntext-align: center;\nline-height: 125%;\ntext-transform: uppercase;\n}"
|
||||
},
|
||||
"id": 1586112780892
|
||||
}
|
||||
},
|
||||
{
|
||||
"graphic_id": 1586112780892,
|
||||
"values": {
|
||||
"nafn": "Bla",
|
||||
"titill": "test"
|
||||
},
|
||||
"sort": 2,
|
||||
"id": 1586213074043,
|
||||
"graphic": {
|
||||
"name": "Nafn",
|
||||
"engine": "text",
|
||||
"settings": {
|
||||
"properties": [
|
||||
"nafn",
|
||||
"titill"
|
||||
],
|
||||
"main": "<%- nafn %>",
|
||||
"html": "<div class=\"outer\">\n<div class=\"inside\">\n<h2><%- nafn %></h2>\n<h4><%- titill %></h4>\n</div>\n</div>",
|
||||
"css": "#Nafn {\nposition: absolute;\nbottom: 50px;\nleft: 0px;\nwidth: 100%;\nright: 0;\nbackground: transparent;\ncolor: black;\nfont-family: Raleway;\ndisplay: flex;\njustify-content: center;\n}\n\n#Nafn .outer {\nborder: 4px solid white;\npadding: 6px;\n}\n\n#Nafn .inside {\npadding: 8px 24px;\nbackground: white;\ndisplay: flex;\nflex-direction: column;\n}\n\n#Nafn .inside h2 {\nfont-weight: bold;\nfont-size: 40px;\ntext-align: center;\ntext-transform: uppercase;\npadding: 0;\nmargin: 0;\nline-height: 120%;\n}\n\n#Nafn .inside h4 {\nfont-weight: normal;\nfont-size: 18px;\npadding: 0;\nmargin: 0;\ntext-align: center;\nline-height: 125%;\ntext-transform: uppercase;\n}"
|
||||
},
|
||||
"id": 1586112780892
|
||||
}
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"casparplayhost": "localhost:3000",
|
||||
"casparhost": "host.docker.internal"
|
||||
},
|
||||
"version": 1,
|
||||
"trash": []
|
||||
}
|
|
@ -1,14 +1,11 @@
|
|||
'use strict'
|
||||
require('babel-register')
|
||||
|
||||
let log = require('./log').default
|
||||
import log from './api/log.mjs'
|
||||
import setup from './api/setup.mjs'
|
||||
|
||||
// Run the database script automatically.
|
||||
log.info('Running database integrity scan.')
|
||||
let setup = require('./script/setup')
|
||||
|
||||
setup().then(() => {
|
||||
require('./api/server')
|
||||
import('./api/server.mjs')
|
||||
}).catch((error) => {
|
||||
log.error(error, 'Error while preparing database')
|
||||
process.exit(1)
|
|
@ -1,6 +1,3 @@
|
|||
'use strict'
|
||||
require('babel-register')
|
||||
|
||||
const _ = require('lodash')
|
||||
const config = require('./config')
|
||||
|
||||
|
|
4082
package-lock.json
generated
Normal file
4082
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
29
package.json
29
package.json
|
@ -3,17 +3,17 @@
|
|||
"version": "1.0.0",
|
||||
"description": "CasparCG superimposed graphics project",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build:styl": "stylus -m app/styl/main.styl --out public && stylus -m app/styl/client.styl --out public && stylus -m app/styl/status.styl --out public",
|
||||
"watch:styl": "stylus -w -m app/styl/main.styl app/styl/client.styl app/styl/status.styl --out public",
|
||||
"build:js": "asbundle app/main/index.js ../../../public/main.js && asbundle app/client/index.js public/client.js && asbundle app/status/index.js ../../public/status.js",
|
||||
"watch:js": "nodemon --watch app --exec \"npm run build:js\"",
|
||||
"watch:server": "nodemon --watch api index.js | bunyan -o short",
|
||||
"start": "node index.js | bunyan -o short",
|
||||
"start:win": "node index.js | bunyan -o short",
|
||||
"dev": "run-p watch:styl watch:js watch:server",
|
||||
"prod-run": "npm run build:js && npm run build-client:js && npm run build-status:js && npm run build:styl && npm run build-client:styl && npm run build-status:styl && npm start",
|
||||
"build": "npm run build:js && npm run build:styl",
|
||||
"js:build:main": "asbundle app/main/index.js public/main.js",
|
||||
"js:build:client": "asbundle app/client/index.js public/client.js",
|
||||
"js:build:status": "asbundle app/status/index.js public/status.js",
|
||||
"js:watch": "nodemon --watch app --exec \"npm run build\"",
|
||||
"start:watch": "nodemon --experimental-modules --watch api 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",
|
||||
"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",
|
||||
"docker:install": "npm run docker -- npm install",
|
||||
"docker:dev": "npm run docker -- npm run dev"
|
||||
|
@ -34,8 +34,6 @@
|
|||
},
|
||||
"homepage": "https://github.com/nfp-projects/caspar-sup#readme",
|
||||
"dependencies": {
|
||||
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
|
||||
"babel-register": "^6.26.0",
|
||||
"bookshelf": "^0.11.1",
|
||||
"bunyan": "^1.8.12",
|
||||
"casparcg-connection": "4.9.0",
|
||||
|
@ -44,18 +42,19 @@
|
|||
"koa": "^2.4.1",
|
||||
"koa-better-serve": "^2.0.7",
|
||||
"koa-socket": "^4.4.0",
|
||||
"lodash.template": "^4.5.0",
|
||||
"lowdb": "^1.0.0",
|
||||
"nconf": "^0.9.1",
|
||||
"socket.io": "^2.3.0",
|
||||
"sqlite3": "^4.1.1",
|
||||
"tslib": "^1.11.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"asbundle": "^2.6.0",
|
||||
"asbundle": "TheThing/asbundle",
|
||||
"dragula": "^3.7.2",
|
||||
"mithril": "^1.1.5",
|
||||
"nodemon": "^2.0.2",
|
||||
"npm-run-all": "^4.1.2",
|
||||
"run-p": "0.0.0",
|
||||
"stylus": "^0.54.7"
|
||||
"run-p": "0.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,54 +1,45 @@
|
|||
* {
|
||||
html, body {
|
||||
width:1920px; /* Set to your channel's resolution */
|
||||
height:1080px; /* Set to your channel's resolution */
|
||||
margin:0; /* Use all available space */
|
||||
padding:0; /* Use all available space */
|
||||
background:transparent; /* The HTML consumer actually makes your background transparent by default, unless a color or image is specified - but this might be usefull when debugging in browsers */
|
||||
overflow:hidden; /* Hide any overflowing elements - to disable scollbars */
|
||||
-webkit-font-smoothing:antialiased !important; /* Set aliasing of fonts - possible options: none, antialiased, subpixel-antialiased */
|
||||
}
|
||||
|
||||
html {
|
||||
overflow: auto;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
/* This sets all elements to be the actual set dimensions, disregarding padding and borders */
|
||||
/* -webkit-backface-visibility: hidden; */
|
||||
/* Hide the backface of elements - useful for 3d effects */
|
||||
-webkit-transition: translate3d(0, 0, 0); /* Turns on hardware acceleration - not known to be of benefit in CCG, but won't hurt */
|
||||
box-sizing:border-box;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
width: 1920px;
|
||||
/* Set to your channel's resolution */
|
||||
height: 1080px;
|
||||
/* Set to your channel's resolution */
|
||||
margin: 0;
|
||||
/* Use all available space */
|
||||
padding: 0;
|
||||
/* Use all available space */
|
||||
background: transparent;
|
||||
/* The HTML consumer actually makes your background transparent by default, unless a color or image is specified - but this might be usefull when debugging in browsers */
|
||||
overflow: hidden;
|
||||
/* Hide any overflowing elements - to disable scollbars */
|
||||
-webkit-font-smoothing: antialiased !important;
|
||||
/* Set aliasing of fonts - possible options: none, antialiased, subpixel-antialiased */
|
||||
|
||||
*, *:before, *:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Calibri, Arial;
|
||||
font-family: Arial;
|
||||
font-size: 40px;
|
||||
color: #fff;
|
||||
/* -webkit-text-stroke-width: 0.5px;
|
||||
color: #FFFFFF;
|
||||
font-weight: normal;
|
||||
font-size: 22pt;
|
||||
/* -webkit-text-stroke-width: 0.5px;
|
||||
-webkit-text-stroke-color: #888888;
|
||||
text-shadow: 2px 2px 1px #000000; */
|
||||
}
|
||||
body {
|
||||
font-family: Arial;
|
||||
font-weight: normal;
|
||||
/* text-shadow: 0px 0px 0px #000000; */
|
||||
font-size: 22pt;
|
||||
}
|
||||
html {
|
||||
overflow: auto;
|
||||
}
|
||||
body > div {
|
||||
|
||||
body > div
|
||||
{
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.root-element {
|
||||
opacity: 0;
|
||||
transition: opacity 1s;
|
||||
}
|
||||
|
||||
.root-element-display {
|
||||
opacity: 1;
|
||||
transition: opacity 1s;
|
||||
}
|
||||
/*# sourceMappingURL=client.css.map */
|
|
@ -1 +0,0 @@
|
|||
{"version":3,"sources":["../app/styl/client.styl"],"names":[],"mappings":"AAAA;EACE,oBAAoB,WAApB;EACA,YAAW,WAAX;AAA4C;AAC7C;AAAiD;EAChD,oBAAoB,qBAApB;AAA4C;;AAE9C;AAAM;EACJ,OAAM,OAAN;AAAyD;EACzD,QAAO,OAAP;AAAyD;EACzD,QAAO,EAAP;AAAyD;EACzD,SAAQ,EAAR;AAAyD;EACzD,YAAW,YAAX;AAAyD;EACzD,UAAS,OAAT;AAAyD;EACzD,wBAAuB,uBAAvB;AAAyD;;AAE3D;EACE,aAAoB,eAApB;EACA,WAAW,KAAX;EACA,OAAO,KAAP;AACA;;;;AAKF;EACE,aAAa,MAAb;EACA,aAAa,OAAb;AACA;EACA,WAAW,KAAX;;AAEF;EACE,UAAU,KAAV;;AAEF;EAEE,UAAU,SAAV;;AAGF;EACE,SAAS,EAAT;EACA,YAAY,WAAZ;;AAGF;EACE,SAAS,EAAT;EACA,YAAY,WAAZ","file":"client.css"}
|
437
public/main.css
Normal file
437
public/main.css
Normal file
|
@ -0,0 +1,437 @@
|
|||
html {
|
||||
box-sizing: border-box;
|
||||
font-size: 16px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
*, *:before, *:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body, h1, h2, h3, h4, h5, h6, p {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #3f3f41;
|
||||
color: #f1f1f1;
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
flex-direction: column;
|
||||
font-family: Helvetica, sans-serif, Arial;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
/* Components */
|
||||
button {
|
||||
border: none;
|
||||
color: #f1f1f1;
|
||||
background: #2199e8;
|
||||
font-size: 0.6em;
|
||||
height: 3em;
|
||||
}
|
||||
button.green {
|
||||
background: #3adb78;
|
||||
}
|
||||
button.red {
|
||||
background: #ec5840;
|
||||
}
|
||||
.error-box {
|
||||
margin: 1rem 0rem 2rem 0;
|
||||
padding: 1rem;
|
||||
background: #f00;
|
||||
color: #fff;
|
||||
font-size: 0.7em;
|
||||
line-height: 1em;
|
||||
}
|
||||
/* Container */
|
||||
.container {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
flex-grow: 2;
|
||||
}
|
||||
/* Header */
|
||||
section.current {
|
||||
padding: 0 13px;
|
||||
background: #000;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
z-index: 10;
|
||||
}
|
||||
section.current h4 {
|
||||
color: #777;
|
||||
font-size: 0.7em;
|
||||
padding: 0.2em;
|
||||
margin: 0;
|
||||
}
|
||||
section.current h3 {
|
||||
font-size: 1em;
|
||||
line-height: 2em;
|
||||
color: #eb6e00;
|
||||
flex-grow: 2;
|
||||
height: 2em;
|
||||
padding-right: 0.5em;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
}
|
||||
section.current button {
|
||||
width: 80px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
section.current .item {
|
||||
display: flex;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.disconnected {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0,0,0,0.8);
|
||||
color: #fff;
|
||||
font-size: 1em;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
/* Menu */
|
||||
nav {
|
||||
width: 200px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
padding: 10px;
|
||||
background: #2d2d30;
|
||||
text-align: center;
|
||||
}
|
||||
nav .header {
|
||||
color: #777;
|
||||
font-size: 0.8em;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
nav .header--space {
|
||||
margin-top: 2em;
|
||||
}
|
||||
nav a {
|
||||
font-size: 0.8em;
|
||||
line-height: 2.6em;
|
||||
display: block;
|
||||
border: 4px solid #2d2d30;
|
||||
background: #007acc;
|
||||
color: #fff;
|
||||
}
|
||||
nav a.active {
|
||||
background: transparent;
|
||||
border: 4px solid #007acc;
|
||||
}
|
||||
nav a:hover {
|
||||
border: 4px solid #007acc;
|
||||
}
|
||||
nav input[type=text] {
|
||||
text-align: center;
|
||||
}
|
||||
nav .status {
|
||||
padding: 5px 20px;
|
||||
font-size: 0.8em;
|
||||
color: #777;
|
||||
text-align: left;
|
||||
position: relative;
|
||||
margin-left: 1.8em;
|
||||
}
|
||||
nav .status::after {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: calc(50% - 5px);
|
||||
content: '';
|
||||
border: 6px solid #ec5840;
|
||||
}
|
||||
nav .status.green::after {
|
||||
border-color: #008000;
|
||||
}
|
||||
/* Main */
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
padding: 10px 1em;
|
||||
flex-grow: 2;
|
||||
width: 300px;
|
||||
}
|
||||
main .header {
|
||||
color: #777;
|
||||
font-size: 0.8em;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
/* Inputs */
|
||||
label {
|
||||
margin-top: 0.6em;
|
||||
color: #f1f1f1;
|
||||
font-size: 0.7em;
|
||||
}
|
||||
label a,
|
||||
label a:hover,
|
||||
label a:visited {
|
||||
color: #aaa;
|
||||
text-decoration: underline;
|
||||
}
|
||||
input[type='text'],
|
||||
textarea,
|
||||
select {
|
||||
font-size: 0.6em;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
background: #333337;
|
||||
border: 1px solid #2d2d30;
|
||||
color: #999;
|
||||
transition-property: none !important;
|
||||
outline: none;
|
||||
}
|
||||
input[type='text']:hover,
|
||||
textarea:hover,
|
||||
select:hover {
|
||||
color: #f1f1f1;
|
||||
border-color: #007acc;
|
||||
}
|
||||
input[type='text']:focus,
|
||||
textarea:focus,
|
||||
select:focus {
|
||||
background: #333337;
|
||||
color: #f1f1f1;
|
||||
border-color: #007acc;
|
||||
box-shadow: none;
|
||||
}
|
||||
textarea#graphic-html {
|
||||
min-height: 150px;
|
||||
}
|
||||
textarea#graphic-css {
|
||||
min-height: 400px;
|
||||
}
|
||||
input[type=submit] {
|
||||
margin-top: 0.6em;
|
||||
border: none;
|
||||
color: #f1f1f1;
|
||||
background: #2199e8;
|
||||
font-size: 0.6em;
|
||||
line-height: 3em;
|
||||
}
|
||||
input[readonly],
|
||||
input[readonly]:hover {
|
||||
background: #2d2d30 !important;
|
||||
border-color: #3f3f3f;
|
||||
}
|
||||
select {
|
||||
height: 2.5em;
|
||||
-webkit-appearance: none;
|
||||
border-radius: 0;
|
||||
background-position: right center;
|
||||
background-size: 9px 6px;
|
||||
background-origin: content-box;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='32' height='24' viewBox='0 0 32 24'><polygon points='0,0 32,0 16,24' style='fill: rgb%28138, 138, 138%29'></polygon></svg>");
|
||||
}
|
||||
select:hover {
|
||||
color: #f1f1f1;
|
||||
border-color: #007acc;
|
||||
}
|
||||
select:focus {
|
||||
background: #333337;
|
||||
color: #f1f1f1;
|
||||
border-color: #007acc;
|
||||
box-shadow: none;
|
||||
}
|
||||
a.button {
|
||||
margin: 0 1rem 0 0;
|
||||
width: 7rem;
|
||||
}
|
||||
/* Graphic */
|
||||
header {
|
||||
display: flex;
|
||||
}
|
||||
header h3 {
|
||||
font-size: 1em;
|
||||
flex-grow: 2;
|
||||
border-bottom: 1px solid #2d2d30;
|
||||
padding-top: 10px;
|
||||
margin-right: 30px;
|
||||
}
|
||||
header button {
|
||||
border: 0;
|
||||
width: 100px;
|
||||
}
|
||||
.graphic-presetlist {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
.graphic-presetadd {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
border: 1px solid #2d2d30;
|
||||
margin: 30px 0 10px;
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
}
|
||||
.graphic-presetadd-header {
|
||||
background: #3f3f41;
|
||||
position: absolute;
|
||||
top: -1.3em;
|
||||
left: 10px;
|
||||
font-size: 0.8em;
|
||||
padding: 0.8em 10px;
|
||||
}
|
||||
.graphic-presetadd-buttons {
|
||||
display: flex;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.graphic-presetadd-buttons button {
|
||||
margin-right: 10px;
|
||||
width: 150px;
|
||||
}
|
||||
.graphic-presetremove {
|
||||
align-self: center;
|
||||
margin-top: 50px;
|
||||
width: 150px;
|
||||
}
|
||||
.graphic-empty {
|
||||
font-size: 0.7em;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.graphic-delete {
|
||||
align-self: center;
|
||||
margin-top: 30px;
|
||||
width: 150px;
|
||||
}
|
||||
.graphic-label {
|
||||
margin-top: 30px;
|
||||
padding-bottom: 0.5em;
|
||||
}
|
||||
.graphic-helper {
|
||||
font-size: 0.7em;
|
||||
color: #999;
|
||||
margin: 5px 0 0;
|
||||
}
|
||||
.graphic-helper.bottom {
|
||||
margin: 0;
|
||||
}
|
||||
.graphic-property,
|
||||
.graphic-preset {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
}
|
||||
.graphic-property-reorder,
|
||||
.graphic-preset-reorder {
|
||||
width: 62px;
|
||||
background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NTc3MiwgMjAxNC8wMS8xMy0xOTo0NDowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODQ5NDcyMzY3MTU5MTFFOEEwQjVFQUFCMEYzRDE2QjciIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODQ5NDcyMzc3MTU5MTFFOEEwQjVFQUFCMEYzRDE2QjciPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4NDk0NzIzNDcxNTkxMUU4QTBCNUVBQUIwRjNEMTZCNyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4NDk0NzIzNTcxNTkxMUU4QTBCNUVBQUIwRjNEMTZCNyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pla8e2wAAABFUExURXd3d////3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d0brzhMAAAAWdFJOUwAAN0NEVXJzdHV2d3h/iImKmaq73e5w9SzPAAAAlElEQVRIx+XT2wqDMBRE0Witt2piW53//9SCoETMwJkXkWY/CgtNBp0jDa/086JgwgNeIwEghpFVAMFOJoCaNNkFMNlIJBImRcrPodJ8ybQ/I285B7mLSCWX+/qL3EVTznK5T/mUu++P7OVyX/9U9T30sLyliUeobR8WmcZ6lnYTrf34HRX8xlbTaZfcA726yziyXX5Joj0NgTfsUQAAAABJRU5ErkJggg==") no-repeat transparent;
|
||||
background-size: 25px;
|
||||
background-position: center;
|
||||
touch-action: none;
|
||||
}
|
||||
.graphic-property input,
|
||||
.graphic-preset input {
|
||||
flex-grow: 2;
|
||||
margin: 0;
|
||||
}
|
||||
.graphic-property button,
|
||||
.graphic-preset button {
|
||||
width: 100px;
|
||||
border: 1px solid #3f3f41;
|
||||
border-left: none;
|
||||
}
|
||||
.graphic-preset input {
|
||||
padding-top: 1.5em;
|
||||
padding-bottom: 1.5em;
|
||||
}
|
||||
.graphic-preset button {
|
||||
height: auto;
|
||||
}
|
||||
.schedule-empty {
|
||||
margin-top: 2em;
|
||||
font-size: 1em;
|
||||
text-align: center;
|
||||
}
|
||||
.settings-empty {
|
||||
text-align: center;
|
||||
margin: 50px 0 30px;
|
||||
font-size: 0.8em;
|
||||
color: #999;
|
||||
}
|
||||
.settings-empty-button {
|
||||
align-self: center;
|
||||
width: 200px;
|
||||
}
|
||||
/* Dragula */
|
||||
|
||||
#dragcontainer {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.gu-mirror {
|
||||
position: absolute !important;
|
||||
margin: 0 !important;
|
||||
z-index: 9999 !important;
|
||||
opacity: 0.8;
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)";
|
||||
filter: alpha(opacity=80);
|
||||
}
|
||||
.gu-hide {
|
||||
display: none !important;
|
||||
}
|
||||
.gu-unselectable {
|
||||
-webkit-user-select: none !important;
|
||||
-moz-user-select: none !important;
|
||||
-ms-user-select: none !important;
|
||||
user-select: none !important;
|
||||
}
|
||||
.gu-transit {
|
||||
opacity: 0.2;
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=20)";
|
||||
filter: alpha(opacity=20);
|
||||
}
|
||||
/* Media queries */
|
||||
body {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
@media only screen and (max-device-width: 600px) {
|
||||
#container {
|
||||
flex-direction: column;
|
||||
}
|
||||
nav {
|
||||
width: auto;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
nav .header {
|
||||
width: 100%;
|
||||
}
|
||||
nav a {
|
||||
width: calc(50% - 8px);
|
||||
}
|
||||
nav input[type=text] {
|
||||
width: 100%;
|
||||
}
|
||||
nav .status {
|
||||
align-self: center;
|
||||
width: 120px;
|
||||
}
|
||||
#content {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
/*# sourceMappingURL=main.css.map */
|
116
public/status.css
Normal file
116
public/status.css
Normal file
|
@ -0,0 +1,116 @@
|
|||
html {
|
||||
box-sizing: border-box;
|
||||
font-size: 16px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
*, *:before, *:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body, h1, h2, h3, h4, h5, h6, p {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #3f3f41;
|
||||
color: #eb6e00;
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
flex-direction: column;
|
||||
font-family: Helvetica, sans-serif, Arial;
|
||||
}
|
||||
|
||||
#container {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
flex-grow: 2;
|
||||
}
|
||||
|
||||
#container h3 {
|
||||
background: black;
|
||||
font-size: 2.3em;
|
||||
line-height: (2.3em);
|
||||
color: #eb6e00;
|
||||
height: (2.3em);
|
||||
overflow: hidden;
|
||||
padding: 0 0.3em;
|
||||
flex-grow: 2;
|
||||
border-radius: 5px 0 0 5px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
#container button {
|
||||
border: none;
|
||||
color: black;
|
||||
background: #eb6e00;
|
||||
font-size: 2em;
|
||||
width: 80px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 0 5px 5px 0;
|
||||
}
|
||||
|
||||
#container .item {
|
||||
display: flex;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
#container .empty {
|
||||
flex-grow: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #ccc;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
margin-bottom: 5px;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
header h2 {
|
||||
font-size: 3em;
|
||||
line-height: 64px;
|
||||
padding: 0 0.3em;
|
||||
flex-grow: 2;
|
||||
}
|
||||
|
||||
header .status {
|
||||
padding: 0 20px 0 30px;
|
||||
line-height: 64px;
|
||||
font-size: 2em;
|
||||
text-align: left;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
header .status::after {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: calc(50% - 7px);
|
||||
content: '';
|
||||
border: 10px solid #ec5840;
|
||||
}
|
||||
|
||||
header .status.green::after {
|
||||
border-color: #00FF00;
|
||||
}
|
||||
|
||||
.disconnected {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0,0,0,0.8);
|
||||
color: white;
|
||||
font-size: 3em;
|
||||
display: flex;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
/* eslint-disable no-console */
|
||||
'use strict'
|
||||
|
||||
const _ = require('lodash')
|
||||
const config = require('../config')
|
||||
let log = require('../log').default
|
||||
|
||||
// This is important for setup to run cleanly.
|
||||
let knexConfig = _.cloneDeep(config.get('knex'))
|
||||
knexConfig.pool = { min: 1, max: 1 }
|
||||
|
||||
let knex = require('knex')(knexConfig)
|
||||
|
||||
log.info(knexConfig, 'Connected to database')
|
||||
|
||||
let setup = module.exports = () =>
|
||||
knex.migrate.latest({
|
||||
directory: './migrations',
|
||||
})
|
||||
.then((result) => {
|
||||
if (result[1].length === 0) {
|
||||
return log.info('Database is up to date')
|
||||
}
|
||||
for (let i = 0; i < result[1].length; i++) {
|
||||
log.info('Applied migration from', result[1][i].substr(result[1][i].lastIndexOf('\\') + 1))
|
||||
}
|
||||
return knex.destroy()
|
||||
})
|
||||
|
||||
if (require.main === module) {
|
||||
// Since we're running this as a script, we should output
|
||||
// directly to the console.
|
||||
log = console
|
||||
log.info = console.log.bind(console)
|
||||
|
||||
setup().then(() => {
|
||||
log.info('Setup ran successfully.')
|
||||
}).catch((error) => {
|
||||
log.error(error, 'Error while running setup.')
|
||||
}).then(() => {
|
||||
process.exit(0)
|
||||
})
|
||||
}
|
Loading…
Reference in a new issue