mirror of
https://github.com/Spengreb/sync.git
synced 2026-06-10 07:12:05 +00:00
Add calendar for displaying scheduled shows per channel
This commit is contained in:
parent
c977cbd754
commit
60c6a50d9e
8 changed files with 134 additions and 2 deletions
|
|
@ -26,6 +26,8 @@ function parseShowRow(row) {
|
|||
channel_name: row.channel_name,
|
||||
channel_id: row.channel_id,
|
||||
name: row.name,
|
||||
notes: row.notes || null,
|
||||
color: row.color || null,
|
||||
playlist,
|
||||
timezone: row.timezone,
|
||||
scheduled_for: row.scheduled_for,
|
||||
|
|
@ -49,6 +51,8 @@ function parseShowRow(row) {
|
|||
function serializeShowInput(input) {
|
||||
return {
|
||||
name: input.name,
|
||||
notes: input.notes || null,
|
||||
color: input.color || null,
|
||||
playlist: JSON.stringify(input.playlist || []),
|
||||
timezone: input.timezone,
|
||||
scheduled_for: input.scheduled_for,
|
||||
|
|
|
|||
|
|
@ -184,6 +184,8 @@ export async function initTables() {
|
|||
.references('id').inTable('channels')
|
||||
.onDelete('cascade');
|
||||
t.string('name', 100).notNullable();
|
||||
t.specificType('notes', 'mediumtext character set utf8mb4');
|
||||
t.string('color', 7).nullable();
|
||||
t.specificType('playlist', 'mediumtext character set utf8mb4 not null');
|
||||
t.string('timezone', 64).notNullable().defaultTo('UTC');
|
||||
t.bigInteger('scheduled_for').notNullable();
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import Promise from 'bluebird';
|
|||
|
||||
const LOGGER = require('@calzoneman/jsli')('database/update');
|
||||
|
||||
const DB_VERSION = 12;
|
||||
const DB_VERSION = 13;
|
||||
var hasUpdates = [];
|
||||
|
||||
module.exports.checkVersion = function () {
|
||||
|
|
@ -53,6 +53,8 @@ function update(version, cb) {
|
|||
addChannelOwnerLastSeenColumn(cb);
|
||||
} else if (version < 12) {
|
||||
addUserInactiveColumn(cb);
|
||||
} else if (version < 13) {
|
||||
addShowsNotesAndColorColumns(cb);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -141,3 +143,29 @@ function addUserInactiveColumn(cb) {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addShowsNotesAndColorColumns(cb) {
|
||||
db.query(
|
||||
"ALTER TABLE channel_shows ADD COLUMN notes MEDIUMTEXT CHARACTER SET utf8mb4 NULL",
|
||||
error => {
|
||||
if (error) {
|
||||
LOGGER.error(`Failed to add shows notes column: ${error}`);
|
||||
cb(error);
|
||||
return;
|
||||
}
|
||||
|
||||
db.query(
|
||||
"ALTER TABLE channel_shows ADD COLUMN color VARCHAR(7) NULL",
|
||||
error => {
|
||||
if (error) {
|
||||
LOGGER.error(`Failed to add shows color column: ${error}`);
|
||||
cb(error);
|
||||
return;
|
||||
}
|
||||
|
||||
cb();
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ const showDB = require('../../../database/shows');
|
|||
const shows = require('../../../shows');
|
||||
const botDB = require('../../../database/bots');
|
||||
const infoGetter = require('../../../get-info');
|
||||
const XSS = require('../../../xss');
|
||||
const { getChannelRow, getUserEffectiveRank, hashToken } = require('./middleware');
|
||||
|
||||
const router = express.Router({ mergeParams: true });
|
||||
|
|
@ -101,9 +102,27 @@ function validateShowPayload(body, old = null) {
|
|||
|
||||
const nextRunAt = status === 'scheduled' ? scheduledFor : (old ? old.next_run_at : scheduledFor);
|
||||
|
||||
const notesRaw = body.notes !== undefined ? body.notes : (old ? old.notes : null);
|
||||
let notes = null;
|
||||
if (typeof notesRaw === 'string' && notesRaw.trim() !== '') {
|
||||
notes = XSS.sanitizeHTML(notesRaw.substring(0, 20000));
|
||||
}
|
||||
|
||||
const colorRaw = body.color !== undefined ? body.color : (old ? old.color : null);
|
||||
let color = null;
|
||||
if (colorRaw !== null && colorRaw !== undefined && String(colorRaw).trim() !== '') {
|
||||
const normalized = String(colorRaw).trim();
|
||||
if (!/^#[0-9a-fA-F]{6}$/.test(normalized)) {
|
||||
return { error: 'color must be a hex string like #1A2B3C' };
|
||||
}
|
||||
color = normalized.toUpperCase();
|
||||
}
|
||||
|
||||
return {
|
||||
value: {
|
||||
name,
|
||||
notes,
|
||||
color,
|
||||
playlist,
|
||||
timezone,
|
||||
scheduled_for: scheduledFor,
|
||||
|
|
|
|||
|
|
@ -259,6 +259,25 @@ html(lang="en")
|
|||
+permeditor()
|
||||
.modal-footer
|
||||
button.btn.btn-default(type="button", data-dismiss="modal") Close
|
||||
#showdetails.modal.fade(tabindex="-1", role="dialog", aria-hidden="true")
|
||||
.modal-dialog
|
||||
.modal-content
|
||||
.modal-header
|
||||
button.close(data-dismiss="modal", aria-hidden="true") ×
|
||||
h4#showdetails-title Show Details
|
||||
.modal-body
|
||||
p
|
||||
strong Time:
|
||||
|
|
||||
span#showdetails-time
|
||||
p
|
||||
strong Status:
|
||||
|
|
||||
span#showdetails-status
|
||||
hr
|
||||
#showdetails-notes
|
||||
.modal-footer
|
||||
button.btn.btn-default(type="button", data-dismiss="modal") Close
|
||||
#pmbar
|
||||
include footer
|
||||
+footer()
|
||||
|
|
|
|||
|
|
@ -267,6 +267,12 @@ mixin shows
|
|||
label.control-label.col-sm-3(for="cs-shows-scheduled-for") Scheduled For
|
||||
.col-sm-9
|
||||
input#cs-shows-scheduled-for.form-control(type="datetime-local")
|
||||
.form-group
|
||||
label.control-label.col-sm-3(for="cs-shows-color") Color
|
||||
.col-sm-3
|
||||
input#cs-shows-color.form-control(type="color", value="#337AB7")
|
||||
.col-sm-6
|
||||
input#cs-shows-color-hex.form-control(type="text", placeholder="#337AB7", maxlength="7")
|
||||
.form-group
|
||||
label.control-label.col-sm-3(for="cs-shows-timezone") Timezone
|
||||
.col-sm-9
|
||||
|
|
@ -296,6 +302,11 @@ mixin shows
|
|||
label(for="cs-shows-start-playback")
|
||||
input#cs-shows-start-playback(type="checkbox")
|
||||
| Skip current playback and start this show's first item immediately
|
||||
.form-group
|
||||
label.control-label.col-sm-3(for="cs-shows-notes") Notes
|
||||
.col-sm-9
|
||||
textarea#cs-shows-notes.form-control(rows="6", placeholder="Optional rich notes (same HTML support as MOTD)")
|
||||
p.text-muted.small(style="margin-top:6px") Supports MOTD-style HTML. Optional.
|
||||
.form-group
|
||||
label.control-label.col-sm-3(for="cs-shows-mediaurl") Show Playlist
|
||||
.col-sm-9
|
||||
|
|
|
|||
|
|
@ -254,6 +254,7 @@ li.ui-sortable-helper, li.ui-sortable-placeholder + li.queue_entry {
|
|||
padding: 2px 4px;
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
|
@ -262,6 +263,11 @@ li.ui-sortable-helper, li.ui-sortable-placeholder + li.queue_entry {
|
|||
.showschedule-show.status-paused { background: #f0ad4e; color: #222; }
|
||||
.showschedule-show.status-completed { background: #777; }
|
||||
|
||||
#showdetails-notes img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.clear {
|
||||
clear: both;
|
||||
}
|
||||
|
|
|
|||
45
www/js/ui.js
45
www/js/ui.js
|
|
@ -37,7 +37,11 @@ function updateScheduleToggleLabel() {
|
|||
$("#toggleschedule").on('click', function () {
|
||||
var row = $("#showschedule-row");
|
||||
if (!row.length) return;
|
||||
var willShow = !row.is(":visible");
|
||||
row.toggle();
|
||||
if (willShow && window.CSTShows && typeof window.CSTShows.load === "function") {
|
||||
window.CSTShows.load();
|
||||
}
|
||||
updateScheduleToggleLabel();
|
||||
});
|
||||
updateScheduleToggleLabel();
|
||||
|
|
@ -1624,11 +1628,21 @@ var CSTShows = (function () {
|
|||
function readFormPayload() {
|
||||
var scheduledRaw = $('#cs-shows-scheduled-for').val();
|
||||
var timezone = $('#cs-shows-timezone').val().trim();
|
||||
var notes = $('#cs-shows-notes').val();
|
||||
var colorHex = ($('#cs-shows-color-hex').val() || '').trim();
|
||||
if (!colorHex) {
|
||||
colorHex = ($('#cs-shows-color').val() || '').trim();
|
||||
}
|
||||
if (!/^#[0-9a-fA-F]{6}$/.test(colorHex || '')) {
|
||||
colorHex = '';
|
||||
}
|
||||
if (!timezone) {
|
||||
timezone = 'UTC';
|
||||
}
|
||||
return {
|
||||
name: $('#cs-shows-name').val().trim(),
|
||||
notes: notes && notes.trim() ? notes : null,
|
||||
color: colorHex ? colorHex.toUpperCase() : null,
|
||||
scheduled_for: scheduledRaw ? new Date(scheduledRaw).toISOString() : null,
|
||||
timezone: timezone,
|
||||
recurrence: $('#cs-shows-recurrence').val(),
|
||||
|
|
@ -1646,6 +1660,7 @@ var CSTShows = (function () {
|
|||
loadTimezoneOptions();
|
||||
selectedId = null;
|
||||
$('#cs-shows-name').val('');
|
||||
$('#cs-shows-notes').val('');
|
||||
$('#cs-shows-scheduled-for').val('');
|
||||
var detectedTz = 'UTC';
|
||||
if (typeof Intl !== 'undefined' && Intl.DateTimeFormat) {
|
||||
|
|
@ -1659,6 +1674,8 @@ var CSTShows = (function () {
|
|||
$('#cs-shows-fill-mode').val('append');
|
||||
$('#cs-shows-conflict-skip').prop('checked', false);
|
||||
$('#cs-shows-start-playback').prop('checked', false);
|
||||
$('#cs-shows-color').val('#337AB7');
|
||||
$('#cs-shows-color-hex').val('');
|
||||
$('#cs-shows-mediaurl').val('');
|
||||
draftPlaylist = [];
|
||||
renderDraftPlaylist();
|
||||
|
|
@ -1668,6 +1685,7 @@ var CSTShows = (function () {
|
|||
loadTimezoneOptions();
|
||||
selectedId = show.id;
|
||||
$('#cs-shows-name').val(show.name);
|
||||
$('#cs-shows-notes').val(show.notes || '');
|
||||
$('#cs-shows-scheduled-for').val(toLocalDateInput(show.scheduled_for));
|
||||
var showTz = show.timezone || 'UTC';
|
||||
if ($('#cs-shows-timezone option[value="' + showTz + '"]').length === 0) {
|
||||
|
|
@ -1678,6 +1696,8 @@ var CSTShows = (function () {
|
|||
$('#cs-shows-fill-mode').val(show.fill_mode || 'append');
|
||||
$('#cs-shows-conflict-skip').prop('checked', (show.conflict_mode || 'force') === 'skip');
|
||||
$('#cs-shows-start-playback').prop('checked', !!show.start_playback);
|
||||
$('#cs-shows-color').val(show.color || '#337AB7');
|
||||
$('#cs-shows-color-hex').val(show.color || '');
|
||||
draftPlaylist = (show.playlist || []).map(function (item) {
|
||||
return {
|
||||
id: item.id,
|
||||
|
|
@ -1729,6 +1749,19 @@ var CSTShows = (function () {
|
|||
weekStart.toLocaleDateString() + ' - ' + weekEnd.toLocaleDateString()
|
||||
);
|
||||
|
||||
function openShowDetailsModal(show, when) {
|
||||
$('#showdetails-title').text(show.name || 'Show Details');
|
||||
$('#showdetails-time').text(when.toLocaleString());
|
||||
$('#showdetails-status').text(show.status || 'scheduled');
|
||||
var notes = (show.notes || '').trim();
|
||||
if (!notes) {
|
||||
$('#showdetails-notes').html('<p class="text-muted">No notes for this show.</p>');
|
||||
} else {
|
||||
$('#showdetails-notes').html(notes);
|
||||
}
|
||||
$('#showdetails').modal();
|
||||
}
|
||||
|
||||
var byCell = {};
|
||||
shows.forEach(function (show) {
|
||||
var at = show.next_run_at || show.scheduled_for;
|
||||
|
|
@ -1778,12 +1811,13 @@ var CSTShows = (function () {
|
|||
$('<a href="javascript:void(0)" class="showschedule-show">')
|
||||
.addClass('status-' + (item.show.status || 'scheduled'))
|
||||
.text(label)
|
||||
.css('background', item.show.color || '')
|
||||
.on('click', function () {
|
||||
if (isAdmin) {
|
||||
openShowsEditor();
|
||||
selectShow(item.show);
|
||||
} else {
|
||||
alert(item.show.name + '\n' + item.date.toLocaleString() + '\nStatus: ' + item.show.status);
|
||||
openShowDetailsModal(item.show, item.date);
|
||||
}
|
||||
})
|
||||
.appendTo(cell);
|
||||
|
|
@ -1917,6 +1951,15 @@ var CSTShows = (function () {
|
|||
}
|
||||
});
|
||||
$('#cs-shows-clear').on('click', clearForm);
|
||||
$('#cs-shows-color').on('change', function () {
|
||||
$('#cs-shows-color-hex').val(($(this).val() || '').toUpperCase());
|
||||
});
|
||||
$('#cs-shows-color-hex').on('input', function () {
|
||||
var v = ($(this).val() || '').trim();
|
||||
if (/^#[0-9a-fA-F]{6}$/.test(v)) {
|
||||
$('#cs-shows-color').val(v);
|
||||
}
|
||||
});
|
||||
$('#cs-shows-playlist-list').sortable({
|
||||
update: function () {
|
||||
var nextDraft = [];
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue