Implemented version 1.0
This commit is contained in:
parent
e9555b215e
commit
918f350dff
34 changed files with 1161 additions and 229 deletions
|
@ -1,36 +1,38 @@
|
||||||
var socket = require('./socket')
|
var socket = require('./socket')
|
||||||
|
|
||||||
socket.on('client.display', function(data) {
|
var engines = {
|
||||||
var exists = document.getElementById(data.key)
|
text: require('./frontend/text'),
|
||||||
|
countdown: require('./frontend/countdown'),
|
||||||
|
}
|
||||||
|
|
||||||
|
var current = []
|
||||||
|
|
||||||
|
function display(data) {
|
||||||
|
var exists = document.getElementById(data.graphic.name)
|
||||||
|
|
||||||
if (exists) {
|
if (exists) {
|
||||||
exists.tag.remove()
|
exists.tag.remove()
|
||||||
exists.remove()
|
exists.remove()
|
||||||
|
|
||||||
|
current.splice(current.indexOf(data.graphic.name), 1)
|
||||||
}
|
}
|
||||||
|
current.push(data.graphic.name)
|
||||||
|
|
||||||
var element = document.createElement('div')
|
let engine = data.graphic.engine
|
||||||
element.innerHTML = data.html
|
|
||||||
element.id = data.key
|
|
||||||
element.classList.add('root-element')
|
|
||||||
|
|
||||||
var styleElement = document.createElement('style')
|
if (engines[engine]) {
|
||||||
styleElement.setAttribute('type', 'text/css')
|
engines[engine](data)
|
||||||
styleElement.innerHTML = data.css
|
}
|
||||||
|
}
|
||||||
|
|
||||||
element.tag = styleElement
|
socket.on('client.display', display)
|
||||||
|
|
||||||
document.body.appendChild(element)
|
|
||||||
document.head.appendChild(styleElement)
|
|
||||||
|
|
||||||
window.setTimeout(function (){
|
|
||||||
element.classList.add('root-element-display')
|
|
||||||
}, 50)
|
|
||||||
})
|
|
||||||
|
|
||||||
socket.on('client.hide', function (data) {
|
socket.on('client.hide', function (data) {
|
||||||
var exists = document.getElementById(data.key)
|
var exists = document.getElementById(data.name)
|
||||||
|
|
||||||
if (exists) {
|
if (exists) {
|
||||||
|
current.splice(current.indexOf(data.name), 1)
|
||||||
|
|
||||||
exists.classList.remove('root-element-display')
|
exists.classList.remove('root-element-display')
|
||||||
|
|
||||||
window.setTimeout(function () {
|
window.setTimeout(function () {
|
||||||
|
@ -39,3 +41,7 @@ socket.on('client.hide', function (data) {
|
||||||
}, 1500)
|
}, 1500)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
socket.on('client.reset', function(data) {
|
||||||
|
data.forEach(display)
|
||||||
|
})
|
||||||
|
|
52
app/controller/add.js
Normal file
52
app/controller/add.js
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
const m = require('mithril')
|
||||||
|
const createModule = require('./module')
|
||||||
|
const components = require('./components')
|
||||||
|
const socket = require('../socket')
|
||||||
|
|
||||||
|
const Add = createModule({
|
||||||
|
init: function() {
|
||||||
|
this.monitor('engines', 'engine.all', [])
|
||||||
|
this.graphic = { }
|
||||||
|
},
|
||||||
|
|
||||||
|
updated: function(name, control) {
|
||||||
|
this.graphic[name] = control.target.value
|
||||||
|
},
|
||||||
|
|
||||||
|
create: function() {
|
||||||
|
if (!Add.vm.graphic.engine) {
|
||||||
|
Add.vm.graphic.engine = Add.vm.engines[0]
|
||||||
|
}
|
||||||
|
if (!Add.vm.graphic.name) {
|
||||||
|
this.error = 'Name cannot be empty'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('graphic.create', Add.vm.graphic)
|
||||||
|
},
|
||||||
|
}, function(ctrl) {
|
||||||
|
return m('div', [
|
||||||
|
m('h3.container-header', 'Add graphics'),
|
||||||
|
m('div.container-panel.panel-add', [
|
||||||
|
components.error(Add.vm.error),
|
||||||
|
m('label', [
|
||||||
|
'Name',
|
||||||
|
m('input[type=text]', {
|
||||||
|
oninput: Add.vm.updated.bind(Add.vm, 'name'),
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
m('label', [
|
||||||
|
'Engine',
|
||||||
|
m('select', {
|
||||||
|
onchange: Add.vm.updated.bind(Add.vm, 'engine'),
|
||||||
|
}, Add.vm.engines.map(engine =>
|
||||||
|
m('option', { key: engine, value: engine }, engine)
|
||||||
|
))
|
||||||
|
]),
|
||||||
|
m('a.button', {
|
||||||
|
onclick: Add.vm.create.bind(Add.vm)
|
||||||
|
}, 'Create'),
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
module.exports = Add
|
41
app/controller/components.js
Normal file
41
app/controller/components.js
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
const m = require('mithril')
|
||||||
|
|
||||||
|
exports.error = function(error) {
|
||||||
|
if (!error) return null
|
||||||
|
|
||||||
|
return m('div.error-box', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.presetList = function(vm) {
|
||||||
|
return [
|
||||||
|
m('a.panel-graphic-preset-add.button', {
|
||||||
|
onclick: vm.addPreset.bind(vm),
|
||||||
|
}, 'Save to preset list'),
|
||||||
|
m('a.panel-graphic-display.success.button', {
|
||||||
|
onclick: vm.displayCurrent.bind(vm),
|
||||||
|
}, 'Display'),
|
||||||
|
m('label', 'Presets'),
|
||||||
|
m('ul.panel-graphic-preset', vm.presets.map((item, index) =>
|
||||||
|
m('li', { key: index }, [
|
||||||
|
m('.row', { key: index }, [
|
||||||
|
m('div', { class: 'small-8 columns panel-graphic-property-item' },
|
||||||
|
m('input[type=text]', {
|
||||||
|
readonly: true,
|
||||||
|
value: item.values[graphic.settings.main],
|
||||||
|
})
|
||||||
|
),
|
||||||
|
m('div', { class: 'small-2 columns' },
|
||||||
|
m('a.panel-graphic-preset-remove.button.success', {
|
||||||
|
onclick: vm.displayPreset.bind(vm, item),
|
||||||
|
}, 'Display')
|
||||||
|
),
|
||||||
|
m('div', { class: 'small-2 columns' },
|
||||||
|
m('a.panel-graphic-preset-remove.button.alert', {
|
||||||
|
onclick: vm.removePreset.bind(vm, item),
|
||||||
|
}, 'Remove')
|
||||||
|
),
|
||||||
|
])
|
||||||
|
])
|
||||||
|
))
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,98 +0,0 @@
|
||||||
const m = require('mithril')
|
|
||||||
const socket = require('../socket')
|
|
||||||
const store = require('./store')
|
|
||||||
|
|
||||||
const Content = { }
|
|
||||||
|
|
||||||
Content.vm = (function() {
|
|
||||||
let vm = {}
|
|
||||||
|
|
||||||
vm.storeUpdated = function() {
|
|
||||||
vm.content = store.get('content') || {}
|
|
||||||
m.redraw()
|
|
||||||
}
|
|
||||||
|
|
||||||
vm.init = function() {
|
|
||||||
vm.content = store.get('content') || {}
|
|
||||||
store.listen('content', vm.storeUpdated)
|
|
||||||
}
|
|
||||||
|
|
||||||
vm.onunload = function() {
|
|
||||||
store.unlisten('content')
|
|
||||||
}
|
|
||||||
|
|
||||||
vm.updated = function(name, control) {
|
|
||||||
vm.content[name] = control.target.value
|
|
||||||
store.set('content', vm.content)
|
|
||||||
}
|
|
||||||
|
|
||||||
vm.display = function() {
|
|
||||||
socket.emit('content.display', vm.content)
|
|
||||||
}
|
|
||||||
|
|
||||||
vm.hide = function() {
|
|
||||||
socket.emit('content.hide')
|
|
||||||
}
|
|
||||||
|
|
||||||
return vm
|
|
||||||
})()
|
|
||||||
|
|
||||||
Content.controller = function() {
|
|
||||||
Content.vm.init()
|
|
||||||
|
|
||||||
this.onunload = Content.vm.onunload
|
|
||||||
}
|
|
||||||
|
|
||||||
Content.view = function() {
|
|
||||||
return m('div', [
|
|
||||||
m('h3', 'Content'),
|
|
||||||
m('div', { class: 'row' }, [
|
|
||||||
m('div', { class: 'small-12 columns' }, [
|
|
||||||
m('label', [
|
|
||||||
'HTML (use <%- name %> and <%- title %> for values)',
|
|
||||||
m('textarea', {
|
|
||||||
rows: '4',
|
|
||||||
oninput: Content.vm.updated.bind(null, 'html'),
|
|
||||||
value: Content.vm.content.html || '',
|
|
||||||
})
|
|
||||||
]),
|
|
||||||
]),
|
|
||||||
m('div', { class: 'small-12 columns' }, [
|
|
||||||
m('label', [
|
|
||||||
'CSS',
|
|
||||||
m('textarea', {
|
|
||||||
rows: '4',
|
|
||||||
oninput: Content.vm.updated.bind(null, 'css'),
|
|
||||||
value: Content.vm.content.css || '',
|
|
||||||
})
|
|
||||||
]),
|
|
||||||
]),
|
|
||||||
m('div', { class: 'small-12 columns' }, [
|
|
||||||
m('label', [
|
|
||||||
'Name',
|
|
||||||
m('input[type=text]', {
|
|
||||||
oninput: Content.vm.updated.bind(null, 'name'),
|
|
||||||
value: Content.vm.content.name || '',
|
|
||||||
})
|
|
||||||
]),
|
|
||||||
]),
|
|
||||||
m('div', { class: 'small-12 columns' }, [
|
|
||||||
m('label', [
|
|
||||||
'Title',
|
|
||||||
m('input[type=text]', {
|
|
||||||
oninput: Content.vm.updated.bind(null, 'title'),
|
|
||||||
value: Content.vm.content.title || '',
|
|
||||||
})
|
|
||||||
]),
|
|
||||||
]),
|
|
||||||
m('a.button', {
|
|
||||||
onclick: Content.vm.display
|
|
||||||
}, 'Display'),
|
|
||||||
m('a.button.alert', {
|
|
||||||
onclick: Content.vm.hide
|
|
||||||
}, 'Hide'),
|
|
||||||
]),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Content
|
|
136
app/controller/graphic/controller.js
Normal file
136
app/controller/graphic/controller.js
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
const _ = require('lodash')
|
||||||
|
const m = require('mithril')
|
||||||
|
const createModule = require('../module')
|
||||||
|
const socket = require('../../socket')
|
||||||
|
|
||||||
|
const Graphic = createModule({
|
||||||
|
init: function() {
|
||||||
|
this.monitor('graphic', 'graphic.single', {}, m.route.param('id'))
|
||||||
|
this.monitor('presets', 'preset.all', [], m.route.param('id'))
|
||||||
|
|
||||||
|
this.currentView = 'view'
|
||||||
|
this.current = {}
|
||||||
|
this.newProperty = m.prop('')
|
||||||
|
},
|
||||||
|
|
||||||
|
updated: function(name, variable, control) {
|
||||||
|
if (!control) {
|
||||||
|
control = variable
|
||||||
|
variable = 'graphic'
|
||||||
|
}
|
||||||
|
_.set(this[variable], name, control.target.value)
|
||||||
|
|
||||||
|
if (variable === 'graphic') {
|
||||||
|
socket.emit('graphic.update', this.graphic)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
addProperty: function() {
|
||||||
|
if (!this.newProperty()) {
|
||||||
|
this.error = 'Please type in property name'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.graphic.settings.properties.includes(this.newProperty())) {
|
||||||
|
this.error = 'A property with that name already exists'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.graphic.settings.properties.push(this.newProperty())
|
||||||
|
this.newProperty('')
|
||||||
|
|
||||||
|
if (!this.graphic.settings.main) {
|
||||||
|
this.graphic.settings.main = this.graphic.settings.properties[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('graphic.update', this.graphic)
|
||||||
|
},
|
||||||
|
|
||||||
|
cleanCurrent: function() {
|
||||||
|
if (this.graphic.engine === 'countdown') {
|
||||||
|
this.current.text = this.graphic.settings.text
|
||||||
|
this.current.countdown = this.graphic.settings.countdown
|
||||||
|
this.current.finished = this.graphic.settings.finished
|
||||||
|
|
||||||
|
if (!this.current.countdown) {
|
||||||
|
this.error = 'Count to had to be defined'
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let test = new Date(this.current.countdown.replace(' ', 'T'))
|
||||||
|
if (!test.getTime()) {
|
||||||
|
this.error = 'Count to has to be valid date and time'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.graphic.settings.properties.forEach(prop => {
|
||||||
|
if (!this.current[prop]) {
|
||||||
|
this.current[prop] = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (this.graphic.settings.main &&
|
||||||
|
!this.current[this.graphic.settings.main]) {
|
||||||
|
this.error = `Property "${this.graphic.settings.main}" cannot be empty`
|
||||||
|
return
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
addPreset: function() {
|
||||||
|
this.error = ''
|
||||||
|
|
||||||
|
this.cleanCurrent()
|
||||||
|
|
||||||
|
if (this.error) return
|
||||||
|
|
||||||
|
socket.emit('preset.add', {
|
||||||
|
graphic_id: this.graphic.id,
|
||||||
|
values: this.current,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
removePreset: function(preset) {
|
||||||
|
socket.emit('preset.remove', preset)
|
||||||
|
},
|
||||||
|
|
||||||
|
remove: function() {
|
||||||
|
socket.emit('graphic.remove', this.graphic)
|
||||||
|
m.route('/')
|
||||||
|
},
|
||||||
|
|
||||||
|
displayPreset: function(preset) {
|
||||||
|
socket.emit('content.display', {
|
||||||
|
graphic: this.graphic,
|
||||||
|
data: preset.values,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
displayCurrent: function() {
|
||||||
|
this.error = ''
|
||||||
|
|
||||||
|
this.cleanCurrent()
|
||||||
|
|
||||||
|
if (this.error) return
|
||||||
|
|
||||||
|
socket.emit('content.display', {
|
||||||
|
graphic: this.graphic,
|
||||||
|
data: this.current,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
removeProperty: function(prop) {
|
||||||
|
this.graphic.settings.properties.splice(
|
||||||
|
this.graphic.settings.properties.indexOf(prop), 1)
|
||||||
|
socket.emit('graphic.update', this.graphic)
|
||||||
|
},
|
||||||
|
|
||||||
|
switchView: function() {
|
||||||
|
if (Graphic.vm.currentView === 'view') {
|
||||||
|
Graphic.vm.currentView = 'settings'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Graphic.vm.currentView = 'view'
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = Graphic
|
||||||
|
|
||||||
|
require('./view')
|
66
app/controller/graphic/engine/countdown.js
Normal file
66
app/controller/graphic/engine/countdown.js
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
const m = require('mithril')
|
||||||
|
const components = require('../../components')
|
||||||
|
|
||||||
|
exports.view = function(ctlr, graphic, vm) {
|
||||||
|
return [
|
||||||
|
m('label', [
|
||||||
|
'Text',
|
||||||
|
m('input[type=text]', {
|
||||||
|
value: vm.graphic.settings.text || '',
|
||||||
|
oninput: vm.updated.bind(vm, 'settings.text'),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
m('label', [
|
||||||
|
'Count to (format: "YYYY-MM-DD hh:mm")',
|
||||||
|
m('input[type=text]', {
|
||||||
|
value: vm.graphic.settings.countdown || '',
|
||||||
|
oninput: vm.updated.bind(vm, 'settings.countdown'),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
m('label', [
|
||||||
|
'Finished (gets displayed in the countdown upon reaching 0)',
|
||||||
|
m('input[type=text]', {
|
||||||
|
value: vm.graphic.settings.finished || '',
|
||||||
|
oninput: vm.updated.bind(vm, 'settings.finished'),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
components.presetList(vm),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.settings = function(cltr, graphic, vm) {
|
||||||
|
return [
|
||||||
|
m('label', [
|
||||||
|
'Name',
|
||||||
|
m('input[type=text]', {
|
||||||
|
value: graphic.name,
|
||||||
|
oninput: vm.updated.bind(vm, 'name'),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
m('label', [
|
||||||
|
'HTML (',
|
||||||
|
m('a', { href: 'https://lodash.com/docs#template', target: '_blank' }, 'variables'),
|
||||||
|
' available: <%- text %>',
|
||||||
|
')',
|
||||||
|
m('p', `<div id="${graphic.name}">`),
|
||||||
|
m('textarea', {
|
||||||
|
rows: '4',
|
||||||
|
oninput: vm.updated.bind(null, 'settings.html'),
|
||||||
|
value: graphic.settings.html || '',
|
||||||
|
}),
|
||||||
|
m('p', `</div>`),
|
||||||
|
]),
|
||||||
|
m('label', [
|
||||||
|
'CSS',
|
||||||
|
m('textarea', {
|
||||||
|
rows: '4',
|
||||||
|
oninput: vm.updated.bind(null, 'settings.css'),
|
||||||
|
value: graphic.settings.css || '',
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
m('a.panel-graphic-delete.button.alert', {
|
||||||
|
onclick: vm.remove.bind(vm),
|
||||||
|
}, 'Delete graphic'),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
108
app/controller/graphic/engine/text.js
Normal file
108
app/controller/graphic/engine/text.js
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
const m = require('mithril')
|
||||||
|
const components = require('../../components')
|
||||||
|
|
||||||
|
exports.view = function(ctlr, graphic, vm) {
|
||||||
|
if (!graphic.settings.properties) {
|
||||||
|
graphic.settings.properties = []
|
||||||
|
}
|
||||||
|
if (graphic.settings.properties.length === 0) {
|
||||||
|
return [
|
||||||
|
m('p', 'No properties have been defined.'),
|
||||||
|
m('p', 'Click settings to create and define properties to display.'),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
graphic.settings.properties.map((prop, index) =>
|
||||||
|
m('label', { key: index }, [
|
||||||
|
prop,
|
||||||
|
m('input[type=text]', {
|
||||||
|
value: vm.current[prop] || '',
|
||||||
|
oninput: vm.updated.bind(vm, prop, 'current'),
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
),
|
||||||
|
components.presetList(vm),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.settings = function(cltr, graphic, vm) {
|
||||||
|
return [
|
||||||
|
m('label', [
|
||||||
|
'Name',
|
||||||
|
m('input[type=text]', {
|
||||||
|
value: graphic.name,
|
||||||
|
oninput: vm.updated.bind(vm, 'name'),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
m('label', [
|
||||||
|
'HTML (',
|
||||||
|
m('a', { href: 'https://lodash.com/docs#template', target: '_blank' }, 'variables'),
|
||||||
|
' available: ',
|
||||||
|
graphic.settings.properties.map(prop =>
|
||||||
|
`<%- ${prop} %>`
|
||||||
|
).join(', '),
|
||||||
|
')',
|
||||||
|
m('p', `<div id="${graphic.name}">`),
|
||||||
|
m('textarea', {
|
||||||
|
rows: '4',
|
||||||
|
oninput: vm.updated.bind(null, 'settings.html'),
|
||||||
|
value: graphic.settings.html || '',
|
||||||
|
}),
|
||||||
|
m('p', `</div>`),
|
||||||
|
]),
|
||||||
|
m('label', [
|
||||||
|
'CSS',
|
||||||
|
m('textarea', {
|
||||||
|
rows: '4',
|
||||||
|
oninput: vm.updated.bind(null, 'settings.css'),
|
||||||
|
value: graphic.settings.css || '',
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
m('label', [
|
||||||
|
'Main property',
|
||||||
|
m('select', {
|
||||||
|
onchange: vm.updated.bind(vm, 'settings.main'),
|
||||||
|
}, graphic.settings.properties.map((prop, index) =>
|
||||||
|
m('option', {
|
||||||
|
key: 'prop-list-' + index,
|
||||||
|
value: prop,
|
||||||
|
selected: prop === graphic.settings.main,
|
||||||
|
}, prop)
|
||||||
|
))
|
||||||
|
]),
|
||||||
|
m('label', 'Properties'),
|
||||||
|
m('div', [
|
||||||
|
graphic.settings.properties.map((prop, index) =>
|
||||||
|
m('.row', { key: 'add-prop-' + index }, [
|
||||||
|
m('div', { class: 'small-10 columns panel-graphic-property-item' },
|
||||||
|
m('input[type=text]', {
|
||||||
|
readonly: true,
|
||||||
|
value: prop,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
m('div', { class: 'small-2 columns' },
|
||||||
|
m('a.panel-graphic-property-remove.button.alert', {
|
||||||
|
onclick: vm.removeProperty.bind(vm, prop),
|
||||||
|
}, 'Remove')
|
||||||
|
)
|
||||||
|
])
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
m('.row', [
|
||||||
|
m('div', { class: 'small-10 columns panel-graphic-property-item' },
|
||||||
|
m('input[type=text]', {
|
||||||
|
value: vm.newProperty(),
|
||||||
|
oninput: m.withAttr('value', vm.newProperty),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
m('div', { class: 'small-2 columns' },
|
||||||
|
m('a.panel-graphic-property-add.button', {
|
||||||
|
onclick: vm.addProperty.bind(vm),
|
||||||
|
}, 'Add')
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
m('a.panel-graphic-delete.button.alert', {
|
||||||
|
onclick: vm.remove.bind(vm),
|
||||||
|
}, 'Delete graphic'),
|
||||||
|
]
|
||||||
|
}
|
27
app/controller/graphic/view.js
Normal file
27
app/controller/graphic/view.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
const m = require('mithril')
|
||||||
|
const Graphic = require('./controller')
|
||||||
|
const components = require('../components')
|
||||||
|
|
||||||
|
const engines = {
|
||||||
|
text: require('./engine/text'),
|
||||||
|
countdown: require('./engine/countdown'),
|
||||||
|
}
|
||||||
|
|
||||||
|
Graphic.view = function(ctrl) {
|
||||||
|
graphic = Graphic.vm.graphic
|
||||||
|
|
||||||
|
return m('div', [
|
||||||
|
m('h3.container-header', 'Graphic'),
|
||||||
|
m('div.container-panel.panel-graphic',
|
||||||
|
!graphic.name && m('p', 'Loading...') ||
|
||||||
|
[
|
||||||
|
m('a.panel-graphic-settings.button', {
|
||||||
|
onclick: Graphic.vm.switchView
|
||||||
|
}, Graphic.vm.currentView === 'view' && 'Settings' || 'Control'),
|
||||||
|
m('h4', graphic.name),
|
||||||
|
components.error(Graphic.vm.error),
|
||||||
|
engines[graphic.engine][Graphic.vm.currentView](ctrl, graphic, Graphic.vm),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
])
|
||||||
|
}
|
30
app/controller/header.js
Normal file
30
app/controller/header.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
const m = require('mithril')
|
||||||
|
const createModule = require('./module')
|
||||||
|
const socket = require('../socket')
|
||||||
|
|
||||||
|
const Header = createModule({
|
||||||
|
init: function() {
|
||||||
|
this.monitor('list', 'content.list', [])
|
||||||
|
},
|
||||||
|
|
||||||
|
hide: function(item) {
|
||||||
|
socket.emit('content.hide', {
|
||||||
|
name: item.name,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}, function(ctrl) {
|
||||||
|
return m('div.header', Header.vm.list.length > 0 && [
|
||||||
|
m('h3.container-header', 'Currently active'),
|
||||||
|
m('ul.header-list', [
|
||||||
|
Header.vm.list.map((item, index) =>
|
||||||
|
m('li.header-item', { key: 'header-' + index, }, [
|
||||||
|
m('a.header-item-hide.button.alert', {
|
||||||
|
onclick: Header.vm.hide.bind(Header.vm, item),
|
||||||
|
}, 'Hide'),
|
||||||
|
m('div.header-item-display', `${item.name} - ${item.display}`),
|
||||||
|
])
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
] || '')
|
||||||
|
})
|
||||||
|
module.exports = Header
|
|
@ -1,23 +1,24 @@
|
||||||
const m = require('mithril')
|
const m = require('mithril')
|
||||||
|
const createModule = require('./module')
|
||||||
|
|
||||||
const Menu = {
|
const Menu = createModule({
|
||||||
controller: function() {
|
init: function() {
|
||||||
return {}
|
this.monitor('list', 'graphic.all', [])
|
||||||
},
|
}
|
||||||
|
}, function(ctrl) {
|
||||||
view: function(ctrl) {
|
return m('div', [
|
||||||
return m('div', [
|
m('h3.container-header', 'Graphics'),
|
||||||
m('h3', 'Menu'),
|
m('div.container-panel.menu', [
|
||||||
m('ul', [
|
m('ul.menu-list', [
|
||||||
m('li', [
|
// m('a', { href: `/`, config: m.route }, 'Home'),
|
||||||
m('a', { href: '/', config: m.route }, 'Home'),
|
Menu.vm.list.map((item) =>
|
||||||
]),
|
m('li.menu-item', [
|
||||||
m('li', [
|
m('a', { href: `/graphic/${item.id}`, config: m.route }, item.name),
|
||||||
m('a', { href: '/content', config: m.route }, 'Content'),
|
])
|
||||||
])
|
)
|
||||||
]),
|
]),
|
||||||
])
|
m('a.menu-item-add', { href: '/add', config: m.route }, 'Add graphic' ),
|
||||||
},
|
]),
|
||||||
}
|
])
|
||||||
|
})
|
||||||
module.exports = Menu
|
module.exports = Menu
|
||||||
|
|
51
app/controller/module.js
Normal file
51
app/controller/module.js
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
const m = require('mithril')
|
||||||
|
const _ = require('lodash')
|
||||||
|
const store = require('./store')
|
||||||
|
const socket = require('../socket')
|
||||||
|
|
||||||
|
function createModule(vm, view) {
|
||||||
|
let newModule = { }
|
||||||
|
let listening = []
|
||||||
|
|
||||||
|
newModule.vm = _.defaults(vm, {
|
||||||
|
_init: function() {
|
||||||
|
this.error = null
|
||||||
|
newModule.vm.init()
|
||||||
|
},
|
||||||
|
|
||||||
|
_storeUpdated: function(key, name, id) {
|
||||||
|
this[key] = store.get(name, id)
|
||||||
|
m.redraw()
|
||||||
|
},
|
||||||
|
|
||||||
|
init: function() { },
|
||||||
|
|
||||||
|
monitor: function(key, name, fallback, id) {
|
||||||
|
this[key] = store.get(name, id) || fallback || { }
|
||||||
|
|
||||||
|
listening.push(name)
|
||||||
|
|
||||||
|
store.listen(name, this._storeUpdated.bind(this, key, name, id), id)
|
||||||
|
|
||||||
|
socket.emit(name, { id: id })
|
||||||
|
},
|
||||||
|
|
||||||
|
onunload: function() {
|
||||||
|
listening.forEach((item) => {
|
||||||
|
store.unlisten(item)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
newModule.controller = function() {
|
||||||
|
newModule.vm._init()
|
||||||
|
|
||||||
|
this.onunload = newModule.vm.onunload
|
||||||
|
}
|
||||||
|
|
||||||
|
newModule.view = view
|
||||||
|
|
||||||
|
return newModule
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = createModule
|
|
@ -1,39 +1,62 @@
|
||||||
|
const _ = require('lodash')
|
||||||
const socket = require('../socket')
|
const socket = require('../socket')
|
||||||
const storage = {}
|
const storage = {}
|
||||||
const events = {}
|
const events = {}
|
||||||
|
|
||||||
|
// Listen on all events
|
||||||
|
let onevent = socket.onevent
|
||||||
|
|
||||||
|
socket.onevent = function(packet) {
|
||||||
|
let args = packet.data || []
|
||||||
|
onevent.call(this, packet) // original call
|
||||||
|
packet.data = ['*'].concat(args)
|
||||||
|
onevent.call(this, packet)
|
||||||
|
}
|
||||||
|
|
||||||
|
function genId(name, id) {
|
||||||
|
if (id) {
|
||||||
|
return `${name}:${id}`
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
const store = {
|
const store = {
|
||||||
get: function(name) {
|
get: function(name, id) {
|
||||||
return storage[name]
|
return storage[genId(name, id)]
|
||||||
},
|
},
|
||||||
|
|
||||||
set: function(name, value, dontSend) {
|
listen: function(name, caller, id) {
|
||||||
storage[name] = value
|
events[genId(name, id)] = caller
|
||||||
|
|
||||||
if (dontSend) {
|
|
||||||
if (events[name]) {
|
|
||||||
events[name]()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.emit('store', {
|
|
||||||
name,
|
|
||||||
value,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
listen: function(name, caller) {
|
|
||||||
events[name] = caller
|
|
||||||
},
|
},
|
||||||
|
|
||||||
unlisten: function(name) {
|
unlisten: function(name) {
|
||||||
delete events[name]
|
delete events[name]
|
||||||
|
delete storage[name]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
events: events,
|
||||||
|
storage: storage,
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.on('store', (data) => {
|
socket.on('*', (event, data) => {
|
||||||
store.set(data.name, data.value, true)
|
let name = genId(event, data && data.id)
|
||||||
|
if (events[name]) {
|
||||||
|
storage[name] = data
|
||||||
|
events[name]()
|
||||||
|
}
|
||||||
|
if (event.contains('single')) {
|
||||||
|
let check = event.replace('single', 'all')
|
||||||
|
if (events[name]) {
|
||||||
|
let index = _.findIndex(storage[check], { id: data.id })
|
||||||
|
if (index > -1) {
|
||||||
|
storage[check][index] = data
|
||||||
|
events[name]()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
window.store = store
|
||||||
|
|
||||||
module.exports = store
|
module.exports = store
|
||||||
|
|
67
app/frontend/countdown.js
Normal file
67
app/frontend/countdown.js
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
|
||||||
|
module.exports = function(data) {
|
||||||
|
var element = document.createElement('div')
|
||||||
|
element.innerHTML = data.html
|
||||||
|
element.id = data.graphic.name
|
||||||
|
element.classList.add('root-element')
|
||||||
|
|
||||||
|
var styleElement = document.createElement('style')
|
||||||
|
styleElement.setAttribute('type', 'text/css')
|
||||||
|
styleElement.innerHTML = data.css
|
||||||
|
|
||||||
|
element.tag = styleElement
|
||||||
|
|
||||||
|
document.body.appendChild(element)
|
||||||
|
document.head.appendChild(styleElement)
|
||||||
|
|
||||||
|
window.setTimeout(function (){
|
||||||
|
element.classList.add('root-element-display')
|
||||||
|
}, 100)
|
||||||
|
|
||||||
|
var timeElement = document.getElementById(data.graphic.name + '-countdown-timer')
|
||||||
|
var time = new Date(data.data.countdown.replace(' ', 'T'))
|
||||||
|
|
||||||
|
function pad(n) { return (n < 10) ? ('0' + n) : n }
|
||||||
|
|
||||||
|
function timer() {
|
||||||
|
var days = 0
|
||||||
|
var hours = 0
|
||||||
|
var mins = 0
|
||||||
|
var secs = 0
|
||||||
|
|
||||||
|
now = new Date()
|
||||||
|
difference = (time - now)
|
||||||
|
|
||||||
|
timeElement = document.getElementById(data.graphic.name + '-countdown-timer')
|
||||||
|
|
||||||
|
if (difference < 0 || !timeElement) {
|
||||||
|
clearInterval(data.timer)
|
||||||
|
if (timeElement) {
|
||||||
|
timeElement.innerHTML = data.data.finished || ''
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeElement.tag !== time) {
|
||||||
|
clearInterval(data.timer)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
days = Math.floor(difference / (60 * 60 * 1000 * 24) * 1);
|
||||||
|
hours = Math.floor((difference % (60 * 60 * 1000 * 24)) / (60 * 60 * 1000) );
|
||||||
|
mins = Math.floor(((difference % (60 * 60 * 1000 * 24)) % (60 * 60 * 1000)) / (60 * 1000) * 1);
|
||||||
|
secs = Math.floor((((difference % (60 * 60 * 1000 * 24)) % (60 * 60 * 1000)) % (60 * 1000)) / 1000 * 1);
|
||||||
|
|
||||||
|
var text = pad(hours) + ':' + pad(mins) + ':' + pad(secs);
|
||||||
|
if (days > 0) {
|
||||||
|
text = days.toString() + ' dag' + (days > 1 && 'a' || '') + ' ' + text;
|
||||||
|
}
|
||||||
|
timeElement.innerHTML = text
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeElement) {
|
||||||
|
timeElement.tag = time
|
||||||
|
timer()
|
||||||
|
data.timer = setInterval(timer, 1000)
|
||||||
|
}
|
||||||
|
}
|
20
app/frontend/text.js
Normal file
20
app/frontend/text.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
|
||||||
|
module.exports = function(data) {
|
||||||
|
var element = document.createElement('div')
|
||||||
|
element.innerHTML = data.html
|
||||||
|
element.id = data.graphic.name
|
||||||
|
element.classList.add('root-element')
|
||||||
|
|
||||||
|
var styleElement = document.createElement('style')
|
||||||
|
styleElement.setAttribute('type', 'text/css')
|
||||||
|
styleElement.innerHTML = data.css
|
||||||
|
|
||||||
|
element.tag = styleElement
|
||||||
|
|
||||||
|
document.body.appendChild(element)
|
||||||
|
document.head.appendChild(styleElement)
|
||||||
|
|
||||||
|
window.setTimeout(function (){
|
||||||
|
element.classList.add('root-element-display')
|
||||||
|
}, 100)
|
||||||
|
}
|
|
@ -16,12 +16,16 @@ require('./socket')
|
||||||
require('./controller/store')
|
require('./controller/store')
|
||||||
|
|
||||||
const m = require('mithril')
|
const m = require('mithril')
|
||||||
|
const Header = require('./controller/header')
|
||||||
const Menu = require('./controller/menu')
|
const Menu = require('./controller/menu')
|
||||||
const Content = require('./controller/content')
|
const Add = require('./controller/add')
|
||||||
|
const Graphic = require('./controller/graphic/controller')
|
||||||
|
|
||||||
|
m.mount(document.getElementById('header'), Header)
|
||||||
m.mount(document.getElementById('menu'), Menu)
|
m.mount(document.getElementById('menu'), Menu)
|
||||||
|
|
||||||
m.route(document.getElementById('content'), '/', {
|
m.route(document.getElementById('content'), '/', {
|
||||||
'/': {},
|
'/': {},
|
||||||
'/content': Content,
|
'/add': Add,
|
||||||
|
'/graphic/:id': Graphic,
|
||||||
});
|
});
|
||||||
|
|
29
migrations/20160412123858_graphics.js
Normal file
29
migrations/20160412123858_graphics.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
/* eslint-disable */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
exports.up = function(knex, Promise) {
|
||||||
|
return Promise.all([
|
||||||
|
knex.schema.dropTable('store'),
|
||||||
|
knex.schema.createTable('graphics', function(table) {
|
||||||
|
table.increments()
|
||||||
|
table.text('name')
|
||||||
|
table.text('engine')
|
||||||
|
table.text('settings')
|
||||||
|
table.boolean('is_deleted')
|
||||||
|
}),
|
||||||
|
knex.schema.createTable('presets', function(table) {
|
||||||
|
table.increments()
|
||||||
|
table.integer('graphic_id').references('graphics.id')
|
||||||
|
table.text('values')
|
||||||
|
table.integer('sort')
|
||||||
|
table.boolean('is_deleted')
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function(knex, Promise) {
|
||||||
|
return Promise.all([
|
||||||
|
knex.schema.dropTable('graphics'),
|
||||||
|
knex.schema.dropTable('presets'),
|
||||||
|
]);
|
||||||
|
};
|
|
@ -11,7 +11,7 @@
|
||||||
"watch-client:js": "watchify app/client.js -o public/client.js --debug",
|
"watch-client:js": "watchify app/client.js -o public/client.js --debug",
|
||||||
"build": "npm run build-main:js && npm run build-client:js",
|
"build": "npm run build-main:js && npm run build-client:js",
|
||||||
"build:watch": "parallelshell \"npm run watch-main:js\" \"npm run watch-client:js\"",
|
"build:watch": "parallelshell \"npm run watch-main:js\" \"npm run watch-client:js\"",
|
||||||
"start": "node index.js",
|
"start": "node index.js | bunyan",
|
||||||
"start:dev": "nodemon index.js | bunyan"
|
"start:dev": "nodemon index.js | bunyan"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
|
@ -22,6 +22,20 @@ body {
|
||||||
text-shadow: 2px 2px 1px #000000;
|
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;
|
||||||
|
}
|
||||||
|
div
|
||||||
|
{
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
.root-element {
|
.root-element {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 1s;
|
transition: opacity 1s;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>CasparCG Client</title>
|
<title>CasparCG Client</title>
|
||||||
<link href="client.css" rel="stylesheet" />
|
<link href="/client.css" rel="stylesheet" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script src="client.js"></script>
|
<script src="client.js"></script>
|
||||||
|
|
|
@ -6,9 +6,12 @@
|
||||||
<link href="main.css" rel="stylesheet" />
|
<link href="main.css" rel="stylesheet" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="row">
|
<div class="expanded row">
|
||||||
<div class="small-3 columns" id="menu"></div>
|
<div class="small-12 columns container" id="header"></div>
|
||||||
<div class="small-9 columns" id="content"></div>
|
</div>
|
||||||
|
<div class="expanded row">
|
||||||
|
<div class="small-3 columns container" id="menu"></div>
|
||||||
|
<div class="small-9 columns container" id="content"></div>
|
||||||
</div>
|
</div>
|
||||||
<script src="main.js"></script>
|
<script src="main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
185
public/main.css
185
public/main.css
|
@ -1,4 +1,181 @@
|
||||||
a.button {
|
body {
|
||||||
margin: 0 1rem;
|
background: #3f3f41;
|
||||||
width: 10rem;
|
color: #f1f1f1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Container */
|
||||||
|
|
||||||
|
.container {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-header {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-left: 1rem;
|
||||||
|
color: #777777;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-panel {
|
||||||
|
border: 1px solid #3f3f3f;
|
||||||
|
background: #2d2d30;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
|
||||||
|
.header-list {
|
||||||
|
list-style-type: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-item-hide {
|
||||||
|
float: right;
|
||||||
|
width: 5rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-item-display {
|
||||||
|
background: #070707;
|
||||||
|
color: #eb6e00;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
margin-right: 5.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Menu */
|
||||||
|
|
||||||
|
.menu-list {
|
||||||
|
list-style-type: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu a {
|
||||||
|
color: #007acc;
|
||||||
|
display: block;
|
||||||
|
border: 1px solid #2d2d30;
|
||||||
|
padding: 0.2rem 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu a:hover {
|
||||||
|
color: #f1f1f1;
|
||||||
|
border: 1px solid #007acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item-add {
|
||||||
|
margin-top: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add */
|
||||||
|
|
||||||
|
.panel-add {
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-graphic-property-add,
|
||||||
|
.panel-graphic-property-remove {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Graphic */
|
||||||
|
|
||||||
|
.panel-graphic-delete {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-graphic-settings {
|
||||||
|
float: right;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-graphic-property-item {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-graphic-preset-add {
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-graphic-preset {
|
||||||
|
margin-top: 1rem;
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-graphic-preset a {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Components */
|
||||||
|
|
||||||
|
.error-box {
|
||||||
|
margin: 0rem 0rem 2rem 0;
|
||||||
|
color: #FF0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inputs */
|
||||||
|
|
||||||
|
label {
|
||||||
|
color: #f1f1f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"],
|
||||||
|
textarea {
|
||||||
|
background: #333337;
|
||||||
|
border-color: #3f3f3f;
|
||||||
|
color: #999999;
|
||||||
|
transition-property: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"]:hover,
|
||||||
|
textarea:hover {
|
||||||
|
color: #f1f1f1;
|
||||||
|
border-color: #007acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"]:focus,
|
||||||
|
textarea:focus {
|
||||||
|
background: #333337;
|
||||||
|
color: #f1f1f1;
|
||||||
|
border-color: #007acc;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[readonly],
|
||||||
|
input[readonly]:hover {
|
||||||
|
background: #2d2d30 !important;
|
||||||
|
border-color: #3f3f3f;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
background: #333337;
|
||||||
|
border-color: #3f3f3f;
|
||||||
|
color: #999999;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
select
|
||||||
|
|
||||||
|
a.button {
|
||||||
|
margin: 0 1rem;
|
||||||
|
width: 10rem;
|
||||||
|
}
|
||||||
|
|
1
run.bat
Normal file
1
run.bat
Normal file
|
@ -0,0 +1 @@
|
||||||
|
npm run build && npm start
|
|
@ -37,12 +37,8 @@ shelf.createModel = (attr, opts) => {
|
||||||
this.on('fetching', this.checkFetching)
|
this.on('fetching', this.checkFetching)
|
||||||
},
|
},
|
||||||
|
|
||||||
remove() {
|
|
||||||
return this.destroy()
|
|
||||||
},
|
|
||||||
|
|
||||||
checkFetching(model, columns, options) {
|
checkFetching(model, columns, options) {
|
||||||
// options.query.where({ is_deleted: false })
|
options.query.where({ is_deleted: false })
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -52,7 +48,7 @@ shelf.createModel = (attr, opts) => {
|
||||||
return this.forge(data).save()
|
return this.forge(data).save()
|
||||||
},
|
},
|
||||||
|
|
||||||
getSingle(id, withRelated = [], required = true) {
|
getSingle(id, withRelated = [], require = true) {
|
||||||
let where = { id: Number(id) || 0 }
|
let where = { id: Number(id) || 0 }
|
||||||
|
|
||||||
return this.query({ where })
|
return this.query({ where })
|
||||||
|
@ -60,6 +56,8 @@ shelf.createModel = (attr, opts) => {
|
||||||
},
|
},
|
||||||
|
|
||||||
getAll(where = {}, withRelated = []) {
|
getAll(where = {}, withRelated = []) {
|
||||||
|
where.is_deleted = false
|
||||||
|
|
||||||
return this.query({ where })
|
return this.query({ where })
|
||||||
.fetchAll({ withRelated })
|
.fetchAll({ withRelated })
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import Store from './store/model'
|
|
||||||
|
import { reset, list } from './content/routes'
|
||||||
|
|
||||||
export function register(ctx, name, method) {
|
export function register(ctx, name, method) {
|
||||||
if (_.isPlainObject(method)) {
|
if (_.isPlainObject(method)) {
|
||||||
|
@ -10,9 +11,7 @@ export function register(ctx, name, method) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.socket.on(name, async (data) => {
|
ctx.socket.on(name, async (data) => {
|
||||||
if (name !== 'store') {
|
ctx.log.info('Got event', name)
|
||||||
ctx.log.info(`Got event ${name}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await method(ctx, data)
|
await method(ctx, data)
|
||||||
|
@ -26,9 +25,6 @@ export function register(ctx, name, method) {
|
||||||
export async function newConnection(ctx) {
|
export async function newConnection(ctx) {
|
||||||
ctx.log.info('Got new socket connection')
|
ctx.log.info('Got new socket connection')
|
||||||
|
|
||||||
let data = await Store.getAll()
|
list(ctx)
|
||||||
|
reset(ctx)
|
||||||
data.forEach(item =>
|
|
||||||
ctx.socket.emit('store', item.toJSON())
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,55 @@
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
export const active = { }
|
||||||
|
|
||||||
|
function getSocket(ctx, all) {
|
||||||
|
if (all === true) return ctx.io
|
||||||
|
return ctx.socket
|
||||||
|
}
|
||||||
|
|
||||||
export function display(ctx, data) {
|
export function display(ctx, data) {
|
||||||
let compiled = _.template(data.html)
|
let compiled = _.template(data.graphic.settings.html)
|
||||||
let html = compiled(data)
|
let html = compiled(data.data)
|
||||||
|
|
||||||
ctx.io.emit('client.display', {
|
let payload = {
|
||||||
key: 'content',
|
graphic: data.graphic,
|
||||||
html,
|
html,
|
||||||
css: data.css,
|
css: data.graphic.settings.css,
|
||||||
})
|
data: data.data,
|
||||||
|
}
|
||||||
|
|
||||||
|
active[data.graphic.name] = payload
|
||||||
|
ctx.io.emit('client.display', payload)
|
||||||
|
|
||||||
|
list(ctx, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hide(ctx) {
|
export function hide(ctx, data) {
|
||||||
|
delete active[data.name]
|
||||||
|
|
||||||
ctx.io.emit('client.hide', {
|
ctx.io.emit('client.hide', {
|
||||||
key: 'content',
|
name: data.name,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
list(ctx, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateDisplayText(item) {
|
||||||
|
if (item.graphic.engine === 'countdown') {
|
||||||
|
return `${item.data[item.graphic.settings.main]} - ${item.data.countdown}`
|
||||||
|
}
|
||||||
|
return item.data[item.graphic.settings.main]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function list(ctx, all) {
|
||||||
|
let payload = Object.keys(active).map(key => ({
|
||||||
|
name: active[key].graphic.name,
|
||||||
|
display: generateDisplayText(active[key]),
|
||||||
|
}))
|
||||||
|
|
||||||
|
getSocket(ctx, all).emit('content.list', payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function reset(ctx) {
|
||||||
|
ctx.socket.emit('client.reset', _.values(active))
|
||||||
}
|
}
|
||||||
|
|
4
server/io/engine/routes.js
Normal file
4
server/io/engine/routes.js
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
export function all(ctx) {
|
||||||
|
ctx.socket.emit('engine.all', ['text', 'countdown'])
|
||||||
|
}
|
30
server/io/graphic/model.js
Normal file
30
server/io/graphic/model.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import bookshelf from '../../bookshelf'
|
||||||
|
|
||||||
|
/* Graphic model:
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
engine,
|
||||||
|
settings,
|
||||||
|
is_deleted,
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
const Graphic = bookshelf.createModel({
|
||||||
|
tableName: 'graphics',
|
||||||
|
|
||||||
|
format(attributes) {
|
||||||
|
attributes.settings = JSON.stringify(attributes.settings)
|
||||||
|
return attributes
|
||||||
|
},
|
||||||
|
|
||||||
|
parse(attributes) {
|
||||||
|
if (attributes.settings) {
|
||||||
|
attributes.settings = JSON.parse(attributes.settings)
|
||||||
|
}
|
||||||
|
return attributes
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
})
|
||||||
|
|
||||||
|
export default Graphic
|
65
server/io/graphic/routes.js
Normal file
65
server/io/graphic/routes.js
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import Graphic from './model'
|
||||||
|
|
||||||
|
function getSocket(ctx, all) {
|
||||||
|
if (all === true) return ctx.io
|
||||||
|
return ctx.socket
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function all(ctx, all) {
|
||||||
|
let data = await Graphic.getAll()
|
||||||
|
|
||||||
|
getSocket(ctx, all).emit('graphic.all', data.toJSON())
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function single(ctx, data, all) {
|
||||||
|
if (!data || !data.id) {
|
||||||
|
ctx.log.warn('called graphic get single but no id specified')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let graphic = await Graphic.getSingle(data.id)
|
||||||
|
|
||||||
|
getSocket(ctx, all).emit('graphic.single', graphic.toJSON())
|
||||||
|
}
|
||||||
|
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
|
||||||
|
await Graphic.create(data)
|
||||||
|
|
||||||
|
await all(ctx, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function remove(ctx, data) {
|
||||||
|
if (!data || !data.id) {
|
||||||
|
ctx.log.warn('called graphic get single but no id specified')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let graphic = await Graphic.getSingle(data.id)
|
||||||
|
graphic.set({ is_deleted: true })
|
||||||
|
await graphic.save()
|
||||||
|
|
||||||
|
await all(ctx, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function update(ctx, data) {
|
||||||
|
if (!data || !data.id) {
|
||||||
|
ctx.log.warn('called graphic update but no id specified')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let graphic = await Graphic.getSingle(data.id)
|
||||||
|
|
||||||
|
graphic.set(data)
|
||||||
|
|
||||||
|
await graphic.save()
|
||||||
|
|
||||||
|
await single(ctx, data, true)
|
||||||
|
}
|
30
server/io/preset/model.js
Normal file
30
server/io/preset/model.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import bookshelf from '../../bookshelf'
|
||||||
|
|
||||||
|
/* Preset model:
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
graphic_id,
|
||||||
|
values,
|
||||||
|
sort,
|
||||||
|
is_deleted,
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
const Preset = bookshelf.createModel({
|
||||||
|
tableName: 'presets',
|
||||||
|
|
||||||
|
format(attributes) {
|
||||||
|
attributes.values = JSON.stringify(attributes.values)
|
||||||
|
return attributes
|
||||||
|
},
|
||||||
|
|
||||||
|
parse(attributes) {
|
||||||
|
if (attributes.values) {
|
||||||
|
attributes.values = JSON.parse(attributes.values)
|
||||||
|
}
|
||||||
|
return attributes
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
})
|
||||||
|
|
||||||
|
export default Preset
|
43
server/io/preset/routes.js
Normal file
43
server/io/preset/routes.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import Preset from './model'
|
||||||
|
|
||||||
|
function getSocket(ctx, all) {
|
||||||
|
if (all === true) return ctx.io
|
||||||
|
return ctx.socket
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function all(ctx, payload, all) {
|
||||||
|
let id = Number(payload.graphic_id || payload.id)
|
||||||
|
|
||||||
|
let data = await Preset.getAll({ graphic_id: id })
|
||||||
|
|
||||||
|
getSocket(ctx, all).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, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
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, true)
|
||||||
|
}
|
|
@ -2,7 +2,9 @@ import logger from '../../log'
|
||||||
import { register, newConnection } from './connection'
|
import { register, newConnection } from './connection'
|
||||||
|
|
||||||
import * as content from './content/routes'
|
import * as content from './content/routes'
|
||||||
import * as store from './store/routes'
|
import * as engine from './engine/routes'
|
||||||
|
import * as graphic from './graphic/routes'
|
||||||
|
import * as preset from './preset/routes'
|
||||||
|
|
||||||
function onConnection(server, data) {
|
function onConnection(server, data) {
|
||||||
const io = server.socket
|
const io = server.socket
|
||||||
|
@ -16,7 +18,9 @@ function onConnection(server, data) {
|
||||||
newConnection(ctx)
|
newConnection(ctx)
|
||||||
|
|
||||||
register(ctx, 'content', content)
|
register(ctx, 'content', content)
|
||||||
register(ctx, 'store', store.updateStore)
|
register(ctx, 'engine', engine)
|
||||||
|
register(ctx, 'graphic', graphic)
|
||||||
|
register(ctx, 'preset', preset)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default onConnection
|
export default onConnection
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
import bookshelf from '../../bookshelf'
|
|
||||||
|
|
||||||
const Store = bookshelf.createModel({
|
|
||||||
tableName: 'store',
|
|
||||||
|
|
||||||
format(attributes) {
|
|
||||||
attributes.value = JSON.stringify(attributes.value)
|
|
||||||
return attributes
|
|
||||||
},
|
|
||||||
|
|
||||||
parse(attributes) {
|
|
||||||
attributes.value = JSON.parse(attributes.value)
|
|
||||||
return attributes
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
getSingle(name, withRelated = [], required = true) {
|
|
||||||
let where = { name }
|
|
||||||
|
|
||||||
return this.query({ where })
|
|
||||||
.fetch({ require, withRelated })
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export default Store
|
|
|
@ -1,11 +0,0 @@
|
||||||
import Store from './model'
|
|
||||||
|
|
||||||
export async function updateStore(ctx, data) {
|
|
||||||
let item = await Store.getSingle(data.name)
|
|
||||||
|
|
||||||
item.set('value', data.value)
|
|
||||||
|
|
||||||
await item.save()
|
|
||||||
|
|
||||||
ctx.socket.broadcast.emit('store', item.toJSON())
|
|
||||||
}
|
|
2
update_run.bat
Normal file
2
update_run.bat
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
git pull
|
||||||
|
npm install && run
|
Loading…
Reference in a new issue