├── 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 |
2 | {{title}}
3 |
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 |
2 | {{title}}
3 |
4 |
--------------------------------------------------------------------------------
/public/YoungCedarTree-128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cedarproject/cedarserver/HEAD/public/YoungCedarTree-128x128.png
--------------------------------------------------------------------------------
/sets/client/templates/settime.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/sets/client/templates/specialClearLayer.html:
--------------------------------------------------------------------------------
1 |
2 | Clear Layer {{layer}}
3 |
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 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/sets/client/templates/specialCustom.html:
--------------------------------------------------------------------------------
1 |
2 | Custom
3 |
4 |
--------------------------------------------------------------------------------
/schedule/client/templates/display.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/minions/client/templates/menu.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{> stagesList}}
4 |
5 |
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 |
2 | Camera {{getCamera}}
3 |
4 |
--------------------------------------------------------------------------------
/sets/client/templates/specialClearChannel.html:
--------------------------------------------------------------------------------
1 |
2 | Clear Channel {{settings.sequence_channel}}
3 |
4 |
--------------------------------------------------------------------------------
/songs/client/templates/song.html:
--------------------------------------------------------------------------------
1 |
2 |
5 |
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 |
2 |
3 |
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 |
2 |
3 |
4 | {{title}}
5 |
6 |
7 |
--------------------------------------------------------------------------------
/sequences/client/templates/sequence.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{title}}
5 |
6 |
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 |
2 |
3 |
Music Stand
4 |
5 | {{> collectionSelector setsSelector}}
6 |
7 |
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 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/lighting/client/templates/light.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{title}}
4 |
5 | {{#if disabled}}
6 | Disabled
7 | {{/if}}
8 |
9 |
10 |
--------------------------------------------------------------------------------
/media/client/templates/items.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{> upload_bootstrap multiple=true formData=mediaFormData}}
4 |
5 | {{> collectionSelector mediaSelector}}
6 |
7 |
8 |
--------------------------------------------------------------------------------
/presentations/client/templates/slideselector.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{#with getPres}}
4 | {{> presentationDisplay}}
5 | {{/with}}
6 |
7 |
8 |
--------------------------------------------------------------------------------
/minions/client/templates/minions.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{#each minions}}
4 | -
5 | {{> minion}}
6 |
7 | {{/each}}
8 |
9 |
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 |
2 |
8 |
9 |
--------------------------------------------------------------------------------
/media/client/templates/mediaMenu.html:
--------------------------------------------------------------------------------
1 |
2 |
8 |
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 |
2 |
3 |
6 |
7 | {{title}}
8 |
9 |
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 |
2 |
8 |
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 |
2 |
3 |
Sets
4 |
5 | {{> collectionSelector setsSelector}}
6 |
7 |
10 |
11 |
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 |
2 |
3 |
4 |

5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/media/client/templates/playlists.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{> collectionSelector mediaSelector}}
4 |
5 |
6 |
7 |
11 |
12 |
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 |
2 |
9 |
10 |
--------------------------------------------------------------------------------
/schedule/client/templates/schedules.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Schedules
4 |
5 | {{> collectionSelector schedulesSelector}}
6 |
7 |
11 |
12 |
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 |
2 |
3 |
Sequences
4 |
5 | {{> collectionSelector sequencesSelector}}
6 |
7 |
11 |
12 |
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 |
2 |
3 |
4 |
5 |
6 |
7 | {{> actionDisplay}}
8 |
9 |
10 |
--------------------------------------------------------------------------------
/lighting/client/templates/valueselector.html:
--------------------------------------------------------------------------------
1 |
2 | {{#each channels}}
3 |
4 |
5 |
6 | {{/each}}
7 |
8 | {{#if hasColor}}
9 |
10 | {{/if}}
11 |
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 |
2 | {{title}}
3 |
4 | {{#if imported}}
5 | {{#each slides}}
6 | {{> presentationDisplaySlideImported}}
7 | {{/each}}
8 | {{else}}
9 | {{#each slides}}
10 | {{> presentationDisplaySlide}}
11 | {{/each}}
12 | {{/if}}
13 |
14 |
15 |
--------------------------------------------------------------------------------
/sets/client/templates/specialTimer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Timer -
5 | {{#if typeIs 'countdown'}}
6 | Countdown {{getTime}}
7 | {{/if}}
8 |
9 | {{#if typeIs 'startAt'}}
10 | Start At {{getTime}}
11 | {{/if}}
12 |
13 | {{#if typeIs 'endAt'}}
14 | End At {{getTime}}
15 | {{/if}}
16 |
17 |
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 |
2 |
3 |
Songs
4 |
5 | {{> collectionSelector songsSelector}}
6 |
7 |
11 |
12 | Import Songs
13 | {{> upload_bootstrap multiple=true fileTypes='.cedarsong' formData=songFormData}}
14 |
15 |
16 |
--------------------------------------------------------------------------------
/lighting/client/templates/groups.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Light Groups
4 |
5 | {{> collectionSelector groupSelector}}
6 |
7 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/main/client/templates/menu.html:
--------------------------------------------------------------------------------
1 |
2 |
13 |
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 |
2 |
3 |
Consoles
4 |
5 | {{> collectionSelector consoleSelector}}
6 |
7 |
11 |
12 |
13 |
14 |
15 |
18 |
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 |
2 |
3 | {{#if imported}}
4 | {{#if importstatusIs 'processing_1' 'processing_2'}}
5 | processing
6 | {{/if}}
7 | {{#if importstatusIs 'error'}}
8 | import error
9 | {{/if}}
10 | {{/if}}
11 |
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 |
2 |
7 |
8 |
11 |
12 |
13 | {{#each tags}}
14 |
15 | {{this}}
16 |
17 | {{/each}}
18 |
19 |
20 |
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 |
2 |
3 |
Presentations
4 |
5 | {{> collectionSelector presentationSelector}}
6 |
7 |
11 |
12 | Import Presentations
13 | {{> upload_bootstrap multiple=true fileTypes='.odp,.ppt,.pptx,.pps,pot,.kth,.key,.pcd,.sda,.sdd,.sdp,.vor' formData=presentationFormData}}
14 |
15 |
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 |
2 | {{title}}
3 |
4 | {{#each content}}
5 |
6 |
7 |
{{title}}
8 |
9 |
12 |
13 |
14 | {{/each}}
15 |
16 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{title}}
9 |
10 |
11 | {{#if connected}}
12 | Connected
13 | {{else}}
14 | Disconnected
15 | {{/if}}
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/lighting/client/templates/scenes.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Scenes
4 |
5 | {{> collectionSelector sceneSelector}}
6 |
7 |
11 |
12 |
13 |
14 |
15 |
18 |
19 |
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 |
2 |
3 |
Lights
4 |
5 | {{> collectionSelector lightSelector}}
6 |
7 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
21 | {{> light}}
22 |
23 |
--------------------------------------------------------------------------------
/presentations/client/templates/importedslide.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
10 |
13 |
14 |
15 |

16 |
17 |
18 |
--------------------------------------------------------------------------------
/songs/client/templates/section.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{title}}
4 |
5 |
6 |
7 | {{#each contents}}
8 |
9 |
10 |
13 |
14 | {{! TODO triggers}}
15 | {{/each}}
16 |
17 |
20 |
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 |
2 | {{#each stages}}
3 |
4 | {{title}}
5 |
6 |
7 |
8 |
9 |
10 | {{> minionsList}}
11 |
12 | {{/each}}
13 |
14 | Unassigned
15 | {{> minionsList}}
16 |
17 |
20 |
23 |
24 |
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 |
2 |
20 |
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 |
2 |
3 |
{{title}}
4 |
7 |
8 | {{#each actions}}
9 | {{#if typeIs 'song'}}
10 |
11 | {{#with getSong}}
12 | {{> musicstandchart}}
13 | {{/with}}
14 |
15 | {{else}}
16 |
17 | {{> actionDisplay}}
18 |
19 | {{/if}}
20 | {{/each}}
21 |
22 |
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 |
2 |
3 |
6 |
7 | {{#if set}}
8 |
11 |
14 | {{/if}}
15 |
16 |
17 | {{#if title}}
18 | {{title}}
19 | {{/if}}
20 |
21 |
22 | {{> actionSettings}}
23 |
24 |
25 | {{> actionDisplay}}
26 |
27 |
--------------------------------------------------------------------------------
/media/client/templates/item.html:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
16 | {{type}}
17 |
18 | {{#if typeIs 'video' 'audio'}}
19 | -
20 | {{getLength}}
21 | {{/if}}
22 |
23 | {{#each tags}}
24 |
25 | {{this}}
26 |
27 | {{/each}}
28 |
29 |
30 |
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 |
2 |
3 |
14 |
{{title}}
15 |
{{order}}
16 |
17 |
18 | {{#each sections}}
19 |
20 |
{{title}}
21 | {{#each contents}}
22 |
{{{getText}}}
23 | {{/each}}
24 |
25 | {{/each}}
26 |
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 |
2 |
3 |
4 |
5 |
6 | {{#each images}}
7 | {{#with getMedia}}
8 |

9 | {{/with}}
10 | {{/each}}
11 |
12 | {{#if fillins}}
13 |
22 | {{/if}}
23 |
24 |
25 |
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 |
2 |
5 |
6 |
42 |
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 |
2 |
3 | {{title}}
4 |
5 |
6 |
7 |
27 |
28 |
29 |
33 |
34 |
39 |
40 |
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 |
2 |
3 | -
4 |
7 |
8 |
Clear Layer
9 |
10 |
11 | -
12 |
15 |
16 |
Clear Channel
17 |
18 |
19 | -
20 |
23 |
24 |
Timer
25 |
26 |
27 | -
28 |
31 |
32 |
Camera
33 |
34 |
35 | -
36 |
39 |
40 |
Custom
41 |
42 |
43 |
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 |
2 | {{#if actionType 'media'}}
3 | {{#with getMedia}}
4 |
5 | {{> mediaItem}}
6 |
7 | {{/with}}
8 | {{/if}}
9 |
10 | {{#if actionType 'playlist'}}
11 | {{#with getPlaylist}}
12 |
13 | {{> mediaPlaylist}}
14 |
15 | {{/with}}
16 | {{/if}}
17 |
18 | {{#if actionType 'light'}}
19 | {{#with getLight}}
20 | {{> light}}
21 | {{/with}}
22 | {{/if}}
23 |
24 | {{#if actionType 'lightgroup'}}
25 | {{#with getLightGroup}}
26 | {{> lightGroup}}
27 | {{/with}}
28 | {{/if}}
29 |
30 | {{#if actionType 'lightscene'}}
31 | {{#with getLightScene}}
32 | {{> lightScene}}
33 | {{/with}}
34 | {{/if}}
35 |
36 | {{#if actionType 'song'}}
37 | {{#with getSong}}
38 | {{> songDisplay}}
39 | {{/with}}
40 | {{/if}}
41 |
42 | {{#if actionType 'presentation'}}
43 | {{#with getPresentation}}
44 | {{> presentationDisplay}}
45 | {{/with}}
46 | {{/if}}
47 |
48 | {{#if actionType 'presentationslide'}}
49 | {{defaulttitle}}
50 | {{/if}}
51 |
52 | {{#if actionType 'sequence'}}
53 | {{#with getSequence}}
54 | {{> sequence}}
55 | {{/with}}
56 | {{/if}}
57 |
58 | {{#if actionType 'clear-layer'}}
59 | {{> specialClearLayer}}
60 | {{/if}}
61 |
62 | {{#if actionType 'clear-channel'}}
63 | {{> specialClearChannel}}
64 | {{/if}}
65 |
66 | {{#if actionType 'timer'}}
67 | {{> specialTimer}}
68 | {{/if}}
69 |
70 | {{#if actionType 'camera'}}
71 | {{> specialCamera}}
72 | {{/if}}
73 |
74 | {{#if actionType 'custom'}}
75 | {{> specialCustom}}
76 | {{/if}}
77 |
78 |
--------------------------------------------------------------------------------
/media/client/templates/mediasettings.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Media Settings
4 |
5 | {{#if typeIs 'video'}}
6 |
7 | {{/if}}
8 |
9 | {{#if typeIs 'audio'}}
10 |
11 | {{/if}}
12 |
13 | {{#if typeIs 'image'}}
14 |

15 | {{/if}}
16 |
17 |
18 |
19 |
20 |
21 |
22 |
Filename: {{location}}
23 |
24 | {{#if typeIs 'audio' 'video'}}
25 |
Length: {{getLength}}
26 | {{/if}}
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
37 |
38 |
39 |
40 |
Separate individual tags with commas.
41 |
42 |
43 | {{#each tags}}
44 |
45 | {{this}}
46 |
47 |
48 | {{/each}}
49 |
50 |
51 |
52 |
53 |
56 |
57 | Close
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/minions/client/templates/settingsdisplay.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
9 |
10 |
11 | Blocks: {{numBlocks}}
12 |
13 |
14 | {{#each settings.blocks}}
15 | -
16 |
17 | {{#each blockGroups}}
18 |
19 |
{{heading}}
20 |
35 |
36 | {{/each}}
37 |
38 |
39 | {{/each}}
40 |
41 |
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 |
2 |
3 |
6 |
7 |
8 |
13 |
14 | {{#if typeIs 'daysofweek'}}
15 |
16 |
18 |
20 |
22 |
24 |
26 |
28 |
30 |
31 | {{/if}}
32 |
33 | {{#if typeIs 'daysofmonth'}}
34 |
35 | {{#each getMonthDays}}
36 |
38 | {{/each}}
39 |
40 | {{/if}}
41 |
42 | Time:
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/songs/client/templates/settings.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{! TODO change settings UI to a collapse like Sets}}
4 |
5 |
8 |
9 |
10 |
11 | Download
12 |
13 |
14 |
15 | {{title}}
16 |
17 |
18 |
19 |
20 |
21 |
22 |
27 |
28 |
Sections
29 |
30 |
31 | {{#each sections}}
32 | -
33 |
36 | {{> songSection}}
37 |
38 | {{/each}}
39 |
40 |
41 |
45 |
46 |
47 |
48 |
Arrangements
49 |
50 |
51 | {{#each arrangements}}
52 | -
53 |
56 | {{> songArrangement}}
57 |
58 | {{/each}}
59 |
60 |
61 |
65 |
66 |
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 |
2 |
3 | {{#each fields}}
4 | {{#if notFixed}}
5 | {{#if isStage}}
6 |
12 | {{else}}
13 |
14 | {{/if}}
15 | {{/if}}
16 | {{/each}}
17 |
18 |
37 |
38 |
39 |
60 |
61 |
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 |
2 |
3 |
4 |
7 |
8 | {{title}}
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
22 |
23 |
24 |
45 |
46 |
50 |
51 |
52 |
69 |
70 |
--------------------------------------------------------------------------------
/minions/client/templates/settingstimers.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
8 |
9 |
10 |
14 |
15 |
16 | {{> colorpicker classes='setting' value=(getSetting 'timers_font_color') data-setting="timers_font_color" format="rgb"}}
17 |
18 |
19 |
20 |
21 |
22 | {{> colorpicker classes='setting' value=(getSetting 'timers_font_outline_color') data-setting="timers_font_outline_color" format="rgb"}}
23 |
24 |
25 |
26 |
27 |
28 | {{> colorpicker classes='setting' value=(getSetting 'timers_font_warn_color') data-setting="timers_font_warn_color" format="rgb"}}
29 |
30 |
31 | {{> colorpicker classes='setting' value=(getSetting 'timers_font_negative_color') data-setting="timers_font_negative_color" format="rgb"}}
32 |
33 |
34 |
39 |
40 |
41 |
46 |
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 |
2 |
3 |
Stage Settings
4 |
5 |
6 |
7 |
8 |
9 |
27 |
28 |
29 |
30 |
31 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
Close
43 |
44 |
45 |
46 |
47 |
48 |
51 |
52 |
53 |
The stage "{{title}}" will be permanently deleted.
54 |
Any minions on this stage will be moved to Unassigned
55 |
56 |
57 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/lighting/client/templates/settings.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Light Settings
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
17 |
18 |
19 |
20 |
21 |
24 |
27 |
28 |
29 |
30 |
31 |
34 |
35 |
Channels
36 |
37 | {{#each channels}}
38 |
39 |
42 | {{> lightChannel}}
43 |
44 | {{/each}}
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
58 |
59 |
60 |
The light "{{title}}" will be permanently deleted.
61 |
62 |
63 |
67 |
68 |
69 |
70 |
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