├── media ├── client │ ├── media.js │ ├── subscriptions.js │ ├── templates │ │ ├── items.html │ │ ├── media.html │ │ ├── mediaMenu.html │ │ ├── playlistdisplay.html │ │ ├── playlists.html │ │ ├── playlist.html │ │ ├── item.html │ │ ├── mediasettings.html │ │ └── playlistsettings.html │ ├── item.js │ ├── items.js │ ├── playlists.js │ ├── mediasettings.js │ └── playlistsettings.js ├── lib │ ├── collections.js │ └── routes.js └── server │ ├── publications.js │ ├── media.js │ └── playlists.js ├── lighting ├── client │ ├── console.css │ ├── scene.js │ ├── scene.css │ ├── templates │ │ ├── group.html │ │ ├── scene.html │ │ ├── light.html │ │ ├── menu.html │ │ ├── valueselector.html │ │ ├── groups.html │ │ ├── consoles.html │ │ ├── scenes.html │ │ ├── lights.html │ │ ├── channel.html │ │ ├── settings.html │ │ └── consolepanel.html │ ├── light.js │ ├── subscriptions.js │ ├── channel.js │ ├── groups.js │ ├── consoles.js │ ├── lights.js │ ├── scenes.js │ ├── valueselector.css │ ├── valueselector.js │ ├── consolepanel.js │ ├── groupsettings.js │ ├── settings.js │ └── scenesettings.js ├── lib │ ├── collections.js │ └── routes.js └── server │ ├── publications.js │ ├── lights.js │ └── consoles.js ├── .meteor ├── release ├── .gitignore ├── platforms ├── .id ├── .finished-upgraders ├── packages └── versions ├── main ├── lib │ ├── constants.js │ ├── collections.js │ ├── routes.js │ └── settings.js ├── client │ ├── subscriptions.js │ ├── templates │ │ ├── colorpicker.html │ │ ├── menu.html │ │ ├── main.html │ │ ├── collectionselector.html │ │ └── layout.html │ ├── layout.css │ ├── layout.js │ ├── util.js │ ├── colorpicker.js │ ├── time.js │ └── collectionselector.js └── server │ ├── time.js │ ├── publications.js │ ├── customactions.js │ ├── upload.js │ ├── settings.js │ ├── update.js │ └── actionactivate.js ├── schedule ├── client │ ├── subscriptions.js │ ├── templates │ │ ├── display.html │ │ ├── schedules.html │ │ ├── scheduleItem.html │ │ └── schedule.html │ ├── schedules.js │ ├── scheduleitem.js │ └── schedule.js ├── lib │ ├── collections.js │ └── routes.js └── server │ ├── publications.js │ └── schedule.js ├── sequences ├── client │ ├── subscriptions.js │ ├── settings.css │ ├── templates │ │ ├── sequence.html │ │ ├── display.html │ │ ├── sequences.html │ │ └── actiondisplay.html │ ├── actiondisplay.js │ └── sequences.js ├── lib │ ├── collections.js │ └── routes.js └── server │ ├── publications.js │ ├── sequences.js │ └── sequencehandler.js ├── sets ├── client │ ├── subscriptions.js │ ├── templates │ │ ├── settime.html │ │ ├── specialClearLayer.html │ │ ├── specialCustom.html │ │ ├── specialCamera.html │ │ ├── specialClearChannel.html │ │ ├── setdisplay.html │ │ ├── sets.html │ │ ├── specialTimer.html │ │ ├── action.html │ │ ├── sidebar.html │ │ ├── specialactions.html │ │ └── actiondisplay.html │ ├── settime.js │ ├── specialTimer.js │ ├── action.js │ ├── sets.js │ ├── set.css │ ├── sidebar.js │ ├── sidebar.css │ ├── actiondisplay.js │ └── actionselector.js ├── lib │ ├── collections.js │ └── routes.js └── server │ └── publications.js ├── minions ├── client │ ├── subscriptions.js │ ├── web │ │ └── mediaminion │ │ │ ├── mediaminion.html │ │ │ ├── mediaminion.css │ │ │ └── clearlayer.js │ ├── templates │ │ ├── menu.html │ │ ├── minions.html │ │ ├── minion.html │ │ ├── stages.html │ │ ├── settingsdisplay.html │ │ ├── settingstimers.html │ │ └── stagesettings.html │ ├── minions.js │ ├── settingssongs.js │ ├── settingstimers.js │ ├── settingspresentations.js │ ├── stages.js │ ├── stagesettings.js │ └── settings.js ├── lib │ ├── collections.js │ └── routes.js └── server │ ├── publications.js │ ├── stages.js │ └── minions.js ├── .gitmodules ├── presentations ├── client │ ├── subscriptions.js │ ├── templates │ │ ├── presentation.html │ │ ├── slideselector.html │ │ ├── displayimportedslide.html │ │ ├── display.html │ │ ├── menuitem.html │ │ ├── menu.html │ │ ├── importedslide.html │ │ └── displayslide.html │ ├── menuitem.js │ ├── slide.css │ ├── slideselector.js │ ├── displayimportedslide.js │ ├── display.js │ ├── importedslide.js │ ├── menu.js │ ├── display.css │ ├── displayslide.js │ └── settings.js ├── lib │ ├── collections.js │ └── routes.js └── server │ ├── publications.js │ └── import.js ├── public ├── YoungCedarTree-16x16.png ├── YoungCedarTree-32x32.png ├── YoungCedarTree-64x64.png └── YoungCedarTree-128x128.png ├── songs ├── client │ ├── subscriptions.js │ ├── templates │ │ ├── song.html │ │ ├── songs.html │ │ ├── display.html │ │ ├── section.html │ │ ├── arrangement.html │ │ └── settings.html │ ├── song.css │ ├── songs.js │ ├── display.css │ ├── display.js │ ├── section.js │ ├── settings.js │ └── arrangement.js ├── lib │ ├── collections.js │ ├── routes.js │ └── util.js └── server │ ├── publications.js │ ├── import.js │ └── songs.js ├── musicstand ├── client │ ├── templates │ │ ├── setdisplay.html │ │ ├── menu.html │ │ ├── musicstand.html │ │ └── chart.html │ ├── menu.js │ ├── musicstand.css │ ├── musicstand.js │ └── chart.js └── lib │ └── routes.js ├── .gitignore └── LICENSE /media/client/media.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lighting/client/console.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lighting/client/scene.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.5 2 | -------------------------------------------------------------------------------- /.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | dev_bundle 2 | local 3 | -------------------------------------------------------------------------------- /main/lib/constants.js: -------------------------------------------------------------------------------- 1 | Stage = 'Stage'; 2 | -------------------------------------------------------------------------------- /.meteor/platforms: -------------------------------------------------------------------------------- 1 | android 2 | browser 3 | server 4 | -------------------------------------------------------------------------------- /main/client/subscriptions.js: -------------------------------------------------------------------------------- 1 | Meteor.subscribe('settings'); 2 | -------------------------------------------------------------------------------- /schedule/client/subscriptions.js: -------------------------------------------------------------------------------- 1 | Meteor.subscribe('schedules'); 2 | -------------------------------------------------------------------------------- /sequences/client/subscriptions.js: -------------------------------------------------------------------------------- 1 | Meteor.subscribe('sequences'); 2 | -------------------------------------------------------------------------------- /lighting/client/scene.css: -------------------------------------------------------------------------------- 1 | .scene-item { 2 | overflow: hidden; 3 | } 4 | -------------------------------------------------------------------------------- /main/lib/collections.js: -------------------------------------------------------------------------------- 1 | settings = new Mongo.Collection('settings'); 2 | 3 | -------------------------------------------------------------------------------- /schedule/lib/collections.js: -------------------------------------------------------------------------------- 1 | schedules = new Mongo.Collection('schedules'); 2 | -------------------------------------------------------------------------------- /sequences/lib/collections.js: -------------------------------------------------------------------------------- 1 | sequences = new Mongo.Collection('sequences'); 2 | -------------------------------------------------------------------------------- /sets/client/subscriptions.js: -------------------------------------------------------------------------------- 1 | Meteor.subscribe('sets'); 2 | Meteor.subscribe('actions'); 3 | -------------------------------------------------------------------------------- /media/client/subscriptions.js: -------------------------------------------------------------------------------- 1 | Meteor.subscribe('media'); 2 | Meteor.subscribe('mediaplaylists'); 3 | -------------------------------------------------------------------------------- /minions/client/subscriptions.js: -------------------------------------------------------------------------------- 1 | Meteor.subscribe('stages') 2 | Meteor.subscribe('minions', false); 3 | -------------------------------------------------------------------------------- /lighting/client/templates/group.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /sets/lib/collections.js: -------------------------------------------------------------------------------- 1 | sets = new Mongo.Collection('sets'); 2 | actions = new Mongo.Collection('actions'); 3 | -------------------------------------------------------------------------------- /minions/lib/collections.js: -------------------------------------------------------------------------------- 1 | stages = new Mongo.Collection('stages'); 2 | minions = new Mongo.Collection('minions'); 3 | -------------------------------------------------------------------------------- /schedule/server/publications.js: -------------------------------------------------------------------------------- 1 | Meteor.publish('schedules', function () { 2 | return schedules.find(); 3 | }); 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "private/common"] 2 | path = private/common 3 | url = https://github.com/cedarsuite/common 4 | -------------------------------------------------------------------------------- /main/server/time.js: -------------------------------------------------------------------------------- 1 | Meteor.methods({ 2 | getTime: function () { 3 | return Date.now(); 4 | } 5 | }); 6 | -------------------------------------------------------------------------------- /presentations/client/subscriptions.js: -------------------------------------------------------------------------------- 1 | Meteor.subscribe('presentations'); 2 | Meteor.subscribe('presentationslides'); 3 | -------------------------------------------------------------------------------- /public/YoungCedarTree-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cedarproject/cedarserver/HEAD/public/YoungCedarTree-16x16.png -------------------------------------------------------------------------------- /public/YoungCedarTree-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cedarproject/cedarserver/HEAD/public/YoungCedarTree-32x32.png -------------------------------------------------------------------------------- /public/YoungCedarTree-64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cedarproject/cedarserver/HEAD/public/YoungCedarTree-64x64.png -------------------------------------------------------------------------------- /sequences/server/publications.js: -------------------------------------------------------------------------------- 1 | Meteor.publish('sequences', function () { 2 | return sequences.find(); 3 | }); 4 | -------------------------------------------------------------------------------- /main/lib/routes.js: -------------------------------------------------------------------------------- 1 | Router.configure({ 2 | layoutTemplate: 'layout' 3 | }); 4 | 5 | Router.route('/', {name: 'mainMenu'}); 6 | -------------------------------------------------------------------------------- /media/lib/collections.js: -------------------------------------------------------------------------------- 1 | media = new Mongo.Collection('media'); 2 | mediaplaylists = new Mongo.Collection('mediaplaylists'); 3 | -------------------------------------------------------------------------------- /presentations/client/templates/presentation.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /public/YoungCedarTree-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cedarproject/cedarserver/HEAD/public/YoungCedarTree-128x128.png -------------------------------------------------------------------------------- /sets/client/templates/settime.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /lighting/client/light.js: -------------------------------------------------------------------------------- 1 | Template.light.helpers({ 2 | disabled: function () { 3 | return !this.enabled; 4 | } 5 | }); 6 | -------------------------------------------------------------------------------- /main/client/templates/colorpicker.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /sets/client/templates/specialClearLayer.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /songs/client/subscriptions.js: -------------------------------------------------------------------------------- 1 | Meteor.subscribe('songs'); 2 | Meteor.subscribe('songsections'); 3 | Meteor.subscribe('songarrangements'); 4 | -------------------------------------------------------------------------------- /minions/client/web/mediaminion/mediaminion.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /sets/client/templates/specialCustom.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /schedule/client/templates/display.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /minions/client/templates/menu.html: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /presentations/lib/collections.js: -------------------------------------------------------------------------------- 1 | presentations = new Mongo.Collection('presentations'); 2 | presentationslides = new Mongo.Collection('presentationslides'); 3 | -------------------------------------------------------------------------------- /sequences/client/settings.css: -------------------------------------------------------------------------------- 1 | #sequence-container { 2 | height: 100vw; 3 | } 4 | 5 | .sequence-panel { 6 | height: 100%; 7 | overflow-y: auto; 8 | } 9 | -------------------------------------------------------------------------------- /sets/client/templates/specialCamera.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /sets/client/templates/specialClearChannel.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /songs/client/templates/song.html: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /minions/client/minions.js: -------------------------------------------------------------------------------- 1 | Template.minionsList.helpers({ 2 | minions: function () { 3 | return minions.find({stage: this._id || null}); 4 | } 5 | }); 6 | -------------------------------------------------------------------------------- /musicstand/client/templates/setdisplay.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /sets/client/settime.js: -------------------------------------------------------------------------------- 1 | Template.setTime.onRendered(function () { 2 | this.$('.set-time').datetimepicker({defaultDate: moment(this.data), sideBySide: true}); 3 | }); 4 | -------------------------------------------------------------------------------- /songs/client/song.css: -------------------------------------------------------------------------------- 1 | .song-section-container { 2 | position: relative; 3 | } 4 | 5 | .content-del { 6 | position: absolute; 7 | top: 0; 8 | right: 0; 9 | } 10 | -------------------------------------------------------------------------------- /sets/server/publications.js: -------------------------------------------------------------------------------- 1 | Meteor.publish('sets', function () { 2 | return sets.find(); 3 | }); 4 | 5 | Meteor.publish('actions', function () { 6 | return actions.find(); 7 | }); 8 | -------------------------------------------------------------------------------- /songs/lib/collections.js: -------------------------------------------------------------------------------- 1 | songs = new Mongo.Collection('songs'); 2 | songsections = new Mongo.Collection('songsections'); 3 | songarrangements = new Mongo.Collection('songarrangements'); 4 | -------------------------------------------------------------------------------- /lighting/client/templates/scene.html: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /sequences/client/templates/sequence.html: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /presentations/client/menuitem.js: -------------------------------------------------------------------------------- 1 | Template.presentationMenuItem.helpers({ 2 | importstatusIs: function () { 3 | return Array.from(arguments).indexOf(this.importstatus) != -1; 4 | } 5 | }); 6 | -------------------------------------------------------------------------------- /sequences/client/actiondisplay.js: -------------------------------------------------------------------------------- 1 | Template.sequenceActionDisplay.helpers({ 2 | isActive: function () { 3 | if (this._id == Session.get('sequence-active')) return 'active'; 4 | } 5 | }); 6 | -------------------------------------------------------------------------------- /main/server/publications.js: -------------------------------------------------------------------------------- 1 | Meteor.publish('settings', function () { 2 | return settings.find(); 3 | }); 4 | 5 | Meteor.publish('customactions', function () { 6 | return customactions.find(); 7 | }); 8 | -------------------------------------------------------------------------------- /media/server/publications.js: -------------------------------------------------------------------------------- 1 | Meteor.publish('media', function () { 2 | return media.find(); 3 | }); 4 | 5 | Meteor.publish('mediaplaylists', function () { 6 | return mediaplaylists.find(); 7 | }); 8 | -------------------------------------------------------------------------------- /lighting/client/subscriptions.js: -------------------------------------------------------------------------------- 1 | Meteor.subscribe('lights'); 2 | Meteor.subscribe('lightgroups'); 3 | Meteor.subscribe('lightscenes'); 4 | Meteor.subscribe('lightconsoles'); 5 | Meteor.subscribe('lightconsolepanels'); 6 | -------------------------------------------------------------------------------- /musicstand/client/templates/menu.html: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /minions/client/web/mediaminion/mediaminion.css: -------------------------------------------------------------------------------- 1 | .media-container { 2 | width: 99vw; 3 | height: 99vh; 4 | } 5 | 6 | .no-scrollbars { 7 | overflow: hidden; 8 | width: 100vh; 9 | height: 100vh; 10 | } 11 | -------------------------------------------------------------------------------- /presentations/server/publications.js: -------------------------------------------------------------------------------- 1 | Meteor.publish('presentations', function () { 2 | return presentations.find(); 3 | }); 4 | 5 | Meteor.publish('presentationslides', function () { 6 | return presentationslides.find(); 7 | }); 8 | -------------------------------------------------------------------------------- /sequences/client/templates/display.html: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /lighting/client/templates/light.html: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /media/client/templates/items.html: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /presentations/client/templates/slideselector.html: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /minions/client/templates/minions.html: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /main/client/layout.css: -------------------------------------------------------------------------------- 1 | .navbar-image { 2 | height: 100%; 3 | } 4 | 5 | body { 6 | background: url('/YoungCedarTreeGrey.svg') bottom left no-repeat fixed; 7 | background-size: auto 75%; 8 | position: relative; /* Required for scrollspy to work */ 9 | } 10 | -------------------------------------------------------------------------------- /presentations/client/slide.css: -------------------------------------------------------------------------------- 1 | .editor-container { 2 | width: 100%; 3 | height: 25em; 4 | border: 1px solid grey; 5 | } 6 | 7 | .editor-container s { 8 | border-top: 1px dotted grey; 9 | border-bottom: 1px dotted grey; 10 | text-decoration: none; 11 | } 12 | -------------------------------------------------------------------------------- /presentations/client/slideselector.js: -------------------------------------------------------------------------------- 1 | Template.presentationSlideSelector.helpers({ 2 | getPres: function () { 3 | var pres = Session.get('presentationSlideSelectorPresentation'); 4 | if (!pres) return; 5 | else return presentations.findOne(pres); 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /lighting/lib/collections.js: -------------------------------------------------------------------------------- 1 | lights = new Mongo.Collection('lights'); 2 | lightgroups = new Mongo.Collection('lightgroups'); 3 | lightscenes = new Mongo.Collection('lightscenes'); 4 | lightconsoles = new Mongo.Collection('lightconsoles'); 5 | lightconsolepanels = new Mongo.Collection('lightconsolepanels'); 6 | -------------------------------------------------------------------------------- /media/client/templates/media.html: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /media/client/templates/mediaMenu.html: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /musicstand/client/menu.js: -------------------------------------------------------------------------------- 1 | Template.musicstandmenu.helpers({ 2 | setsSelector: { 3 | collection: sets, 4 | displayTemplate: 'musicstandSetDisplay', 5 | fields: [{field: 'title', type: String}], 6 | sort: [['title', 'asc']], 7 | addbutton: false 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /songs/server/publications.js: -------------------------------------------------------------------------------- 1 | Meteor.publish('songs', function () { 2 | return songs.find(); 3 | }); 4 | 5 | Meteor.publish('songsections', function () { 6 | return songsections.find(); 7 | }); 8 | 9 | Meteor.publish('songarrangements', function () { 10 | return songarrangements.find(); 11 | }); 12 | -------------------------------------------------------------------------------- /minions/server/publications.js: -------------------------------------------------------------------------------- 1 | Meteor.publish('stages', function () { 2 | return stages.find(); 3 | }); 4 | 5 | Meteor.publish('minions', function (minionid) { 6 | if (minionid) { 7 | return minions.find({_id: minionid}); 8 | } 9 | else { 10 | return minions.find(); 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /sets/client/templates/setdisplay.html: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /media/client/item.js: -------------------------------------------------------------------------------- 1 | Template.mediaItem.helpers({ 2 | typeIs: function () { 3 | for (var arg in arguments) { 4 | if (arguments[arg] == this.type) return true; 5 | } 6 | }, 7 | 8 | getLength: function () { 9 | return secondsToTimeString(parseFloat(this.duration)); 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /minions/client/settingssongs.js: -------------------------------------------------------------------------------- 1 | Template.minionSettingsSongs.helpers({ 2 | getSetting: function (setting) { 3 | return combineSettings(this.settings)[setting]; 4 | }, 5 | 6 | isSelected: function (setting, value) { 7 | if (combineSettings(this.settings)[setting] == value) return 'selected'; 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /minions/client/settingstimers.js: -------------------------------------------------------------------------------- 1 | Template.minionSettingsTimers.helpers({ 2 | getSetting: function (setting) { 3 | return combineSettings(this.settings)[setting]; 4 | }, 5 | 6 | isSelected: function (setting, value) { 7 | if (combineSettings(this.settings)[setting] == value) return 'selected'; 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | vnpnxjbs2fxm1rhyoxb 8 | -------------------------------------------------------------------------------- /media/client/templates/playlistdisplay.html: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /minions/client/settingspresentations.js: -------------------------------------------------------------------------------- 1 | Template.minionSettingsPresentations.helpers({ 2 | getSetting: function (setting) { 3 | return combineSettings(this.settings)[setting]; 4 | }, 5 | 6 | isSelected: function (setting, value) { 7 | if (combineSettings(this.settings)[setting] == value) return 'selected'; 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /sets/client/templates/sets.html: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /sets/client/specialTimer.js: -------------------------------------------------------------------------------- 1 | Template.specialTimer.helpers({ 2 | typeIs: function (type) { 3 | return combineSettings(this.settings).timers_type == type; 4 | }, 5 | 6 | getTime: function (type) { 7 | return `${this.settings.timer_time.hours}:${this.settings.timer_time.minutes}:${this.settings.timer_time.seconds}`; 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /presentations/client/displayimportedslide.js: -------------------------------------------------------------------------------- 1 | Template.presentationDisplaySlideImported.helpers({ 2 | isActive: function () { 3 | var set = Template.parentData(3); 4 | var action = Template.parentData(2); 5 | 6 | if (set.active == action._id) { 7 | if (this.order == action.args.order) return 'active'; 8 | } 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /presentations/client/templates/displayimportedslide.html: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /media/client/templates/playlists.html: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /minions/client/stages.js: -------------------------------------------------------------------------------- 1 | Template.stagesList.helpers({ 2 | stages: function () { 3 | return stages.find(); 4 | } 5 | }); 6 | 7 | Template.stagesList.events({ 8 | 'click .stages-new': function (event) { 9 | Meteor.call('stageNew'); 10 | }, 11 | 12 | 'click .web-minion-new': function (event) { 13 | Meteor.call('minionNew', 'media'); 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /media/client/items.js: -------------------------------------------------------------------------------- 1 | Template.mediaItems.helpers({ 2 | mediaFormData: { 3 | type: 'uploadmedia' 4 | }, 5 | 6 | mediaSelector: { 7 | collection: media, 8 | displayTemplate: 'media', 9 | fields: [{field: 'title', type: String}, {field: 'tags', type: Array}], 10 | sort: [['new', 'desc'], ['title', 'asc']], 11 | addbutton: false 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /minions/client/web/mediaminion/clearlayer.js: -------------------------------------------------------------------------------- 1 | MediaMinionClearLayer = class MediaMinionClearLayer { 2 | constructor (action, minion) { 3 | this.action = action; 4 | this.minion = minion; 5 | } 6 | 7 | show (old) { 8 | if (old) { 9 | old.hide(); 10 | old.remove(); 11 | } 12 | } 13 | 14 | hide () {} 15 | 16 | remove () {} 17 | } 18 | -------------------------------------------------------------------------------- /presentations/client/display.js: -------------------------------------------------------------------------------- 1 | Template.presentationDisplay.helpers({ 2 | slides: function () { 3 | var slides = []; 4 | presentationslides.find({presentation: this._id}, {sort: [['order', 'asc']]}).forEach(function (slide) { 5 | slide.action = this.action; 6 | slides.push(slide); 7 | }.bind(this)); 8 | 9 | return slides; 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /lighting/client/templates/menu.html: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /schedule/client/templates/schedules.html: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /schedule/lib/routes.js: -------------------------------------------------------------------------------- 1 | Router.route('/schedules', {name: 'schedules'}); 2 | 3 | Router.route('/schedule/:_id', { 4 | name: 'schedule', 5 | waitOn: function () { 6 | return [ 7 | Meteor.subscribe('schedules'), 8 | Meteor.subscribe('actions') 9 | ]; 10 | }, 11 | data: function () { 12 | return schedules.findOne({_id: this.params._id}); 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /sequences/client/templates/sequences.html: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /sequences/lib/routes.js: -------------------------------------------------------------------------------- 1 | Router.route('/sequences', {name: 'sequences'}); 2 | 3 | Router.route('/sequence/:_id', { 4 | name: 'sequenceSettings', 5 | waitOn: function () { 6 | return [ 7 | Meteor.subscribe('sequences'), 8 | Meteor.subscribe('actions') 9 | ]; 10 | }, 11 | data: function () { 12 | return sequences.findOne({_id: this.params._id}); 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /sequences/client/templates/actiondisplay.html: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /lighting/client/templates/valueselector.html: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /main/server/customactions.js: -------------------------------------------------------------------------------- 1 | customactions = new Mongo.Collection('customactions'); 2 | 3 | Meteor.startup(function () { 4 | customactions.remove({}); 5 | }); 6 | 7 | Meteor.methods({ 8 | 'customActionTriggered': function (action_string) { 9 | _id = customactions.insert({action_string: action_string}); 10 | 11 | Meteor.setTimeout(() => { 12 | customactions.remove(_id); 13 | }, 1000); 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /presentations/client/templates/display.html: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /sets/client/templates/specialTimer.html: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /musicstand/lib/routes.js: -------------------------------------------------------------------------------- 1 | Router.route('/musicstand', { 2 | name: 'musicstandmenu', 3 | waitOn: function () { 4 | return Meteor.subscribe('sets'); 5 | } 6 | }); 7 | 8 | Router.route('/musicstand/:_id', { 9 | name: 'musicstand', 10 | waitOn: function () { 11 | return [ 12 | Meteor.subscribe('sets'), 13 | Meteor.subscribe('songs') 14 | ]; 15 | }, 16 | data: function () { 17 | return sets.findOne(this.params._id); 18 | } 19 | }); 20 | -------------------------------------------------------------------------------- /songs/client/templates/songs.html: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /lighting/client/templates/groups.html: -------------------------------------------------------------------------------- 1 | 13 | 14 | 17 | -------------------------------------------------------------------------------- /main/client/templates/menu.html: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /presentations/client/importedslide.js: -------------------------------------------------------------------------------- 1 | Template.presentationSlideImported.events({ 2 | 'click .slide-down': function (event, template) { 3 | Meteor.call('presentationSlideMove', template.data._id, template.data.order + 1); 4 | }, 5 | 6 | 'click .slide-up': function (event, template) { 7 | Meteor.call('presentationSlideMove', template.data._id, template.data.order - 1); 8 | }, 9 | 10 | 'click .slide-del': function (event, template) { 11 | Meteor.call('presentationSlideDel', template.data._id); 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /schedule/client/schedules.js: -------------------------------------------------------------------------------- 1 | Template.schedules.helpers({ 2 | schedulesSelector: { 3 | collection: schedules, 4 | displayTemplate: 'scheduleDisplay', 5 | fields: [{field: 'title', type: String}], 6 | sort: [['title', 'asc']], 7 | addbutton: false 8 | } 9 | }); 10 | 11 | Template.schedules.events({ 12 | 'click #schedule-new': function (event, template) { 13 | Meteor.call('scheduleNew', function (err, _id) { 14 | if (!err) Router.go(`/schedule/${_id}`); 15 | }); 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /sequences/client/sequences.js: -------------------------------------------------------------------------------- 1 | Template.sequences.helpers({ 2 | sequencesSelector: { 3 | collection: sequences, 4 | displayTemplate: 'sequenceDisplay', 5 | fields: [{field: 'title', type: String}], 6 | sort: [['title', 'asc']], 7 | addbutton: false 8 | } 9 | }); 10 | 11 | Template.sequences.events({ 12 | 'click #sequence-new': function (event, template) { 13 | Meteor.call('sequenceNew', function (err, _id) { 14 | if (!err) Router.go(`/sequence/${_id}`); 15 | }); 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /presentations/lib/routes.js: -------------------------------------------------------------------------------- 1 | Router.route('/presentations', { 2 | name: 'presentationsMenu', 3 | waitOn: function () { 4 | return Meteor.subscribe('presentations'); 5 | } 6 | }); 7 | 8 | Router.route('/presentations/presentation/:_id', { 9 | name: 'presentationSettings', 10 | waitOn: function () { 11 | return [ 12 | Meteor.subscribe('presentations'), 13 | Meteor.subscribe('presentationslides') 14 | ]; 15 | }, 16 | data: function () {return presentations.findOne({_id: this.params._id});} 17 | }); 18 | -------------------------------------------------------------------------------- /songs/client/songs.js: -------------------------------------------------------------------------------- 1 | Template.songs.helpers({ 2 | songFormData: { 3 | type: 'importsong' 4 | }, 5 | 6 | songsSelector: { 7 | collection: songs, 8 | displayTemplate: 'song', 9 | fields: [{field: 'title', type: String}], 10 | sort: [['title', 1]], 11 | addbutton: false 12 | } 13 | }); 14 | 15 | Template.songs.events({ 16 | 'click #song-new': function () { 17 | Meteor.call('songNew', (err, _id) => { 18 | if (!err) Router.go('/songs/song/' + _id); 19 | }); 20 | } 21 | }); 22 | -------------------------------------------------------------------------------- /lighting/client/templates/consoles.html: -------------------------------------------------------------------------------- 1 | 13 | 14 | 19 | -------------------------------------------------------------------------------- /lighting/server/publications.js: -------------------------------------------------------------------------------- 1 | Meteor.publish('lights', function (minionid) { 2 | if (minionid) return lights.find({minion: minionid}); 3 | else return lights.find(); 4 | }); 5 | 6 | Meteor.publish('lightgroups', function () { 7 | return lightgroups.find(); 8 | }); 9 | 10 | Meteor.publish('lightscenes', function () { 11 | return lightscenes.find(); 12 | }); 13 | 14 | Meteor.publish('lightconsoles', function () { 15 | return lightconsoles.find(); 16 | }); 17 | 18 | Meteor.publish('lightconsolepanels', function () { 19 | return lightconsolepanels.find(); 20 | }); 21 | -------------------------------------------------------------------------------- /presentations/client/templates/menuitem.html: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /media/client/playlists.js: -------------------------------------------------------------------------------- 1 | Template.mediaPlaylists.helpers({ 2 | mediaSelector: { 3 | collection: mediaplaylists, 4 | displayTemplate: 'mediaPlaylistDisplay', 5 | fields: [{field: 'title', type: String}, {field: 'tags', type: Array}], 6 | sort: [['title', 'asc']], 7 | addbutton: false 8 | } 9 | }); 10 | 11 | Template.mediaPlaylists.events({ 12 | 'click #new-playlist': function (event, template) { 13 | Meteor.call('playlistNew', (err, playlistid) => { 14 | Router.go('/media/playlist/' + playlistid); 15 | }); 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /lighting/client/channel.js: -------------------------------------------------------------------------------- 1 | Template.lightChannel.helpers({ 2 | typeIs: function (type) { 3 | if (this.type == type) return true; 4 | }, 5 | 6 | types: function () { 7 | var types = [ 8 | 'red', 9 | 'green', 10 | 'blue', 11 | 'intensity', 12 | 'fixed' 13 | ]; 14 | 15 | for (var i in types) { 16 | types[i] = {type: types[i], selected: false}; 17 | if (this.type == types[i].type) types[i].selected = true; 18 | } 19 | 20 | return types; 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /media/client/templates/playlist.html: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /sets/client/action.js: -------------------------------------------------------------------------------- 1 | Template.setAction.helpers({ 2 | triggersActive: function () { 3 | if (this.settings.triggers) return 'btn-default active'; 4 | else return 'btn-danger'; 5 | } 6 | }); 7 | 8 | Template.setAction.events({ 9 | 'click .settings-button': function (event, template) { 10 | event.stopImmediatePropagation(); 11 | template.$('.settings').first().collapse('toggle'); 12 | }, 13 | 14 | 'click .action-triggers': function (event, template) { 15 | Meteor.call('actionSetting', template.data._id, 'triggers', !template.data.settings.triggers); 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /presentations/client/templates/menu.html: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /sets/lib/routes.js: -------------------------------------------------------------------------------- 1 | Router.route('/sets', { 2 | name: 'setsMenu', 3 | waitOn: function () { 4 | return Meteor.subscribe('sets'); 5 | } 6 | }); 7 | 8 | Router.route('/set/:_id', { 9 | name: 'set', 10 | waitOn: function () { 11 | return [ 12 | Meteor.subscribe('sets'), 13 | Meteor.subscribe('actions'), 14 | Meteor.subscribe('media'), 15 | Meteor.subscribe('songs'), 16 | Meteor.subscribe('presentations'), 17 | Meteor.subscribe('lightscenes') 18 | ]; 19 | }, 20 | data: function () {return sets.findOne(this.params._id);} 21 | }); 22 | -------------------------------------------------------------------------------- /songs/client/templates/display.html: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /main/client/layout.js: -------------------------------------------------------------------------------- 1 | Template.layout.helpers({ 2 | notFullscreen: function () { 3 | var curr = Router.current().lookupTemplate(); 4 | if (curr == 'Webminionmedia' || curr == 'StreamingSourceStream') return false; 5 | else return true; 6 | }, 7 | 8 | isActive: function (section) { 9 | var current = Router.current().lookupTemplate(); 10 | if (current.toLowerCase().startsWith(section)) return 'active'; 11 | }, 12 | 13 | serverTitle: function () { 14 | var s = settings.findOne({key: 'servertitle'}); 15 | if (s) return s['value']; 16 | else return 'Cedar'; 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /.meteor/.finished-upgraders: -------------------------------------------------------------------------------- 1 | # This file contains information which helps Meteor properly upgrade your 2 | # app when you run 'meteor update'. You should check it into version control 3 | # with your project. 4 | 5 | notices-for-0.9.0 6 | notices-for-0.9.1 7 | 0.9.4-platform-file 8 | notices-for-facebook-graph-api-2 9 | 1.2.0-standard-minifiers-package 10 | 1.2.0-meteor-platform-split 11 | 1.2.0-cordova-changes 12 | 1.2.0-breaking-changes 13 | 1.3.0-split-minifiers-package 14 | 1.3.5-remove-old-dev-bundle-link 15 | 1.4.0-remove-old-dev-bundle-link 16 | 1.4.1-add-shell-server-package 17 | 1.4.3-split-account-service-packages 18 | 1.5-add-dynamic-import-package 19 | -------------------------------------------------------------------------------- /songs/server/import.js: -------------------------------------------------------------------------------- 1 | import_song = function (fileInfo, formData) { 2 | var prefix = settings.findOne({key: 'mediadir'}).value; 3 | var path = prefix + '/' + fileInfo.name; 4 | 5 | var fs = Npm.require('fs'); 6 | var songstring = fs.readFileSync(path, {encoding: 'utf-8'}); 7 | 8 | var song = EJSON.parse(songstring); 9 | 10 | songs.insert(song.song); 11 | 12 | for (var i in song.sections) 13 | songsections.insert(song.sections[i]); 14 | 15 | for (var i in song.arrangements) 16 | songarrangements.insert(song.arrangements[i]); 17 | 18 | fs.unlink(path, function () {}); 19 | } 20 | -------------------------------------------------------------------------------- /lighting/client/groups.js: -------------------------------------------------------------------------------- 1 | Template.lightGroups.helpers({ 2 | groupSelector: { 3 | collection: lightgroups, 4 | displayTemplate: 'lightgroupsListItem', 5 | fields: [{field: 'title', type: String}, {field: 'stage', type: Stage}], 6 | sort: [['title', 1]], 7 | addbutton: false 8 | }, 9 | 10 | lightgroups: function () { 11 | return lightgroups.find(); 12 | } 13 | }); 14 | 15 | Template.lightGroups.events({ 16 | 'click .group-add': function (event) { 17 | Meteor.call('lightGroupNew', function (err, val) { 18 | if (!err) Router.go('/lighting/group/' + val); 19 | }); 20 | } 21 | }); 22 | -------------------------------------------------------------------------------- /main/client/templates/main.html: -------------------------------------------------------------------------------- 1 | 2 | Cedar 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /minions/client/templates/minion.html: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /lighting/client/templates/scenes.html: -------------------------------------------------------------------------------- 1 | 13 | 14 | 20 | -------------------------------------------------------------------------------- /lighting/client/consoles.js: -------------------------------------------------------------------------------- 1 | Template.lightConsoles.helpers({ 2 | consoles: function () { 3 | return lightconsoles.find(); 4 | }, 5 | 6 | consoleSelector: { 7 | collection: lightconsoles, 8 | displayTemplate: 'lightconsolesListItem', 9 | fields: [{field: 'title', type: String}, {field: 'stage', type: Stage}], 10 | sort: [['title', 1]], 11 | addbutton: false 12 | }, 13 | }); 14 | 15 | Template.lightConsoles.events({ 16 | 'click #new-console': function () { 17 | Meteor.call('lightConsoleNew', function (err, val) { 18 | if (!err) Router.go('/lighting/console/' + val); 19 | }); 20 | } 21 | }); 22 | -------------------------------------------------------------------------------- /presentations/client/menu.js: -------------------------------------------------------------------------------- 1 | Template.presentationsMenu.helpers({ 2 | presentationSelector: { 3 | collection: presentations, 4 | displayTemplate: 'presentationMenuItem', 5 | fields: [{field: 'title', type: String}], 6 | sort: [['title', 'asc']], 7 | addbutton: false 8 | }, 9 | 10 | presentationFormData: { 11 | type: 'importpresentation' 12 | } 13 | }); 14 | 15 | Template.presentationsMenu.events({ 16 | 'click #pres-add': function (event, template) { 17 | Meteor.call('presentationNew', function (err, pres) { 18 | if (!err) Router.go('/presentations/presentation/' + pres); 19 | }); 20 | } 21 | }); 22 | -------------------------------------------------------------------------------- /sets/client/sets.js: -------------------------------------------------------------------------------- 1 | Template.setsMenu.helpers({ 2 | setsSelector: { 3 | collection: sets, 4 | displayTemplate: 'setDisplay', 5 | fields: [{field: 'title', type: String}], 6 | sort: [['title', 'asc']], 7 | addbutton: false 8 | } 9 | }); 10 | 11 | Template.setsMenu.events({ 12 | 'click .sets-new': function () { 13 | Meteor.call('setNew', function (err, newid) { 14 | if (!err) Router.go(`/set/${newid}`); 15 | }); 16 | }, 17 | 18 | 'click .set-copy': function () { 19 | Meteor.call('setCopy', this._id, function (err, newid) { 20 | if (!err) Router.go(`/set/${newid}`); 21 | }); 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /main/server/upload.js: -------------------------------------------------------------------------------- 1 | function handle_upload (fileInfo, formFields) { 2 | if (formFields.type == 'importsong') import_song(fileInfo, formFields); 3 | else if (formFields.type == 'importpresentation') import_presentation(fileInfo, formFields); 4 | else if (formFields.type == 'uploadmedia') process_media(fileInfo, formFields); 5 | else throw new Meteor.Error('upload-failed', 'What was I supposed to do with that?'); 6 | } 7 | 8 | Meteor.startup(function () { 9 | var dir = settings.findOne({key: 'mediadir'}).value; 10 | UploadServer.init({ 11 | uploadDir: dir, 12 | tmpDir: dir + '/tmp', 13 | checkCreateDirectories: true, 14 | finished: handle_upload 15 | }) 16 | }); 17 | -------------------------------------------------------------------------------- /lighting/client/templates/lights.html: -------------------------------------------------------------------------------- 1 | 13 | 14 | 23 | -------------------------------------------------------------------------------- /presentations/client/templates/importedslide.html: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /songs/client/templates/section.html: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /main/lib/settings.js: -------------------------------------------------------------------------------- 1 | defaults = '{}'; 2 | 3 | if (Meteor.isServer) { 4 | defaults = Assets.getText('common/default_settings.json'); 5 | 6 | Meteor.methods({ 7 | settingsDefaultsJSON: function () { 8 | return defaults; 9 | } 10 | }); 11 | } else { 12 | Meteor.call('settingsDefaultsJSON', (err, res) => { 13 | defaults = res; 14 | }); 15 | }; 16 | 17 | combineSettings = function () { 18 | var out = JSON.parse(defaults); 19 | 20 | for (var i in arguments) { 21 | for (var p in arguments[i]) { 22 | if (arguments[i].hasOwnProperty(p) && arguments[i][p] !== null && typeof arguments[i][p] !== 'undefined') 23 | out[p] = arguments[i][p]; 24 | } 25 | } 26 | 27 | return out; 28 | }; 29 | -------------------------------------------------------------------------------- /minions/client/templates/stages.html: -------------------------------------------------------------------------------- 1 | 25 | -------------------------------------------------------------------------------- /main/server/settings.js: -------------------------------------------------------------------------------- 1 | var defaults = [ 2 | { 3 | key: 'mediadir', 4 | value: process.env.PWD + '/.uploads/', 5 | description: 'Directory to store uploaded media files.' 6 | }, 7 | 8 | { 9 | key: 'mediaurl', 10 | value: '/media/static/', 11 | description: 'URL fragment of media server' // TODO get rid of this, it's not really useful. 12 | } 13 | ]; 14 | 15 | Meteor.startup(function () { 16 | defaults.forEach(function (setting, index, defaults) { 17 | if (settings.findOne({key: setting.key}) === undefined) { 18 | settings.insert(setting); 19 | } 20 | }); 21 | }); 22 | 23 | Meteor.methods({ 24 | mainSetting: function (key, value) { 25 | settings.update({key: key}, {$set: {value: value}}); 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /main/client/util.js: -------------------------------------------------------------------------------- 1 | // Misc global utilities. It's terrible practice to have a 'misc' file, but I'm doing it anyway. 2 | 3 | scrollTo = function (element) { 4 | $('html, body').delay(50).animate({ 5 | scrollTop: $(element).offset().top - window.innerHeight * 0.3 6 | }, 200); 7 | }; 8 | 9 | secondsToTimeString = function (t) { 10 | if (t < 0) { 11 | var sign = '-'; 12 | t = Math.abs(t); 13 | } else { 14 | var sign = ''; 15 | } 16 | 17 | // And the award for 'worst abuse of template strings' goes to... 18 | var hours = `0${Math.floor(t / 3600)}`.slice(-2); 19 | var minutes = `0${Math.floor((t - hours * 3600) / 60)}`.slice(-2); 20 | var seconds = `0${Math.floor(t % 60)}`.slice(-2); 21 | 22 | return `${sign}${hours}:${minutes}:${seconds}`; 23 | }; 24 | -------------------------------------------------------------------------------- /lighting/client/templates/channel.html: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /minions/lib/routes.js: -------------------------------------------------------------------------------- 1 | Router.route('/minions', {name: 'minionsMenu'}); 2 | 3 | Router.route('/stages/settings/:_id', { 4 | name: 'stageSettings', 5 | data: function () {return stages.findOne({_id: this.params._id});} 6 | }); 7 | 8 | Router.route('/minions/settings/:_id', { 9 | name: 'minionsettings', 10 | data: function () {return minions.findOne({_id: this.params._id});} 11 | }); 12 | 13 | Router.route('/minions/web/media/:_id', { 14 | name: 'webminionmedia', 15 | waitOn: function () { 16 | return [Meteor.subscribe('minions'), 17 | Meteor.subscribe('stages'), 18 | Meteor.subscribe('media'), 19 | Meteor.subscribe('songs'), 20 | Meteor.subscribe('presentations')]; 21 | }, 22 | data: function () {return minions.findOne(this.params._id);} 23 | }); 24 | -------------------------------------------------------------------------------- /lighting/client/lights.js: -------------------------------------------------------------------------------- 1 | Template.lights.helpers({ 2 | lights: function () { 3 | return lights.find(); 4 | }, 5 | 6 | lightSelector: { 7 | collection: lights, 8 | displayTemplate: 'lightsListItem', 9 | fields: [{field: 'title', type: String}, {field: 'stage', type: Stage}], 10 | sort: [['title', 1]], 11 | addbutton: false 12 | }, 13 | }); 14 | 15 | Template.lights.events({ 16 | 'click .light-add': function (event) { 17 | Meteor.call('lightNew', function (err, val) { 18 | if (!err) Router.go('/lighting/light/' + val); 19 | }); 20 | }, 21 | 22 | 'click .light-clone': function (event) { 23 | Meteor.call('lightClone', this._id, function (err, val) { 24 | if (!err) Router.go('/lighting/light/' + val); 25 | }); 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /songs/client/display.css: -------------------------------------------------------------------------------- 1 | .song-content { 2 | background: #FAFAFA; 3 | color: black; 4 | 5 | border: 2px solid gray; 6 | border-radius: 10px; 7 | margin: 2px; 8 | 9 | width: 100%; 10 | height: 8em; 11 | 12 | transition: box-shadow 0.25s border 0.25s; 13 | } 14 | 15 | .song-content-title { 16 | position: absolute; 17 | 18 | left: 25px; 19 | top: 2.5px; 20 | } 21 | 22 | .song-content-container { 23 | display: table; 24 | 25 | width: 100%; 26 | height: 100%; 27 | } 28 | 29 | .song-content-text { 30 | display: table-cell; 31 | vertical-align: middle; 32 | text-align: center; 33 | 34 | font-size: 115%; 35 | line-height: 1; 36 | } 37 | 38 | .list-group-item.active .song-content.active { 39 | border: 2px solid #FFFF88; 40 | box-shadow: 0px 0px 16px 8px #FFFF88; 41 | } 42 | -------------------------------------------------------------------------------- /musicstand/client/templates/musicstand.html: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /lighting/client/scenes.js: -------------------------------------------------------------------------------- 1 | Template.lightScenes.helpers({ 2 | scenes: function () { 3 | return lightscenes.find(); 4 | }, 5 | 6 | sceneSelector: { 7 | collection: lightscenes, 8 | displayTemplate: 'lightscenesListItem', 9 | fields: [{field: 'title', type: String}, {field: 'stage', type: Stage}], 10 | sort: [['title', 1]], 11 | addbutton: false 12 | }, 13 | }); 14 | 15 | Template.lightScenes.events({ 16 | 'click .scene-add': function (event) { 17 | Meteor.call('sceneAdd', function (err, val) { 18 | if (!err) Router.go('/lighting/scene/' + val); 19 | }); 20 | }, 21 | 22 | 'click .scene-clone': function (event, template) { 23 | Meteor.call('sceneClone', this._id, function (err, val) { 24 | if (!err) Router.go('/lighting/scene/' + val); 25 | }); 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /sets/client/templates/action.html: -------------------------------------------------------------------------------- 1 | 27 | -------------------------------------------------------------------------------- /media/client/templates/item.html: -------------------------------------------------------------------------------- 1 | 31 | -------------------------------------------------------------------------------- /presentations/client/display.css: -------------------------------------------------------------------------------- 1 | .presentation-content { 2 | transition: box-shadow 0.25s; 3 | 4 | border: 1px solid gray; 5 | border-radius: 2.5px; 6 | 7 | font-size: 115%; 8 | 9 | border: 2px solid gray; 10 | border-radius: 10px; 11 | 12 | margin: 2px; 13 | height: 20em; 14 | overflow: auto; 15 | } 16 | 17 | .presentation-content.presentation-content-imported { 18 | height: auto; 19 | } 20 | 21 | .list-group-item.active .presentation-content.active { 22 | border: 2px solid #FFFF88; 23 | box-shadow: 0px 0px 16px 8px #FFFF88; 24 | } 25 | 26 | .presentation-content .display-container { 27 | height: auto; 28 | } 29 | 30 | .fillin-controls { 31 | position: absolute; 32 | bottom: 1px; 33 | left: 0; 34 | width: 100%; 35 | } 36 | 37 | .display-container s { 38 | border-top: 1px dotted grey; 39 | border-bottom: 1px dotted grey; 40 | text-decoration: none; 41 | } 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | /lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | 43 | # Translations 44 | *.mo 45 | *.pot 46 | 47 | # Django stuff: 48 | *.log 49 | 50 | # Sphinx documentation 51 | docs/_build/ 52 | 53 | # PyBuilder 54 | target/ 55 | 56 | notes.txt 57 | .uploads/ 58 | packages/ 59 | android/ 60 | -------------------------------------------------------------------------------- /lighting/lib/routes.js: -------------------------------------------------------------------------------- 1 | Router.route('/lighting', {name: 'lightingMenu'}); 2 | 3 | Router.route('/lighting/lights', {name: 'lights'}); 4 | Router.route('/lighting/light/:_id', { 5 | name: 'lightSettings', 6 | data: function () {return lights.findOne({_id: this.params._id});} 7 | }); 8 | 9 | Router.route('/lighting/groups', {name: 'lightGroups'}); 10 | Router.route('/lighting/group/:_id', { 11 | name: 'lightGroupSettings', 12 | data: function () {return lightgroups.findOne({_id: this.params._id});} 13 | }); 14 | 15 | Router.route('/lighting/scenes', {name: 'lightScenes'}); 16 | Router.route('/lighting/scene/:_id', { 17 | name: 'lightSceneSettings', 18 | data: function () {return lightscenes.findOne({_id: this.params._id});} 19 | }); 20 | 21 | Router.route('/lighting/consoles', {name: 'lightConsoles'}); 22 | Router.route('/lighting/console/:_id', { 23 | name: 'lightConsole', 24 | data: function () {return lightconsoles.findOne({_id: this.params._id});} 25 | }); 26 | -------------------------------------------------------------------------------- /.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # Check this file (and the other files in this directory) into your repository. 3 | # 4 | # 'meteor add' and 'meteor remove' will edit this file for you, 5 | # but you can also edit it by hand. 6 | 7 | iron:router 8 | fish:ffmpeg 9 | tomi:upload-server 10 | tomi:upload-jquery 11 | reactive-var@1.0.11 12 | twbs:bootstrap 13 | patte:mime-npm 14 | cfs:graphicsmagick 15 | pfafman:font-awesome-4 16 | meteor-base@1.1.0 17 | mobile-experience@1.0.4 18 | mongo@1.1.18 19 | blaze-html-templates 20 | session@1.1.7 21 | jquery@1.11.10 22 | tracker@1.1.3 23 | logging@1.1.17 24 | reload@1.1.11 25 | random@1.0.10 26 | ejson@1.0.13 27 | spacebars 28 | check@1.2.5 29 | ecmascript@0.8.0 30 | tsega:bootstrap3-datetimepicker 31 | risul:bootstrap-colorpicker 32 | mrt:later 33 | risul:moment-timezone 34 | standard-minifier-css@1.3.4 35 | standard-minifier-js@2.1.0 36 | http@1.2.12 37 | hunternet93:quilljs 38 | rgnevashev:bootstrap-slider 39 | shell-server 40 | dynamic-import 41 | -------------------------------------------------------------------------------- /musicstand/client/musicstand.css: -------------------------------------------------------------------------------- 1 | .musicstand-action { 2 | transition: box-shadow 0.2s; 3 | margin-top: 10px; 4 | margin-bottom: 10px; 5 | } 6 | 7 | .musicstand-active { 8 | box-shadow: 0px 0px 10px 2px #337AB7; 9 | } 10 | 11 | .musicstand-header { 12 | position: sticky; 13 | top: 0em; 14 | border: 1px solid #C8C8C8; 15 | border-radius: 10px; 16 | background-color: rgba(255, 255, 255, 0.8); 17 | z-index: 100; 18 | } 19 | 20 | .musicstand-chart { 21 | transition: box-shadow 0.2s; 22 | line-height: 2.5; 23 | margin-bottom: 2em; 24 | font-size: 3vw; 25 | white-space: pre; 26 | } 27 | 28 | .musicstand-chord { 29 | position: relative; 30 | } 31 | 32 | .musicstand-chord > span { 33 | position: absolute; 34 | top: -1.5em; 35 | font-weight: bold; 36 | } 37 | 38 | .musicstand-chord-full { 39 | position: relative; 40 | top: -1.5em; 41 | font-weight: bold; 42 | } 43 | 44 | #scroll-lock { 45 | position: fixed; 46 | right: 0; 47 | bottom: 0; 48 | } 49 | -------------------------------------------------------------------------------- /main/client/colorpicker.js: -------------------------------------------------------------------------------- 1 | Template.colorpicker.onRendered(function () { 2 | this.firstEventTriggered = false; 3 | 4 | var v = []; 5 | for (var i in this.data.value) v[i] = Math.round(this.data.value[i] * 255.0); 6 | 7 | if (v.length == 3) var c = `rgb(${v.join(',')})`; 8 | else var c = `rgba(${v.join(',')})`; 9 | 10 | var picker = this.$('div').colorpicker({ 11 | format: this.data['format'], 12 | inline: true, 13 | container: true 14 | }).colorpicker('setValue', c); 15 | 16 | for (var prop in this.data) { 17 | if (prop.startsWith('data-')) { 18 | picker.data(prop.slice(5), this.data[prop]); 19 | } 20 | } 21 | }); 22 | 23 | Template.colorpicker.events({ 24 | 'colorChange': function (event, template) { 25 | if (!this.firstEventTriggered) { 26 | // Prevent event from firing on colorpicker init. 27 | event.stopImmediatePropagation(); 28 | this.firstEventTriggered = true; 29 | } 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /musicstand/client/templates/chart.html: -------------------------------------------------------------------------------- 1 | 27 | -------------------------------------------------------------------------------- /songs/client/display.js: -------------------------------------------------------------------------------- 1 | Template.songDisplay.helpers({ 2 | content: function () { 3 | var contents = []; 4 | this.arrangement.order.forEach((_id, i) => { 5 | var section = songsections.findOne(_id); 6 | for (var c in section.contents) { 7 | var content = section.contents[c]; 8 | content.section = i; 9 | if (c == 0) content.title = section.title; 10 | 11 | content.index = parseInt(c); 12 | content.action = this.action; 13 | 14 | contents.push(content); 15 | } 16 | }); 17 | 18 | return contents; 19 | }, 20 | 21 | getText: function () { 22 | return songTextToHTML(this.text); 23 | }, 24 | 25 | isActive: function () { 26 | var action = Template.parentData(2); 27 | if (action['args']) { 28 | if (this.section == action.args.section && this.index == action.args.index) return 'active'; 29 | } 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /lighting/client/valueselector.css: -------------------------------------------------------------------------------- 1 | .light-slider-container-red .slider-selection { 2 | background: rgb(200,0,0); 3 | } 4 | 5 | .light-slider-container-red .slider-handle { 6 | background: rgb(255,0,0); 7 | } 8 | 9 | .light-slider-container-green .slider-selection { 10 | background: rgb(0,200,0); 11 | } 12 | 13 | .light-slider-container-green .slider-handle { 14 | background: rgb(0,255,0); 15 | } 16 | 17 | .light-slider-container-blue .slider-selection { 18 | background: rgb(0,0,200); 19 | } 20 | 21 | .light-slider-container-blue .slider-handle { 22 | background: rgb(0,0,255); 23 | } 24 | 25 | .light-slider-container-intensity .slider-selection { 26 | background: rgb(225, 225, 225); 27 | } 28 | 29 | .light-slider-container-intensity .slider-handle { 30 | background: rgb(175, 175, 175); 31 | } 32 | 33 | .light-color-display { 34 | background: rgb(0,0,0); 35 | transition: background 0.1s; 36 | 37 | width: 80%; 38 | height: 2em; 39 | margin: 5px auto; 40 | 41 | border: 1px solid gray; 42 | border-radius: 10px; 43 | } 44 | -------------------------------------------------------------------------------- /presentations/client/templates/displayslide.html: -------------------------------------------------------------------------------- 1 | 26 | -------------------------------------------------------------------------------- /sets/client/set.css: -------------------------------------------------------------------------------- 1 | .set-action { 2 | transition: background 0.25s, opacity 1s, transform 1s, box-shadow 0.25s; 3 | border: 1px solid rgba(200, 200, 200, 1); 4 | border-radius: 5px; 5 | padding: 5px; 6 | overflow: hidden; 7 | } 8 | 9 | .set-action.moving { 10 | transform: scale(1.1, 1.1); 11 | } 12 | 13 | .set-action.movetarget { 14 | opacity: 0.5; 15 | } 16 | 17 | .set-action.movetarget:hover { 18 | box-shadow: 0 0 10px blue; 19 | } 20 | 21 | .dragmarker { 22 | transition: border .5s, height .25s, background 0.25s; 23 | border: 0px; 24 | height: 1em; 25 | width: 90%; 26 | } 27 | 28 | .dragmarker.dragging { 29 | border: 2px dashed blue; 30 | height: 20px; 31 | } 32 | 33 | .dragmarker.over { 34 | background-color: blue; 35 | } 36 | 37 | @media screen and (min-width: 768px) { 38 | .row-equal, .row-equal > div[class*='col-'] { 39 | display: -webkit-box; 40 | display: -moz-box; 41 | display: -ms-flexbox; 42 | display: -webkit-flex; 43 | display: flex; 44 | flex: 1 0 auto; 45 | }} 46 | 47 | .well { 48 | color: black; 49 | } 50 | -------------------------------------------------------------------------------- /songs/lib/routes.js: -------------------------------------------------------------------------------- 1 | Router.route('/songs', {name: 'songs'}); 2 | 3 | Router.route('/songs/song/:_id', { 4 | name: 'songSettings', 5 | waitOn: function () { 6 | return [Meteor.subscribe('songs'), Meteor.subscribe('songsections'), 7 | Meteor.subscribe('songarrangements')]; 8 | }, 9 | data: function () {return songs.findOne({_id: this.params._id});} 10 | }); 11 | 12 | Router.route('/songs/download/:_id', function () { 13 | var file = { 14 | song: songs.findOne(this.params._id), 15 | sections: songsections.find({song: this.params._id}).fetch(), 16 | arrangements: songarrangements.find({song: this.params._id}).fetch(), 17 | }; 18 | 19 | var headers = { 20 | 'Content-Type': 'application/json; charset=utf-8', 21 | 'Content-Disposition': 'attachment; filename="' + file.song.title + '.cedarsong"', 22 | 'Cache-Control': 'max-age=0', // Don't cache Songs. 23 | }; 24 | 25 | this.response.writeHead(200, headers); 26 | this.response.end(EJSON.stringify(file)); 27 | }, { 28 | name: 'songdownload', 29 | where: 'server' 30 | }); 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 cedarsuite 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /main/client/time.js: -------------------------------------------------------------------------------- 1 | time = { 2 | offsets: [], 3 | now: function () {return performance.now() + _.reduce(this.offsets, (m,n) => m+n) / this.offsets.length}, 4 | since: function (t) {return this.now() - t}, 5 | calc: function () { 6 | var start = performance.now(); 7 | 8 | Meteor.call('getTime', (err, server_now) => { 9 | var now = performance.now(); 10 | var latency = now - start; 11 | 12 | var offset = (server_now - latency / 2) - now; 13 | 14 | if (this.offsets.length >= 8) this.offsets.shift(); 15 | this.offsets.push(offset); 16 | }) 17 | } 18 | } 19 | 20 | Meteor.startup(function () { 21 | time.calc.bind(time)(); 22 | Meteor.setInterval(time.calc.bind(time), 100); 23 | }); 24 | 25 | //test = {l: 0, l_t: 0, a: [], r: function () {var t = time.now(); var n = performance.now(); var d = (t - test.l) - (n - test.l_t); test.a.push(Math.abs(d)); console.log(`o:${d}, a:${_.reduce(test.a, (m,n) => m+n)/test.a.length}, m:${_.max(test.a)}`); test.l = t; test.l_t = n;}}; test.r(); test.a.pop(); Meteor.setInterval(test.r, 1000); 26 | -------------------------------------------------------------------------------- /songs/client/section.js: -------------------------------------------------------------------------------- 1 | Template.songSection.events({ 2 | 'click .section-title': function (event, template) { 3 | template.$('.section-title').addClass('hidden'); 4 | template.$('.section-title-edit').removeClass('hidden'); 5 | }, 6 | 7 | 'blur .section-title-edit': function (event, template) { 8 | Meteor.call('songSectionTitle', this._id, template.$('.section-title-edit').val()); 9 | template.$('.section-title-edit').addClass('hidden'); 10 | template.$('.section-title').removeClass('hidden'); 11 | }, 12 | 13 | 'click #content-add': function (event, template) { 14 | Meteor.call('songSectionAddContent', this._id); 15 | }, 16 | 17 | 'click .content-del': function (event, template) { 18 | Meteor.call('songSectionDelContent', template.data._id, template.data.contents.indexOf(this)); 19 | }, 20 | 21 | 'blur .content-text': function (event, template) { 22 | var index = template.data.contents.indexOf(this); 23 | var text = $(event.target).val(); 24 | 25 | Meteor.call('songSectionChangeContent', template.data._id, index, text); 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /sets/client/sidebar.js: -------------------------------------------------------------------------------- 1 | Template.setSidebar.helpers({ 2 | actions: function () { 3 | return actions.find({set: Template.parentData()._id}, {sort: {order: 1}}); 4 | }, 5 | 6 | getLayers: function () { 7 | if (this.stage) return stages.findOne({_id: this.stage}).settings.layers; 8 | } 9 | }); 10 | 11 | Template.setSidebar.events({ 12 | 'click #sidebar-toggle': function (event, template) { 13 | template.$('#sidebar').toggleClass('sidebar-open'); 14 | }, 15 | 16 | 'click #set-lock': function (event, template) { 17 | if (Session.get('set-control-locked')) { 18 | Session.set('set-control-locked', false); 19 | template.$('#set-lock').removeClass('btn-danger') 20 | .removeClass('active').addClass('btn-default') 21 | .html('Lock'); 22 | } else { 23 | Session.set('set-control-locked', true); 24 | template.$('#set-lock').removeClass('btn-default') 25 | .addClass('btn-danger').addClass('active') 26 | .html('Unlock'); 27 | } 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /sets/client/sidebar.css: -------------------------------------------------------------------------------- 1 | #sidebar-container { 2 | padding-left: 0; 3 | padding-right: 0; 4 | } 5 | 6 | #sidebar { 7 | position: sticky; 8 | top: 10px; 9 | width: 100%; 10 | height: 90vh; 11 | background-color: #F8F8F8; 12 | } 13 | 14 | @media (min-width: 992px) { 15 | #sidebar-toggle { 16 | display: none; 17 | } 18 | } 19 | 20 | @media (max-width: 992px) { 21 | #sidebar-toggle { 22 | position: fixed; 23 | left: 5px; 24 | top: 5px; 25 | z-index: 10; 26 | opacity: 0.75; 27 | } 28 | 29 | #sidebar { 30 | position: fixed; 31 | height: 100vh; 32 | left: 0; 33 | top: 0; 34 | transform: translateX(-100vw); 35 | transition: transform 0.5s; 36 | z-index: 9; 37 | } 38 | 39 | #sidebar.sidebar-open { 40 | transform: translateX(0); 41 | } 42 | } 43 | 44 | #sidebar-nav { 45 | overflow-y: scroll; 46 | height: 45%; 47 | 48 | line-height: 1.1; 49 | border-bottom: 1px solid black; 50 | } 51 | 52 | #sidebar-layers { 53 | height: 45%; 54 | overflow-y: scroll; 55 | } 56 | 57 | #sidebar-layers > h4 { 58 | overflow: hidden; 59 | } 60 | 61 | #sidebar-controls { 62 | margin-top: auto; 63 | } 64 | -------------------------------------------------------------------------------- /media/server/media.js: -------------------------------------------------------------------------------- 1 | var checkMedia = function (mediaid) { 2 | var m = media.findOne(mediaid); 3 | if (m) return m; 4 | else throw new Meteor.Error('media-not-found', 'Could not find media with the _id: ' + mediaid); 5 | } 6 | 7 | Meteor.methods({ 8 | mediaSetNew: function (mediaid, state) { 9 | var m = checkMedia(mediaid); 10 | media.update(m, {$set: {new: state}}); 11 | }, 12 | 13 | mediaTitle: function (mediaid, title) { 14 | var m = checkMedia(mediaid); 15 | media.update(m, {$set: {title: title}}); 16 | }, 17 | 18 | mediaAddTags: function (mediaid, tags) { 19 | var m = checkMedia(mediaid); 20 | media.update(m, {$push: {tags: {$each: tags}}}); 21 | }, 22 | 23 | mediaDelTag: function (mediaid, tag) { 24 | var m = checkMedia(mediaid); 25 | media.update(m, {$pull: {tags: tag}}); 26 | }, 27 | 28 | mediaDel: function (mediaid) { 29 | var m = checkMedia(mediaid); 30 | var fs = Npm.require('fs'); 31 | 32 | var prefix = settings.findOne({key: 'mediadir'}).value + '/'; 33 | 34 | fs.unlink(prefix + m.location, function () {}); 35 | if (m['thumbnail']) fs.unlink(prefix + m.thumbnail, function () {}); 36 | 37 | media.remove(m); 38 | } 39 | }); 40 | -------------------------------------------------------------------------------- /sets/client/actiondisplay.js: -------------------------------------------------------------------------------- 1 | Template.actionDisplay.helpers({ 2 | actionType: function (type) { 3 | return this.type == type 4 | }, 5 | 6 | getMedia: function () { 7 | return media.findOne(this.media); 8 | }, 9 | 10 | getPlaylist: function () { 11 | return mediaplaylists.findOne(this.playlist); 12 | }, 13 | 14 | getLight: function () { 15 | return lights.findOne(this.light); 16 | }, 17 | 18 | getLightGroup: function () { 19 | return lightgroups.findOne(this.lightgroup); 20 | }, 21 | 22 | getLightScene: function () { 23 | return lightscenes.findOne(this.lightscene); 24 | }, 25 | 26 | getSong: function () { 27 | var song = songs.findOne(this.song); 28 | song.arrangement = songarrangements.findOne(this.settings.arrangement); 29 | song.action = this._id; 30 | return song; 31 | }, 32 | 33 | getPresentation: function () { 34 | var pres = presentations.findOne(this.presentation); 35 | pres.action = this._id; 36 | return pres; 37 | }, 38 | 39 | getPresentationSlide: function () { 40 | var slides = presentationslides.findOne(this.presentationslide); 41 | slide.action = this._id; 42 | return slide; 43 | }, 44 | 45 | getSequence: function () { 46 | return sequences.findOne(this.sequence); 47 | }, 48 | }); 49 | -------------------------------------------------------------------------------- /media/server/playlists.js: -------------------------------------------------------------------------------- 1 | var checkPlaylist = function (playlistid) { 2 | var p = mediaplaylists.findOne(playlistid); 3 | if (p) return p; 4 | else throw new Meteor.Error('playlist-not-found', 'Could not find playlist with the _id: ' + playlistid); 5 | }; 6 | 7 | Meteor.methods({ 8 | playlistNew: function () { 9 | return mediaplaylists.insert({ 10 | title: 'New Playlist', 11 | tags: [], 12 | settings: {}, 13 | contents: [] 14 | }); 15 | }, 16 | 17 | playlistTitle: function (playlistid, title) { 18 | var playlist = checkPlaylist(playlistid); 19 | mediaplaylists.update(playlist, {$set: {title: title}}); 20 | }, 21 | 22 | playlistAddTags: function (playlistid, tags) { 23 | var playlist = checkPlaylist(playlistid); 24 | mediaplaylists.update(playlist, {$push: {tags: {$each: tags}}}); 25 | }, 26 | 27 | playlistDelTag: function (playlistid, tag) { 28 | var playlist = checkPlaylist(playlistid); 29 | mediaplaylists.update(playlist, {$pull: {tags: tag}}); 30 | }, 31 | 32 | playlistDel: function (playlistid) { 33 | var playlist = checkPlaylist(playlistid); 34 | mediaplaylists.remove(playlist); 35 | }, 36 | 37 | playlistContents: function (playlistid, contents) { 38 | var playlist = checkPlaylist(playlistid); 39 | mediaplaylists.update(playlist, {$set: {contents: contents}}); 40 | } 41 | }); 42 | -------------------------------------------------------------------------------- /songs/lib/util.js: -------------------------------------------------------------------------------- 1 | key2num = {'A': 0, 'A#': 1, 'Bb': 1, 'B': 2, 'C': 3, 'C#': 4, 'Db': 4, 'D': 5, 'D#': 6, 'Eb': 6, 'E': 7, 'F': 8, 'F#': 9, 'Gb': 9, 'G': 10, 'G#': 11, 'Ab': 11}; 2 | num2keyflat = ['A','Bb','B','C','Db','D','Eb','E','F','Gb','G','Ab']; 3 | num2keysharp = ['A','A#','B','C','C#','D','D#','E','F','F#','G','G#']; 4 | 5 | songTextToCanvas = function (text) { 6 | return text.replace(/\[[^\[]*\]/g, '').replace(/ +/g, ' ');; 7 | }; 8 | 9 | songTextToHTML = function (text) { 10 | text = text.replace(/(\r\n|\n|\r)/gm, '
') 11 | text = text.replace(/\[[^\[]*\]/g, ''); 12 | return text; 13 | }; 14 | 15 | songTextToChordChart = function (text, transpose, flat) { 16 | text = text.replace(/(\r\n|\n|\r)/gm, '
'); 17 | text = text.replace(/\[[^\[]*\]/g, (tag) => { 18 | if (tag[tag.length-2] == '_') { 19 | var full = true; 20 | tag = tag.substring(1, tag.length-2); 21 | } else { 22 | var full = false; 23 | tag = tag.substring(1, tag.length-1); 24 | } 25 | 26 | tag = tag.replace(/[CDEFGAB][#b]?/g, (chord) => { 27 | var n = (key2num[chord] + transpose) % 12; 28 | if (n < 0) n += 12; 29 | 30 | if (flat) return num2keyflat[n]; 31 | else return num2keysharp[n]; 32 | }); 33 | 34 | if (full) return `${tag}`; 35 | else return `${tag}`; 36 | }); 37 | 38 | return text; 39 | }; 40 | -------------------------------------------------------------------------------- /sequences/server/sequences.js: -------------------------------------------------------------------------------- 1 | Meteor.startup(function () { 2 | sequence_handlers = {}; 3 | }); 4 | 5 | Meteor.methods({ 6 | sequenceNew: function () { 7 | var _id = sequences.insert({ 8 | title: 'New Sequence', 9 | stage: null, 10 | settings: { 11 | duration: 30.0, 12 | loop: 'no', 13 | useBPM: 'no', 14 | bpm: 60 15 | } 16 | }); 17 | 18 | return _id; 19 | }, 20 | 21 | sequenceTitle: function (sequence, title) { 22 | sequences.update(sequence, {$set: {title: title}}); 23 | }, 24 | 25 | sequenceStage: function (sequence, stage) { 26 | sequences.update(sequence, {$set: {stage: stage}}); 27 | }, 28 | 29 | sequenceSetting: function (sequence, setting, value) { 30 | var s = {}; s['settings.' + setting] = value; 31 | sequences.update(sequence, {$set: s}); 32 | }, 33 | 34 | sequenceActionActivate: function (action) { 35 | var sequence = sequences.findOne(action.sequence) 36 | var stage = stages.findOne(sequence.stage); 37 | 38 | var s = {}; s['sequences.' + action.settings.sequence_channel] = sequence._id; 39 | stages.update(stage, {$set: s}); 40 | }, 41 | 42 | sequenceDeactivateChannel: function (channel) { 43 | // TODO this should only reset one stage's channel, not all. 44 | var s = {}; s['sequences.' + channel] = null; 45 | stages.update({}, {$unset: s}, {multi: true}); 46 | } 47 | }); 48 | -------------------------------------------------------------------------------- /lighting/client/valueselector.js: -------------------------------------------------------------------------------- 1 | toHexColor = function (i) {return ('0' + Math.round(i * 255 || 0).toString(16)).slice(-2)}; 2 | 3 | Template.valueSelector.helpers({ 4 | channels: function () { 5 | var dc = []; 6 | for (var c in this.channels) { 7 | var channel = this.channels[c]; 8 | channel.index = c; 9 | if (channel.type != 'fixed') dc.push(channel); 10 | } 11 | 12 | return dc; 13 | }, 14 | 15 | hasColor: function () { 16 | for (var i in this.channels) { 17 | if (['red', 'green', 'blue'].indexOf(this.channels[i].type) != -1) return true; 18 | } 19 | 20 | return false; 21 | }, 22 | 23 | getColor: function () { 24 | var color = {red: 0, green: 0, blue: 0, intensity: 1}; 25 | for (var c in this.channels) { 26 | color[this.channels[c].type] = this.values[c]; 27 | } 28 | 29 | var string = '#' + toHexColor(color.red * color.intensity) + toHexColor(color.green * color.intensity) + toHexColor(color.blue * color.intensity); 30 | return string; 31 | }, 32 | }); 33 | 34 | Template.valueSelector.onRendered(function () { 35 | this.sliders = []; 36 | this.$('.light-slider').each((i, e) => { 37 | this.sliders.push(new Slider(e, {max: 1, step: 0.01, tooltip: 'hide'})); 38 | }); 39 | 40 | this.autorun(() => { 41 | var d = Template.currentData() 42 | for (var c in d.channels) { 43 | this.sliders[c].setValue(d.values[c]); 44 | } 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /musicstand/client/musicstand.js: -------------------------------------------------------------------------------- 1 | Template.musicstand.helpers({ 2 | actions: function () { 3 | return actions.find({set: this._id}, {sort: {order: 1}}); 4 | }, 5 | 6 | typeIs: function (type) { 7 | return this.type == type; 8 | }, 9 | 10 | isActive: function () { 11 | if (Template.parentData()['active'] == this._id) return 'musicstand-active'; 12 | }, 13 | 14 | getSong: function () { 15 | var song = songs.findOne(this.song); 16 | song.arrangement = songarrangements.findOne(this.settings.arrangement); 17 | song.action = this._id; 18 | return song; 19 | } 20 | }); 21 | 22 | Template.musicstand.onRendered(function () { 23 | this.autorun(function () { 24 | // Bind to the current Set and Action data context so this gets autorun when they change. 25 | var activeid = Template.currentData().active; 26 | var action = actions.findOne(activeid); 27 | 28 | if ($('#scroll-lock').hasClass('active')) { 29 | if (action.type == 'song' && action.args['section'] !== null ) { 30 | scrollTo(`#${action._id}-${action.args.section} .musicstand-chart:nth-child(${action.args.index+2})`); 31 | } else { 32 | scrollTo(`#action-${action._id}`); 33 | } 34 | } 35 | }); 36 | }); 37 | 38 | Template.musicstand.events({ 39 | 'click #scroll-lock': function (event, template) { 40 | $(event.currentTarget).toggleClass('active'); 41 | if ($(event.currentTarget).hasClass('active')); 42 | } 43 | }); 44 | -------------------------------------------------------------------------------- /sets/client/templates/sidebar.html: -------------------------------------------------------------------------------- 1 | 43 | -------------------------------------------------------------------------------- /main/server/update.js: -------------------------------------------------------------------------------- 1 | // misc updates for when db schema changes, which is currently pretty often 2 | 3 | Meteor.startup(function () { 4 | // make sure stages have layers object 5 | stages.find({layers: {$exists: false}}).forEach((stage) => { 6 | var layers = {}; 7 | stage.settings.layers.forEach((layer) => { 8 | layers[layer] = null; 9 | }); 10 | 11 | stages.update(stage, {$set: {layers: layers}}); 12 | }); 13 | 14 | // make sure stages have sequences object 15 | stages.update({sequences: {$exists: false}}, {$set: {sequences: {}}}, {multi: true}); 16 | 17 | // change layers from object to array 18 | minions.find({type: 'media'}).forEach((minion) => { 19 | if (minion.layers.__proto__ !== Array.prototype) { 20 | var layers = []; 21 | for (var l in minion.layers) { 22 | if (minion.layers.hasOwnProperty(l)) layers.push(l); 23 | } 24 | 25 | minions.update(minion, {$set: {layers: layers}}); 26 | } 27 | }); 28 | 29 | // move name variable to title 30 | minions.find({name: {$exists: true}}).forEach((minion) => { 31 | minions.update(minion, {$set: {title: minion.name}, $unset: {name: null}}); 32 | }); 33 | 34 | // ensure all presentations have settings object 35 | presentations.update({settings: {$exists: false}}, {$set: {settings: {}}}, {multi: true}); 36 | 37 | // ensure all presentations have imported property 38 | presentations.update({imported: {$exists: false}}, {$set: {imported: false}}, {multi: true}); 39 | }); 40 | -------------------------------------------------------------------------------- /songs/client/templates/arrangement.html: -------------------------------------------------------------------------------- 1 | 41 | -------------------------------------------------------------------------------- /media/client/mediasettings.js: -------------------------------------------------------------------------------- 1 | Template.mediaSettings.helpers({ 2 | typeIs: function () { 3 | for (var arg in arguments) { 4 | if (arguments[arg] == this.type) return true; 5 | } 6 | }, 7 | 8 | mediaPath: function () { 9 | return settings.findOne({key: 'mediaurl'}).value + this.location; 10 | }, 11 | 12 | getLength: function () { 13 | return secondsToTimeString(parseFloat(this.duration)); 14 | } 15 | }); 16 | 17 | Template.mediaSettings.onRendered(function () { 18 | if (this.data && this.data['new']) Meteor.call('mediaSetNew', this.data._id, false); 19 | }); 20 | 21 | Template.mediaSettings.events({ 22 | 'blur .media-title': function (event, template) { 23 | var title = $(event.target).val(); 24 | Meteor.call('mediaTitle', this._id, title); 25 | }, 26 | 27 | 'click .tag-add, keypress .tag-input': function (event, template) { 28 | if (event.type == 'keypress' && event.which != 13) return true; 29 | 30 | var tags = template.$('.tag-input').val().split(','); 31 | 32 | for (var i in tags) { 33 | tags[i] = tags[i].trim(); 34 | if (tags[i].length == 0) tags.splice(i, 1); 35 | } 36 | 37 | Meteor.call('mediaAddTags', this._id, tags); 38 | template.$('.tag-input').val(''); 39 | }, 40 | 41 | 'click .media-tag': function (event, template) { 42 | var tag = $(event.target).data('tag'); 43 | Meteor.call('mediaDelTag', template.data._id, tag); 44 | }, 45 | 46 | 'click .media-del': function (event, template) { 47 | Meteor.call('mediaDel', this._id); 48 | Router.go('/media/items'); 49 | } 50 | }); 51 | -------------------------------------------------------------------------------- /sets/client/templates/specialactions.html: -------------------------------------------------------------------------------- 1 | 44 | -------------------------------------------------------------------------------- /main/server/actionactivate.js: -------------------------------------------------------------------------------- 1 | action_activate = function (action) { 2 | if (action.set) action.stage = sets.findOne(action.set).stage; 3 | else if (action.schedule) action.stage = schedules.findOne(action.schedule).stage; 4 | 5 | if (action.type == 'media' || action.type == 'playlist' || 6 | action.type == 'clear-layer' || action.type == 'timer' || 7 | action.type == 'song' || action.type == 'presentation' || 8 | action.type == 'presentationslide' || action.type == 'camera') { 9 | 10 | var l = {}; l['layers.' + action.layer] = action; 11 | stages.update(action.stage, {$set: l}); 12 | } 13 | 14 | else if (action.type == 'light') { 15 | var settings = { 16 | time: action.time, 17 | fade: parseFloat(combineSettings(action.settings).lights_fade) 18 | }; 19 | 20 | Meteor.call('lightValues', action.light, action.settings.values, settings); 21 | } 22 | 23 | else if (action.type == 'lightgroup') { 24 | var settings = { 25 | time: action.time, 26 | fade: parseFloat(combineSettings(action.settings).lights_fade) 27 | }; 28 | 29 | Meteor.call('lightGroupValues', action.lightgroup, action.settings.values, settings); 30 | } 31 | 32 | else if (action.type == 'lightscene') { 33 | Meteor.call('sceneActionActivate', action); 34 | } 35 | 36 | else if (action.type == 'sequence') { 37 | Meteor.call('sequenceActionActivate', action); 38 | } 39 | 40 | else if (action.type == 'clear-channel') { 41 | Meteor.call('sequenceClearChannel', action.settings.sequence_channel); 42 | } 43 | 44 | else if (action.type == 'custom') { 45 | Meteor.call('customActionTriggered', action.settings.action_string || ''); 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /minions/client/stagesettings.js: -------------------------------------------------------------------------------- 1 | Template.stageSettings.events({ 2 | 'blur #stage-title': function (event, template) { 3 | Meteor.call('stageTitle', template.data._id, $('#stage-title').val()); 4 | }, 5 | 6 | 'click #add-layer, keypress #add-layer-title': function (event, template) { 7 | if (event.type == 'keypress' && event.which != 13) return; 8 | 9 | Meteor.call('stageAddLayer', template.data._id, $('#add-layer-title').val()); 10 | $('#add-layer-title').val(''); 11 | }, 12 | 13 | 'click .layer-down': function (event, template) { 14 | var layers = template.data.settings.layers; 15 | var i = layers.indexOf(this.toString()); 16 | if (i < layers.length-1) { 17 | layers.splice(i+1, 0, layers.splice(i, 1)[0]); 18 | Meteor.call('stageSetting', template.data._id, 'layers', layers); 19 | } 20 | }, 21 | 22 | 'click .layer-up': function (event, template) { 23 | var layers = template.data.settings.layers; 24 | var i = layers.indexOf(this.toString()); 25 | if (i > 0) { 26 | layers.splice(i-1, 0, layers.splice(i, 1)[0]); 27 | Meteor.call('stageSetting', template.data._id, 'layers', layers); 28 | } 29 | }, 30 | 31 | 'click .layer-del': function (event, template) { 32 | Meteor.call('stageDelLayer', template.data._id, this.toString()); 33 | }, 34 | 35 | 'click .stage-settings-delete': function (event, template) { 36 | template.$('#delete-modal').modal('show'); 37 | }, 38 | 39 | 'click .stage-delete-cancel': function (event, template) { 40 | template.$('#delete-modal').modal('hide'); 41 | }, 42 | 43 | 'click .stage-delete-confirm': function (event, template) { 44 | template.$('#delete-modal').removeClass('fade').modal('hide'); 45 | Meteor.call('stageDelete', this._id); 46 | Router.go('/minions/'); 47 | } 48 | }); 49 | -------------------------------------------------------------------------------- /songs/client/settings.js: -------------------------------------------------------------------------------- 1 | Template.songSettings.helpers({ 2 | getKeys: function () { 3 | var keys = []; 4 | for (var p in key2num) if (key2num.hasOwnProperty(p)) keys.push(p); 5 | return keys; 6 | }, 7 | 8 | keySelected: function (key) { 9 | if (Template.parentData().key == key) return 'selected'; 10 | }, 11 | 12 | sections: function () { 13 | return songsections.find({song: this._id}); 14 | }, 15 | 16 | arrangements: function () { 17 | return songarrangements.find({song: this._id}); 18 | } 19 | }); 20 | 21 | Template.songSettings.events({ 22 | 'click .song-title': function (event, template) { 23 | template.$('.song-title').addClass('hidden'); 24 | template.$('.song-title-edit').removeClass('hidden'); 25 | }, 26 | 27 | 'blur .song-title-edit': function (event, template) { 28 | Meteor.call('songTitle', this._id, template.$('.song-title-edit').val()); 29 | template.$('.song-title-edit').addClass('hidden'); 30 | template.$('.song-title').removeClass('hidden'); 31 | }, 32 | 33 | 'click #song-del': function (event, template) { 34 | Meteor.call('songDel', template.data._id); 35 | Router.go('/songs'); 36 | }, 37 | 38 | 'change #song-key': function (event, template) { 39 | Meteor.call('songKey', template.data._id, $(event.target).val()); 40 | }, 41 | 42 | 'click #section-add': function (event, template) { 43 | Meteor.call('songAddSection', this._id); 44 | }, 45 | 46 | 'click .section-del': function (event, template) { 47 | Meteor.call('songDelSection', this._id); 48 | }, 49 | 50 | 'click #arrangement-add': function (event, template) { 51 | Meteor.call('songAddArrangement', this._id); 52 | }, 53 | 54 | 'click .arrangement-del': function (event, template) { 55 | Meteor.call('songDelArrangement', this._id); 56 | } 57 | }); 58 | -------------------------------------------------------------------------------- /sets/client/templates/actiondisplay.html: -------------------------------------------------------------------------------- 1 | 78 | -------------------------------------------------------------------------------- /media/client/templates/mediasettings.html: -------------------------------------------------------------------------------- 1 | 61 | -------------------------------------------------------------------------------- /minions/client/templates/settingsdisplay.html: -------------------------------------------------------------------------------- 1 | 42 | -------------------------------------------------------------------------------- /minions/server/stages.js: -------------------------------------------------------------------------------- 1 | function checkStage(stageid) { 2 | var stage = stages.findOne({_id: stageid}); 3 | if (!stage) { 4 | throw new Meteor.Error('stage-not-found', "Can't find a stage with id: " + stageid); 5 | } 6 | 7 | return stage; 8 | } 9 | 10 | Meteor.methods({ 11 | stageNew: function () { 12 | stages.insert({ 13 | title: 'New Stage', 14 | active: null, 15 | settings: { 16 | layers: ['audio', 'background', 'foreground'] 17 | }, 18 | layers: { 19 | 'audio': null, 20 | 'background': null, 21 | 'foreground': null 22 | }, 23 | sequences: {} 24 | }); 25 | }, 26 | 27 | stageDelete: function (stageid) { 28 | var stage = checkStage(stageid); 29 | stages.remove(stage); 30 | minions.update({stage: stageid}, 31 | {$set: {stage: null}}, 32 | {multi: true}); 33 | }, 34 | 35 | stageTitle: function (stageid, newtitle) { 36 | var stage = checkStage(stageid); 37 | stages.update(stage, {$set: {title: newtitle}}); 38 | }, 39 | 40 | stageSetting: function (stageid, key, value) { 41 | var stage = checkStage(stageid); 42 | var s = {}; s['settings.' + key] = value; 43 | stages.update(stage, {$set: s}); 44 | }, 45 | 46 | stageAddLayer: function (stageid, layer) { 47 | var stage = checkStage(stageid); 48 | var s = {}; s['layers.' + layer] = null; 49 | stages.update(stage, {$push: {'settings.layers': layer}, $set: s}); 50 | }, 51 | 52 | stageDelLayer: function (stageid, layer) { 53 | var stage = checkStage(stageid); 54 | var s = {}; s['layers.' + layer] = null; 55 | stages.update(stage, {$pull: {'settings.layers': layer}, $unset: s}); 56 | }, 57 | 58 | stageLayer: function (stageid, layer, value) { 59 | var stage = checkStage(stageid); 60 | var l = {}; l['layers.' + layer] = value; 61 | stages.update(stage, {$set: l}); 62 | }, 63 | }); 64 | -------------------------------------------------------------------------------- /media/lib/routes.js: -------------------------------------------------------------------------------- 1 | Router.route('/media', {name: 'mediaMenu'}); 2 | 3 | Router.route('/media/items', {name: 'mediaItems'}); 4 | Router.route('/media/item/:_id', { 5 | name: 'mediaSettings', 6 | data: function () {return media.findOne(this.params._id);} 7 | }); 8 | 9 | Router.route('/media/playlists', {name: 'mediaPlaylists'}); 10 | Router.route('/media/playlist/:_id', { 11 | name: 'mediaPlaylistSettings', 12 | data: function () {return mediaplaylists.findOne(this.params._id);} 13 | }); 14 | 15 | Router.route('/media/static/:filepath*', function () { 16 | var fs = Npm.require('fs'); 17 | var filepath = settings.findOne({key: 'mediadir'}).value + '/' + this.params.filepath; 18 | var mimetype = MIME.lookup(filepath); 19 | 20 | try { 21 | var stats = fs.statSync(filepath); 22 | } 23 | 24 | catch (e) { 25 | this.next(); 26 | return; 27 | } 28 | 29 | if (!stats.isFile()) { 30 | this.next(); 31 | return; 32 | } 33 | 34 | var status = 200; 35 | var headers = { 36 | 'Cache-Control': 'max-age=2592000', // 30 days 37 | 'Content-Length': stats.size, 38 | 'Content-Type': mimetype, 39 | 'Accept-Ranges': 'bytes' 40 | }; 41 | 42 | // Very hacky HTTP Range implementation! 43 | 44 | var start = 0; var end = stats.size; 45 | var range = this.request.headers['range']; 46 | 47 | if (range && range.startsWith('bytes=')) { 48 | var range = range.slice(6); 49 | var dashindex = range.indexOf('-'); 50 | 51 | if (dashindex <= 0) { 52 | var start = 0; 53 | var end = parseInt(range.split('-')[0]); 54 | } else if (dashindex == range.length - 1) { 55 | var start = parseInt(range.split('-')[0]); 56 | var end = stats.size; 57 | } 58 | 59 | headers['Content-Range'] = `bytes ${start}-${end}/${stats.size}`; 60 | headers['Content-Length'] = end - start; 61 | var status = 206; 62 | } 63 | 64 | this.response.writeHead(status, headers); 65 | var stream = fs.createReadStream(filepath, {start: start, end: end}); 66 | return stream.pipe(this.response); 67 | }, {where: 'server'}); 68 | -------------------------------------------------------------------------------- /.meteor/versions: -------------------------------------------------------------------------------- 1 | allow-deny@1.0.5 2 | autoupdate@1.3.12 3 | babel-compiler@6.19.3 4 | babel-runtime@1.0.1 5 | base64@1.0.10 6 | binary-heap@1.0.10 7 | blaze@2.3.2 8 | blaze-html-templates@1.1.2 9 | blaze-tools@1.0.10 10 | boilerplate-generator@1.1.1 11 | caching-compiler@1.1.9 12 | caching-html-compiler@1.1.2 13 | callback-hook@1.0.10 14 | cfs:graphicsmagick@0.0.18 15 | check@1.2.5 16 | ddp@1.2.5 17 | ddp-client@1.3.4 18 | ddp-common@1.2.8 19 | ddp-server@1.3.14 20 | deps@1.0.12 21 | diff-sequence@1.0.7 22 | dynamic-import@0.1.1 23 | ecmascript@0.8.1 24 | ecmascript-runtime@0.4.1 25 | ecmascript-runtime-client@0.4.2 26 | ecmascript-runtime-server@0.4.1 27 | ejson@1.0.13 28 | fastclick@1.0.13 29 | fish:ffmpeg@1.0.0 30 | geojson-utils@1.0.10 31 | hot-code-push@1.0.4 32 | html-tools@1.0.11 33 | htmljs@1.0.11 34 | http@1.2.12 35 | hunternet93:quilljs@1.1.0 36 | id-map@1.0.9 37 | iron:controller@1.0.12 38 | iron:core@1.0.11 39 | iron:dynamic-template@1.0.12 40 | iron:layout@1.0.12 41 | iron:location@1.0.11 42 | iron:middleware-stack@1.1.0 43 | iron:router@1.1.2 44 | iron:url@1.1.0 45 | jquery@1.11.10 46 | launch-screen@1.1.1 47 | livedata@1.0.18 48 | logging@1.1.17 49 | meteor@1.6.1 50 | meteor-base@1.1.0 51 | minifier-css@1.2.16 52 | minifier-js@2.1.0 53 | minimongo@1.2.1 54 | mobile-experience@1.0.4 55 | mobile-status-bar@1.0.14 56 | modules@0.9.2 57 | modules-runtime@0.8.0 58 | momentjs:moment@2.18.1 59 | mongo@1.1.18 60 | mongo-id@1.0.6 61 | mrt:later@1.6.1 62 | npm-mongo@2.2.24 63 | observe-sequence@1.0.16 64 | ordered-dict@1.0.9 65 | patte:mime-npm@0.0.1 66 | pfafman:font-awesome-4@4.6.1 67 | promise@0.8.9 68 | random@1.0.10 69 | reactive-dict@1.1.9 70 | reactive-var@1.0.11 71 | reload@1.1.11 72 | retry@1.0.9 73 | rgnevashev:bootstrap-slider@6.1.6 74 | risul:bootstrap-colorpicker@2.3.6 75 | risul:moment-timezone@0.5.7 76 | routepolicy@1.0.12 77 | session@1.1.7 78 | shell-server@0.2.3 79 | spacebars@1.0.15 80 | spacebars-compiler@1.1.2 81 | standard-minifier-css@1.3.4 82 | standard-minifier-js@2.1.0 83 | templating@1.3.2 84 | templating-compiler@1.3.2 85 | templating-runtime@1.3.2 86 | templating-tools@1.1.2 87 | tomi:upload-jquery@2.4.0 88 | tomi:upload-server@1.3.4_3 89 | tracker@1.1.3 90 | tsega:bootstrap3-datetimepicker@4.17.37_1 91 | twbs:bootstrap@3.3.6 92 | ui@1.0.13 93 | underscore@1.0.10 94 | url@1.1.0 95 | webapp@1.3.16 96 | webapp-hashing@1.0.9 97 | -------------------------------------------------------------------------------- /sets/client/actionselector.js: -------------------------------------------------------------------------------- 1 | Template.actionSelector.helpers({ 2 | mediaSelector: { 3 | collection: media, 4 | displayTemplate: 'mediaItem', 5 | fields: [{field: 'title', type: String}, {field: 'tags', type: Array}], 6 | sort: [['title', 1]], 7 | addbutton: true 8 | }, 9 | 10 | playlistSelector: { 11 | collection: mediaplaylists, 12 | displayTemplate: 'mediaPlaylist', 13 | fields: [{field: 'title', type: String}, {field: 'tags', type: Array}], 14 | sort: [['title', 'asc']], 15 | addbutton: true 16 | }, 17 | 18 | lightSelector: { 19 | collection: lights, 20 | displayTemplate: 'light', 21 | fields: [{field: 'title', type: String}, {field: 'stage', type: Stage}], 22 | sort: [['title', 1]], 23 | addbutton: true 24 | }, 25 | 26 | lightgroupSelector: { 27 | collection: lightgroups, 28 | displayTemplate: 'lightGroup', 29 | fields: [{field: 'title', type: String}, {field: 'stage', type: Stage}], 30 | sort: [['title', 1]], 31 | addbutton: true 32 | }, 33 | 34 | sceneSelector: { 35 | collection: lightscenes, 36 | displayTemplate: 'lightScene', 37 | fields: [{field: 'title', type: String}], 38 | sort: [['title', 1]], 39 | addbutton: true 40 | }, 41 | 42 | songSelector: { 43 | collection: songs, 44 | displayTemplate: 'song', 45 | fields: [{field: 'title', type: String}, {field: 'tags', type: Array}], 46 | sort: [['title', 1]], 47 | addbutton: true 48 | }, 49 | 50 | presentationSelector: { 51 | collection: presentations, 52 | displayTemplate: 'presentation', 53 | fields: [{field: 'title', type: String}], 54 | sort: [['title', 1]], 55 | addbutton: true, 56 | altbutton: true 57 | }, 58 | 59 | sequenceSelector: { 60 | collection: sequences, 61 | displayTemplate: 'sequence', 62 | fields: [{field: 'title', type: String}], 63 | sort: [['title', 1]], 64 | addbutton: true 65 | } 66 | }); 67 | 68 | Template.actionSelector.events({ 69 | 'click .action-selector-cancel': function (event) { 70 | $(event.target).parents('.modal').modal('hide'); 71 | } 72 | }); 73 | -------------------------------------------------------------------------------- /songs/client/arrangement.js: -------------------------------------------------------------------------------- 1 | Template.songArrangement.helpers({ 2 | getOrder: function () { 3 | var out = []; 4 | for (var i in this.order) { 5 | out.push({ 6 | title: songsections.findOne(this.order[i]).title, 7 | index: parseInt(i) 8 | }); 9 | } 10 | 11 | return out; 12 | }, 13 | 14 | sections: function () { 15 | return songsections.find({song: this.song}); 16 | } 17 | }); 18 | 19 | Template.songArrangement.events({ 20 | 'click .arrangement-title': function (event, template) { 21 | template.$('.arrangement-title').addClass('hidden'); 22 | template.$('.arrangement-title-edit').removeClass('hidden'); 23 | }, 24 | 25 | 'blur .arrangement-title-edit': function (event, template) { 26 | Meteor.call('songArrangementTitle', this._id, template.$('.arrangement-title-edit').val()); 27 | template.$('.arrangement-title').addClass('hidden'); 28 | template.$('.arrangement-title-edit').removeClass('hidden'); 29 | }, 30 | 31 | 'click .order-add': function (event, template) { 32 | var order = template.data.order; 33 | order.push(this._id); 34 | Meteor.call('songArrangementOrder', template.data._id, order); 35 | }, 36 | 37 | 'click .order-down': function (event, template) { 38 | var order = template.data.order; 39 | if (this.index < order.length-1) { 40 | order.splice(this.index+1, 0, order.splice(this.index, 1)[0]); 41 | Meteor.call('songArrangementOrder', template.data._id, order); 42 | } 43 | }, 44 | 45 | 'click .order-up': function (event, template) { 46 | var order = template.data.order; 47 | if (this.index > 0) { 48 | order.splice(this.index-1, 0, order.splice(this.index, 1)[0]); 49 | Meteor.call('songArrangementOrder', template.data._id, order); 50 | } 51 | }, 52 | 53 | 'click .order-del': function (event, template) { 54 | var order = template.data.order; 55 | order.splice(this.index, 1); 56 | Meteor.call('songArrangementOrder', template.data._id, order); 57 | }, 58 | 59 | 'click #arrangement-del': function (event, template) { 60 | Meteor.call('songDelArrangement', template.data._id); 61 | } 62 | }); 63 | -------------------------------------------------------------------------------- /schedule/client/templates/scheduleItem.html: -------------------------------------------------------------------------------- 1 | 46 | -------------------------------------------------------------------------------- /songs/client/templates/settings.html: -------------------------------------------------------------------------------- 1 | 67 | -------------------------------------------------------------------------------- /lighting/server/lights.js: -------------------------------------------------------------------------------- 1 | function checkLight(lightid) { 2 | var light = lights.findOne(lightid); 3 | if (!light) { 4 | throw new Meteor.Error('light-not-found', "Can't find a light with _id " + lightid); 5 | } 6 | 7 | return light; 8 | } 9 | 10 | Meteor.methods({ 11 | lightNew: function () { 12 | var lightid = lights.insert({ 13 | title: 'New Light', 14 | minion: null, 15 | stage: null, 16 | enabled: true, 17 | channels: [], 18 | values: [], 19 | }); 20 | 21 | return lightid; 22 | }, 23 | 24 | lightClone: function (lightid) { 25 | var light = checkLight(lightid); 26 | delete light._id; 27 | 28 | var t = light.title.split(' '); 29 | if (!isNaN(parseInt(t[t.length-1]))) { 30 | t[t.length-1] = parseInt(t[t.length-1]) + 1; 31 | light.title = t.join(' '); 32 | } else light.title = light.title + ' (copy)'; 33 | 34 | return lights.insert(light); 35 | }, 36 | 37 | lightDelete: function (lightid) { 38 | var light = checkLight(lightid); 39 | lights.remove(light); 40 | }, 41 | 42 | lightTitle: function (lightid, title) { 43 | var light = checkLight(lightid); 44 | lights.update(light, {$set: {title: title}}); 45 | }, 46 | 47 | lightMinion: function (lightid, minionid) { 48 | var light = checkLight(lightid); 49 | var stageid = minions.findOne(minionid).stage; 50 | lights.update(light, {$set: {minion: minionid, stage: stageid}}); 51 | }, 52 | 53 | lightEnabled: function (lightid, enabled) { 54 | var light = checkLight(lightid); 55 | lights.update(light, {$set: {enabled: enabled}}); 56 | }, 57 | 58 | lightChannels: function (lightid, channels) { 59 | var light = checkLight(lightid); 60 | 61 | lights.update(light, {$set: {channels: channels}}); 62 | }, 63 | 64 | lightValues: function (lightid, values, settings) { 65 | var light = checkLight(lightid); 66 | 67 | for (var c in light.channels) { 68 | if (light.channels[c].type == 'fixed') values[c] = light.channels[c].value; 69 | } 70 | 71 | if (!settings['time']) settings.time = Date.now() * 0.001; 72 | 73 | if (!light.disabled) lights.update(light, {$set: {values: values, settings: settings}}); 74 | } 75 | }); 76 | -------------------------------------------------------------------------------- /presentations/client/displayslide.js: -------------------------------------------------------------------------------- 1 | Template.presentationDisplaySlide.helpers({ 2 | isActive: function () { 3 | var set = Template.parentData(3); 4 | var action = Template.parentData(2); 5 | 6 | if (set.active == action._id) { 7 | if (this.order == action.args.order) return 'active'; 8 | } 9 | }, 10 | 11 | getMedia: function () { 12 | return media.findOne(this.toString()); 13 | }, 14 | 15 | backgroundColor: function () { 16 | var pres = presentations.findOne(this.presentation); 17 | var color = combineSettings(pres.settings, this.settings).presentations_background_color; 18 | return `rgb(${Math.round(color[0] * 255.0)}, ${Math.round(color[1] * 255.0)}, ${Math.round(color[2] * 255.0)})`; 19 | }, 20 | 21 | fontColor: function () { 22 | var pres = presentations.findOne(this.presentation); 23 | var color = combineSettings(pres.settings, this.settings).presentations_font_color; 24 | return `rgb(${Math.round(color[0] * 255.0)}, ${Math.round(color[1] * 255.0)}, ${Math.round(color[2] * 255.0)})`; 25 | }, 26 | 27 | fillins: function () { 28 | var tags = 1; 29 | var numbers = [{action: this.action, order: this.order, fillin: 0}]; 30 | 31 | var cont = false; 32 | 33 | this.content.ops.forEach((section, i) => { 34 | if (section['attributes'] && section['attributes']['strike']) { 35 | if (!cont) { 36 | numbers.push({action: this.action, order: this.order, fillin: tags++}); 37 | cont = true; 38 | } 39 | } else { 40 | cont = false; 41 | } 42 | }); 43 | 44 | if (numbers.length > 1) return numbers; 45 | }, 46 | 47 | fillinActive: function () { 48 | var set = Template.parentData(4); 49 | var action = Template.parentData(3); 50 | 51 | if (set.active == action._id) { 52 | if (this.order == action.args.order && this.fillin == action.args.fillin) return 'active'; 53 | } 54 | } 55 | }); 56 | 57 | 58 | Template.presentationDisplaySlide.onRendered(function () { 59 | this.quill = new Quill(this.$('.display-container')[0], { 60 | readOnly: true 61 | }); 62 | 63 | this.autorun(function () { 64 | var content = presentationslides.findOne(Template.currentData()._id).content; 65 | Template.instance().quill.setContents(content); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /media/client/playlistsettings.js: -------------------------------------------------------------------------------- 1 | Template.mediaPlaylistSettings.helpers({ 2 | mediaSelector: { 3 | collection: media, 4 | displayTemplate: 'mediaItem', 5 | fields: [{field: 'title', type: String}, {field: 'tags', type: Array}], 6 | sort: [['title', 'asc']], 7 | addbutton: true 8 | }, 9 | 10 | getContents: function () { 11 | var out = []; 12 | for (var i in this.contents) { 13 | var m = media.findOne(this.contents[i]); 14 | m.index = parseInt(i); 15 | out.push(m) 16 | } 17 | 18 | return out; 19 | } 20 | }); 21 | 22 | Template.mediaPlaylistSettings.events({ 23 | 'click #settings-toggle': function (event, template) { 24 | template.$('#settings-pane').collapse('toggle'); 25 | }, 26 | 27 | 'click #delete': function (event, template) { 28 | Meteor.call('playlistDel', template.data._id); 29 | Router.go('/media/playlists'); 30 | }, 31 | 32 | 'blur #title': function (event, template) { 33 | Meteor.call('playlistTitle', template.data._id, $(event.target).val()); 34 | }, 35 | 36 | 'click #add-item': function (event, template) { 37 | template.$('#media-modal').modal('show'); 38 | }, 39 | 40 | 'click .collection-add': function (event, template) { 41 | var contents = template.data.contents; 42 | contents.push($(event.target).data('id')); 43 | Meteor.call('playlistContents', template.data._id, contents); 44 | }, 45 | 46 | 'click .modal-close': function (event, template) { 47 | template.$('#media-modal').modal('hide'); 48 | }, 49 | 50 | 'click .item-down': function (event, template) { 51 | var contents = template.data.contents; 52 | if (this.index < contents.length-1) { 53 | contents.splice(this.index+1, 0, contents.splice(this.index, 1)[0]); 54 | Meteor.call('playlistContents', template.data._id, contents); 55 | } 56 | }, 57 | 58 | 'click .item-up': function (event, template) { 59 | var contents = template.data.contents; 60 | if (this.index > 0) { 61 | contents.splice(this.index-1, 0, contents.splice(this.index, 1)[0]); 62 | Meteor.call('playlistContents', template.data._id, contents); 63 | } 64 | }, 65 | 66 | 'click .item-del': function (event, template) { 67 | var contents = template.data.contents; 68 | contents.splice(this.index, 1); 69 | Meteor.call('playlistContents', template.data._id, contents); 70 | } 71 | }); 72 | -------------------------------------------------------------------------------- /main/client/templates/collectionselector.html: -------------------------------------------------------------------------------- 1 | 62 | -------------------------------------------------------------------------------- /schedule/server/schedule.js: -------------------------------------------------------------------------------- 1 | schedule_handles = {}; 2 | 3 | function schedule_callback (scheduleid) { 4 | var schedule = schedules.findOne(scheduleid); 5 | var time = (Date.now() + 100) * 0.001; 6 | 7 | actions.find({schedule: schedule._id}).forEach((action) => { 8 | action.time = time; 9 | action_activate(action); 10 | }); 11 | } 12 | 13 | function schedule_register (schedule) { 14 | if (schedule_handles[schedule._id]) { 15 | schedule_handles[schedule._id].clear(); 16 | delete schedule_handles[schedule._id]; 17 | } 18 | 19 | if (schedule.active && schedule.when.schedules.length > 0) { 20 | schedule_handles[schedule._id] = later.setInterval( 21 | Meteor.bindEnvironment(schedule_callback.bind(this, schedule._id)), 22 | schedule.when 23 | ); 24 | } 25 | } 26 | 27 | Meteor.startup(function () { 28 | later.date.localTime(); 29 | 30 | schedules.find().forEach((schedule) => { 31 | schedule_register(schedule); 32 | }); 33 | }); 34 | 35 | Meteor.methods({ 36 | scheduleNew: function () { 37 | return schedules.insert({ 38 | title: 'New Schedule', 39 | active: true, 40 | stage: null, 41 | when: {schedules: []}, 42 | settings: {} 43 | }); 44 | }, 45 | 46 | scheduleDel: function (scheduleid) { 47 | actions.remove({schedule: scheduleid}); 48 | schedules.remove(scheduleid); 49 | 50 | if (schedule_handles[scheduleid]) { 51 | schedule_handles[scheduleid].clear(); 52 | delete schedule_handles[scheduleid]; 53 | } 54 | }, 55 | 56 | scheduleTitle: function (scheduleid, title) { 57 | schedules.update(scheduleid, {$set: {title: title}}); 58 | }, 59 | 60 | scheduleStage: function (scheduleid, stageid) { 61 | schedules.update(scheduleid, {$set: {stage: stageid}}); 62 | }, 63 | 64 | scheduleActive: function (scheduleid, active) { 65 | schedules.update(scheduleid, {$set: {active: active}}); 66 | 67 | var schedule = schedules.findOne(scheduleid); 68 | schedule_register(schedule); 69 | }, 70 | 71 | scheduleSet: function (scheduleid, when) { 72 | schedules.update(scheduleid, {$set: {when: when}}); 73 | 74 | var schedule = schedules.findOne(scheduleid); 75 | schedule_register(schedule); 76 | }, 77 | 78 | scheduleAddAction: function (action) { 79 | actions.insert(action); 80 | }, 81 | 82 | scheduleDelAction: function (actionid) { 83 | actions.remove(actionid); 84 | } 85 | }); 86 | -------------------------------------------------------------------------------- /lighting/server/consoles.js: -------------------------------------------------------------------------------- 1 | function checkConsole (consoleid) { 2 | var console = lightconsoles.findOne(consoleid); 3 | if (console) return console; 4 | else throw new Meteor.Error('console-not-found', `Couldn't find console with id: ${consoleid}`); 5 | } 6 | 7 | function checkPanel (panelid) { 8 | var panel = lightconsolepanels.findOne(panelid); 9 | if (panel) return panel; 10 | else throw new Meteor.Error('panel-not-found', `Couldn't find panel with id: ${panelid}`); 11 | } 12 | 13 | Meteor.methods({ 14 | lightConsoleNew: function () { 15 | return lightconsoles.insert({ 16 | title: 'New Console', 17 | stage: null, 18 | settings: {fade: 0}, 19 | }); 20 | }, 21 | 22 | lightConsoleDel: function (consoleid) { 23 | var console = checkConsole(consoleid); 24 | lightconsoles.remove(consoleid); 25 | lightconsolepanels.remove({console: consoleid}); 26 | }, 27 | 28 | lightConsoleTitle: function (consoleid, title) { 29 | var console = checkConsole(consoleid); 30 | lightconsoles.update(console, {$set: {title: title}}); 31 | }, 32 | 33 | lightConsoleStage: function (consoleid, stage) { 34 | var console = checkConsole(consoleid); 35 | lightconsoles.update(console, {$set: {stage: stage}}); 36 | }, 37 | 38 | lightConsoleSetting: function (consoleid, setting, value) { 39 | var console = checkConsole(consoleid); 40 | var s = {}; s['settings.' + setting] = value; 41 | lightconsoles.update(console, {$set: s}); 42 | }, 43 | 44 | lightConsoleAddPanel: function (consoleid) { 45 | var console = checkConsole(consoleid); 46 | var o = lightconsolepanels.find({console: consoleid}).count(); 47 | return lightconsolepanels.insert({ 48 | console: consoleid, 49 | title: 'New Panel', 50 | order: o, 51 | settings: {}, 52 | controls: [] 53 | }); 54 | }, 55 | 56 | lightConsoleDelPanel: function (panelid) { 57 | var panel = checkPanel(panelid); 58 | lightconsolepanels.remove(panelid); 59 | lightconsolepanels.update({console: panelid, order: {$gt: panel.order}}, {$inc: {order: -1}}, {multi: true}); 60 | }, 61 | 62 | lightConsolePanelTitle: function (panelid, title) { 63 | var panel = checkPanel(panelid); 64 | lightconsolepanels.update(panel, {$set: {title: title}}); 65 | }, 66 | 67 | lightConsolePanelControls: function (panelid, controls) { 68 | var panel = checkPanel(panelid); 69 | lightconsolepanels.update(panel, {$set: {controls: controls}}); 70 | }, 71 | }); 72 | -------------------------------------------------------------------------------- /lighting/client/consolepanel.js: -------------------------------------------------------------------------------- 1 | Template.lightConsolePanel.helpers({ 2 | isHidden: function () { 3 | if (Template.currentData().hasOwnProperty('_id')) var id = Template.currentData()._id; 4 | else var id = Template.parentData()._id; 5 | 6 | if (!Session.get(`panel-edit-${id}`)) return 'hidden'; 7 | }, 8 | 9 | getControls: function () { 10 | var out = []; 11 | this.controls.forEach((c, i) => { 12 | c.index = i; 13 | out.push(c); 14 | }); 15 | 16 | return out; 17 | }, 18 | 19 | getLight: function (lightid) { 20 | return lights.findOne(lightid); 21 | }, 22 | 23 | getGroup: function (groupid) { 24 | return lightgroups.findOne(groupid); 25 | }, 26 | 27 | getScene: function (sceneid) { 28 | return lightscenes.findOne(sceneid); 29 | } 30 | }); 31 | 32 | Template.lightConsolePanel.onCreated(function () { 33 | Session.set(`panel-edit-${this.data._id}`, false); 34 | }); 35 | 36 | Template.lightConsolePanel.events({ 37 | 'click .panel-edit-toggle': function (event, template) { 38 | $(event.target).toggleClass('active'); 39 | Session.set(`panel-edit-${template.data._id}`, !Session.get(`panel-edit-${template.data._id}`)); 40 | }, 41 | 42 | 'blur .panel-title': function (event, template) { 43 | Meteor.call('lightConsolePanelTitle', template.data._id, $(event.target).val()); 44 | }, 45 | 46 | 'click .add-control': function (event, template) { 47 | Session.set('add-to', template.data._id); 48 | }, 49 | 50 | 'click .control-down': function (event, template) { 51 | var controls = template.data.controls; 52 | if (this.index < controls.length-1) { 53 | controls.splice(this.index+1, 0, controls.splice(this.index, 1)[0]); 54 | Meteor.call('lightConsolePanelControls', template.data._id, controls); 55 | } 56 | }, 57 | 58 | 'click .control-up': function (event, template) { 59 | var controls = template.data.controls; 60 | if (this.index > 0) { 61 | controls.splice(this.index-1, 0, controls.splice(this.index, 1)[0]); 62 | Meteor.call('lightConsolePanelControls', template.data._id, controls); 63 | } 64 | }, 65 | 66 | 'click .control-del': function (event, template) { 67 | var controls = template.data.controls; 68 | controls.splice(this.index, 1); 69 | Meteor.call('lightConsolePanelControls', template.data._id, controls); 70 | }, 71 | 72 | 'click .panel-del': function (event, template) { 73 | Meteor.call('lightConsoleDelPanel', template.data._id); 74 | } 75 | }); 76 | -------------------------------------------------------------------------------- /media/client/templates/playlistsettings.html: -------------------------------------------------------------------------------- 1 | 70 | -------------------------------------------------------------------------------- /minions/client/templates/settingstimers.html: -------------------------------------------------------------------------------- 1 | 47 | -------------------------------------------------------------------------------- /lighting/client/groupsettings.js: -------------------------------------------------------------------------------- 1 | Template.lightGroupSettings.helpers({ 2 | stages: function () { 3 | return stages.find({_id: {$ne: this.stage}}); 4 | }, 5 | 6 | titleOf: function (stageid) { 7 | var stage = stages.findOne({_id: stageid}); 8 | if (stage) {return stage.title;} 9 | else {return 'Unassigned';} 10 | }, 11 | 12 | members: function () { 13 | var members = []; 14 | this.members.forEach(function (memberid) { 15 | members.push(lights.findOne(memberid)); 16 | }); 17 | return members; 18 | }, 19 | 20 | lightSelector: { 21 | collection: lights, 22 | displayTemplate: 'light', 23 | fields: [{field: 'title', type: String}, {field: 'stage', type: Stage}], 24 | sort: [['title', 1]], 25 | addbutton: true 26 | } 27 | }); 28 | 29 | Template.lightGroupSettings.events({ 30 | 'click .group-add': function (event, template) { 31 | template.$('.group-add-modal').modal('show'); 32 | }, 33 | 34 | 'click .group-add-close': function (event, template) { 35 | template.$('.group-add-modal').modal('hide'); 36 | }, 37 | 38 | 'click .collection-add': function (event, template) { 39 | var lightid = $(event.target).data('id'); 40 | Meteor.call('lightGroupAddLight', template.data._id, lightid); 41 | }, 42 | 43 | 'click .group-remove': function (event, template) { 44 | Meteor.call('lightGroupRemoveLight', template.data._id, this._id); 45 | }, 46 | 47 | 'click .group-settings': function (event, template) { 48 | template.$('.group-settings-modal').modal('show'); 49 | }, 50 | 51 | 'click .group-settings-cancel': function (event, template) { 52 | template.$('.group-settings-modal').modal('hide'); 53 | }, 54 | 55 | 'click .group-settings-delete': function (event, template) { 56 | template.$('.group-settings-modal').modal('hide'); 57 | template.$('.delete-confirm-modal').modal('show'); 58 | }, 59 | 60 | 'click .delete-cancel': function (event, template) { 61 | template.$('.delete-confirm-modal').modal('hide'); 62 | }, 63 | 64 | 'click .delete-confirm': function (event, template) { 65 | template.$('.delete-confirm-modal').modal('hide'); 66 | Meteor.call('lightGroupDel', this._id); 67 | Router.go('lightGroups'); 68 | }, 69 | 70 | 'click .group-settings-save': function (event, template) { 71 | template.$('.group-settings-modal').modal('hide'); 72 | var title = template.$('.group-title').val(); 73 | if (title) Meteor.call('lightGroupTitle', this._id, title); 74 | 75 | var stage = template.$('.group-stage').val(); 76 | if (stage) Meteor.call('lightGroupStage', this._id, stage); 77 | } 78 | }); 79 | -------------------------------------------------------------------------------- /minions/client/templates/stagesettings.html: -------------------------------------------------------------------------------- 1 | 66 | 67 | -------------------------------------------------------------------------------- /lighting/client/templates/settings.html: -------------------------------------------------------------------------------- 1 | 71 | -------------------------------------------------------------------------------- /minions/client/settings.js: -------------------------------------------------------------------------------- 1 | Template.minionsettings.helpers({ 2 | stages: function () { 3 | return stages.find({_id: {$ne: this.stage}}); 4 | }, 5 | 6 | titleOf: function (stageid) { 7 | var stage = stages.findOne({_id: stageid}); 8 | if (stage) {return stage.title;} 9 | else {return 'Unassigned';} 10 | }, 11 | 12 | getSetting: function (setting) { 13 | return combineSettings(this.settings)[setting]; 14 | }, 15 | 16 | isSelected: function (setting, value) { 17 | if (combineSettings(this.settings)[setting] == value) return 'selected'; 18 | }, 19 | 20 | typeIs: function (type) { 21 | return type == this.type; 22 | }, 23 | 24 | getLayers: function () { 25 | var stage = stages.findOne({_id: this.stage}); 26 | return stage.settings.layers; 27 | }, 28 | 29 | isLayerChecked: function () { 30 | if (Template.parentData().layers.indexOf(this.toString()) > -1) return 'true'; 31 | } 32 | }); 33 | 34 | Template.minionsettings.events({ 35 | 'blur .minion-title': function (event, template) { 36 | Meteor.call('minionTitle', this._id, $(event.target).val()); 37 | }, 38 | 39 | 'change .minion-stage': function (event, template) { 40 | Meteor.call('minionStage', this._id, $(event.target).val()); 41 | }, 42 | 43 | 'click .display-layer-checkbox': function (event, template) { 44 | if (event.target.checked) { 45 | Meteor.call('minionAddLayer', template.data._id, this.toString()); 46 | } else { 47 | Meteor.call('minionDelLayer', template.data._id, this.toString()); 48 | } 49 | }, 50 | 51 | 'change .setting': function (event, template) { 52 | var setting = $(event.target).data('setting'); 53 | var value = $(event.target).val(); 54 | Meteor.call('minionSetting', template.data._id, setting, value); 55 | }, 56 | 57 | 'changeColor .setting': function (event, template) { 58 | var setting = $(event.target).data('setting'); 59 | 60 | var c = event.color.toRGB(); 61 | if ($(event.target).data('colorpicker').options.format == 'rgba') { 62 | var value = [c.r / 255.0, c.g / 255.0, c.b / 255.0, c.a]; 63 | } else { 64 | var value = [c.r / 255.0, c.g / 255.0, c.b / 255.0]; 65 | } 66 | 67 | Meteor.call('minionSetting', template.data._id, setting, value); 68 | }, 69 | 70 | 'click .minion-settings-delete': function (event) { 71 | $('.delete-modal').modal('show'); 72 | }, 73 | 74 | 'click .minion-delete-cancel': function (event) { 75 | $('.delete-modal').modal('hide'); 76 | }, 77 | 78 | 'click .minion-delete-confirm': function (event) { 79 | $('.delete-modal').removeClass('fade').modal('hide'); 80 | Meteor.call('minionDelete', this._id); 81 | Router.go('/minions'); 82 | }, 83 | }); 84 | -------------------------------------------------------------------------------- /presentations/client/settings.js: -------------------------------------------------------------------------------- 1 | Template.presentationSettings.helpers({ 2 | imageSelector: { 3 | collection: media, 4 | displayTemplate: 'mediaItem', 5 | fields: [{field: 'title', type: String}, {field: 'tags', type: Array}, {field: 'type', type: String, fixed: 'image'}], 6 | sort: [['title', 'asc']], 7 | addbutton: true 8 | }, 9 | 10 | notImported: function () { 11 | return !this.imported; 12 | }, 13 | 14 | importstatusIs: function () { 15 | return Array.from(arguments).indexOf(this.importstatus) != -1; 16 | }, 17 | 18 | slides: function () { 19 | return presentationslides.find({presentation: this._id}, {sort: {order: 1}}); 20 | }, 21 | 22 | isSelected: function (setting, value) { 23 | if (this.settings[setting] == value) return 'selected'; 24 | }, 25 | 26 | getSetting: function (setting) { 27 | return combineSettings(this.settings)[setting]; 28 | } 29 | }); 30 | 31 | Template.presentationSettings.events({ 32 | 'click #pres-settings-toggle': function (event, template) { 33 | template.$('#pres-settings-collapse').collapse('toggle'); 34 | }, 35 | 36 | 'blur #pres-title': function (event, template) { 37 | Meteor.call('presentationTitle', template.data._id, $(event.target).val()); 38 | }, 39 | 40 | 'click #pres-del': function (event, template) { 41 | Meteor.call('presentationDel', template.data._id); 42 | Router.go('/presentations'); 43 | }, 44 | 45 | 'click #add-slide': function (event, template) { 46 | Meteor.call('presentationAddSlide', template.data._id); 47 | }, 48 | 49 | 'click .collection-add': function (event, template) { 50 | Meteor.call('presentationSlideImageAdd', Session.get('add-to'), $(event.target).data('id')); 51 | template.$('.image-modal').modal('hide'); 52 | }, 53 | 54 | 'change .setting': function (event, template) { 55 | var setting = $(event.target).data('setting'); 56 | var value = $(event.target).val(); 57 | if (value == '') value = null; 58 | Meteor.call('presentationSetting', template.data._id, setting, value); 59 | }, 60 | 61 | 'changeColor .setting': function (event, template) { 62 | var setting = $(event.target).data('setting'); 63 | 64 | var c = event.color.toRGB(); 65 | if ($(event.target).data('colorpicker').options.format == 'rgba') { 66 | var value = [c.r / 255.0, c.g / 255.0, c.b / 255.0, c.a]; 67 | } else { 68 | var value = [c.r / 255.0, c.g / 255.0, c.b / 255.0]; 69 | } 70 | 71 | Meteor.call('presentationSetting', template.data._id, setting, value); 72 | }, 73 | 74 | 'click .setting-reset': function (event, template) { 75 | var setting = $(event.target).data('setting'); 76 | Meteor.call('presentationSetting', template.data._id, setting, null); 77 | } 78 | }); 79 | -------------------------------------------------------------------------------- /minions/server/minions.js: -------------------------------------------------------------------------------- 1 | function checkMinion (minionid) { 2 | var minion = minions.findOne(minionid); 3 | if (!minion) { 4 | throw new Meteor.Error('minion-not-found', "Can't find a minion with _id " + minionid); 5 | } 6 | 7 | return minion; 8 | } 9 | 10 | Meteor.methods({ 11 | minionNew: function (type) { 12 | if (['media', 'lighting'].indexOf(type) < 0) { 13 | throw new Meteor.Error('minion-invalid-type', 'Invalid type of minion: ' + type); 14 | } 15 | 16 | var minion = { 17 | title: 'New ' + type + ' minion', 18 | stage: null, 19 | type: type, 20 | settings: {}, 21 | connected: false 22 | }; 23 | 24 | if (type == 'media') { 25 | minion.layers = ['audio', 'background', 'foreground']; 26 | minion.settings.blocks = [{ 27 | points: [[-1, -1], [1, -1], [1, 1], [-1, 1]], 28 | width: 1, 29 | height: 1, 30 | x: 0, y: 0, 31 | brightness: 1, 32 | blend_top: 0, blend_bottom: 0, 33 | blend_left: 0, blend_right: 0, 34 | alpha_mask: false 35 | }]; 36 | } 37 | 38 | return minions.insert(minion); 39 | }, 40 | 41 | minionConnect: function (minionid) { 42 | var minion = checkMinion(minionid); 43 | minions.update(minion, {$set: {connected: true}}); 44 | this.connection.onClose(function () { 45 | minions.update(minion._id, {$set: {connected: false}}); 46 | }); 47 | }, 48 | 49 | minionDelete: function (minionid) { 50 | var minion = checkMinion(minionid); 51 | minions.remove(minion); 52 | }, 53 | 54 | minionTitle: function (minionid, title) { 55 | var minion = checkMinion(minionid); 56 | minions.update(minion, {$set: {title: title}}); 57 | }, 58 | 59 | minionStage: function (minionid, stageid) { 60 | var minion = checkMinion(minionid); 61 | var stage = stages.findOne(stageid); 62 | if (stage) { 63 | minions.update(minion, {$set: {stage: stageid}}); 64 | }; 65 | // Silently fail on invalid stage id, because of how I coded the