fully working

This commit is contained in:
jr-k 2024-06-30 14:21:40 +02:00
parent 4526550376
commit d468c906dc
18 changed files with 196 additions and 52 deletions

File diff suppressed because one or more lines are too long

View File

@ -2,23 +2,19 @@ jQuery(document).ready(function ($) {
const $tableActive = $('table.active-slides'); const $tableActive = $('table.active-slides');
const $tableInactive = $('table.inactive-slides'); const $tableInactive = $('table.inactive-slides');
const getCronDateTime = function(cronExpression) {
const [minutes, hours, day, month, _, year] = cronExpression.split(' ');
return `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')} ${hours.padStart(2, '0')}:${minutes.padStart(2, '0')}`;
};
const loadDateTimePicker = function($els) { const loadDateTimePicker = function($els) {
const d = new Date();
$els.each(function() { $els.each(function() {
var $el = $(this); var $el = $(this);
if (!$el.val()) {
$el.val(prettyTimestamp(Date.now()).slice(0, -4));
}
$el.flatpickr({ $el.flatpickr({
enableTime: true, enableTime: true,
time_24hr: true, time_24hr: true,
allowInput: false, allowInput: false,
allowInvalidPreload: false, allowInvalidPreload: false,
dateFormat: 'Y-m-d H:i', dateFormat: 'Y-m-d H:i',
defaultHour: d.getHours(),
defaultMinute: d.getMinutes(),
onChange: function(selectedDates, dateStr, instance) { onChange: function(selectedDates, dateStr, instance) {
const d = selectedDates[0]; const d = selectedDates[0];
const $target = $el.parents('.widget:eq(0)').find('.target'); const $target = $el.parents('.widget:eq(0)').find('.target');
@ -81,6 +77,7 @@ jQuery(document).ready(function ($) {
const $scheduleStartGroup = $modal.find('.slide-schedule-group'); const $scheduleStartGroup = $modal.find('.slide-schedule-group');
const $scheduleEndGroup = $modal.find('.slide-schedule-end-group'); const $scheduleEndGroup = $modal.find('.slide-schedule-end-group');
const $durationGroup = $modal.find('.slide-duration-group'); const $durationGroup = $modal.find('.slide-duration-group');
const $isNotificationGroup = $modal.find('.slide-notification-group');
const $triggerStart = $scheduleStartGroup.find('.trigger'); const $triggerStart = $scheduleStartGroup.find('.trigger');
const $triggerEnd = $scheduleEndGroup.find('.trigger'); const $triggerEnd = $scheduleEndGroup.find('.trigger');
@ -90,42 +87,88 @@ jQuery(document).ready(function ($) {
const $datetimepickerStart = $scheduleStartGroup.find('.datetimepicker'); const $datetimepickerStart = $scheduleStartGroup.find('.datetimepicker');
const $datetimepickerEnd = $scheduleEndGroup.find('.datetimepicker'); const $datetimepickerEnd = $scheduleEndGroup.find('.datetimepicker');
const $isNotification = $isNotificationGroup.find('.trigger');
const isNotification = $isNotification.prop('checked');
let isLoopStart = $triggerStart.val() === 'loop';
let isCronStart = $triggerStart.val() === 'cron';
function updateScheduleChoices(isNotification, isLoopStart, isCronStart) {
let scheduleStartChoices = $.extend({}, schedule_start_choices);
let scheduleEndChoices = $.extend({}, schedule_end_choices);
if (!isNotification || isLoopStart) {
delete scheduleStartChoices['cron'];
delete scheduleEndChoices['duration'];
}
if (isNotification) {
delete scheduleStartChoices['loop'];
delete scheduleEndChoices['stayloop'];
if (isCronStart) {
delete scheduleEndChoices['datetime'];
}
}
return { scheduleStartChoices, scheduleEndChoices };
}
function applyChoices() {
const { scheduleStartChoices, scheduleEndChoices } = updateScheduleChoices(isNotification, isLoopStart, isCronStart);
recreateSelectOptions($triggerStart, scheduleStartChoices);
recreateSelectOptions($triggerEnd, scheduleEndChoices);
}
applyChoices();
isLoopStart = $triggerStart.val() === 'loop';
isCronStart = $triggerStart.val() === 'cron';
const isCronStart = $triggerStart.val() === 'cron';
const isCronEnd = $triggerEnd.val() === 'cron'; const isCronEnd = $triggerEnd.val() === 'cron';
const isDatetimeStart = $triggerStart.val() === 'datetime'; const isDatetimeStart = $triggerStart.val() === 'datetime';
const isDatetimeEnd = $triggerEnd.val() === 'datetime'; const isDatetimeEnd = $triggerEnd.val() === 'datetime';
const isLoopStart = $triggerStart.val() === 'loop'; const isStayloopEnd = $triggerEnd.val() === 'stayloop';
const isDurationEnd = $triggerEnd.val() === 'duration'; const isDurationEnd = $triggerEnd.val() === 'duration';
const flushValueStart = isLoopStart; const flushValueStart = isLoopStart;
const flushValueEnd = isLoopStart || isDurationEnd; const flushValueEnd = isLoopStart || isStayloopEnd || isDurationEnd;
const flushDuration = !isLoopStart && !isDurationEnd; const flushDuration = isNotification && isDatetimeEnd;
$targetCronFieldStart.toggleClass('hidden', !isCronStart); function toggleVisibility() {
$targetCronFieldEnd.toggleClass('hidden', !isCronEnd); $targetCronFieldStart.toggleClass('hidden', !isCronStart);
$datetimepickerStart.toggleClass('hidden', !isDatetimeStart); $targetCronFieldEnd.toggleClass('hidden', !isCronEnd);
$datetimepickerEnd.toggleClass('hidden', !isDatetimeEnd); $datetimepickerStart.toggleClass('hidden', !isDatetimeStart);
$datetimepickerEnd.toggleClass('hidden', !isDatetimeEnd);
$durationGroup.toggleClass('hidden', !isLoopStart && !isDurationEnd); $durationGroup.toggleClass('hidden', isNotification && isDatetimeEnd);
$scheduleEndGroup.toggleClass('hidden', isLoopStart); $scheduleEndGroup.toggleClass('hidden', isLoopStart);
$durationGroup.find('.widget input').prop('required', $durationGroup.is(':visible')); $durationGroup.find('.widget input').prop('required', $durationGroup.is(':visible'));
if (flushValueStart) {
$targetCronFieldStart.val('');
$datetimepickerStart.val('');
} }
if (flushValueEnd) { function flushValues() {
$targetCronFieldEnd.val(''); if (flushValueStart) {
$datetimepickerEnd.val(''); $targetCronFieldStart.val('');
$datetimepickerStart.val('');
}
if (flushValueEnd) {
$targetCronFieldEnd.val('');
$datetimepickerEnd.val('');
}
if (flushDuration) {
$targetDuration.val('1');
}
} }
if (flushDuration) { toggleVisibility();
$targetDuration.val('1'); flushValues();
} applyChoices();
}; };
const main = function () { const main = function () {
$("table").tableDnD({ $("table").tableDnD({
dragHandle: 'td a.slide-sort', dragHandle: 'td a.slide-sort',
@ -156,7 +199,7 @@ jQuery(document).ready(function ($) {
updateTable(); updateTable();
}); });
$(document).on('change', '.modal-slide select.trigger', function () { $(document).on('change', '.modal-slide select.trigger, .modal-slide input.trigger', function () {
inputSchedulerUpdate(); inputSchedulerUpdate();
}); });
@ -179,6 +222,7 @@ jQuery(document).ready(function ($) {
const hasCronEnd = slide.cron_schedule_end && slide.cron_schedule_end.length > 0; const hasCronEnd = slide.cron_schedule_end && slide.cron_schedule_end.length > 0;
const hasDateTimeEnd = hasCronEnd && validateCronDateTime(slide.cron_schedule_end); const hasDateTimeEnd = hasCronEnd && validateCronDateTime(slide.cron_schedule_end);
const isNotification = slide.is_notification;
let location = slide.location; let location = slide.location;
@ -186,17 +230,19 @@ jQuery(document).ready(function ($) {
location = 'https://www.youtube.com/watch?v=' + slide.location; location = 'https://www.youtube.com/watch?v=' + slide.location;
} }
console.log(slide)
$('.modal-slide-edit input:visible:eq(0)').focus().select(); $('.modal-slide-edit input:visible:eq(0)').focus().select();
$('#slide-edit-name').val(slide.name); $('#slide-edit-name').val(slide.name);
$('#slide-edit-type').val(slide.type); $('#slide-edit-type').val(slide.type);
$('#slide-edit-location').val(location).prop('disabled', !slide.is_editable); $('#slide-edit-location').val(location).prop('disabled', !slide.is_editable);
$('#slide-edit-duration').val(slide.duration); $('#slide-edit-duration').val(slide.duration);
$('#slide-edit-is-notification').prop('checked', isNotification);
$('#slide-edit-cron-schedule').val(slide.cron_schedule).toggleClass('hidden', !hasCron || hasDateTime); $('#slide-edit-cron-schedule').val(slide.cron_schedule).toggleClass('hidden', !hasCron || hasDateTime);
$('#slide-edit-cron-schedule-trigger').val(hasDateTime ? 'datetime' : (hasCron ? 'cron' : 'loop')); $('#slide-edit-cron-schedule-trigger').val(hasDateTime ? 'datetime' : (hasCron ? 'cron' : 'loop'));
$('#slide-edit-cron-schedule-end').val(slide.cron_schedule_end).toggleClass('hidden', !hasCronEnd || hasDateTimeEnd); $('#slide-edit-cron-schedule-end').val(slide.cron_schedule_end).toggleClass('hidden', !hasCronEnd || hasDateTimeEnd);
$('#slide-edit-cron-schedule-end-trigger').val(hasDateTimeEnd ? 'datetime' : (hasCronEnd ? 'cron' : 'duration')); $('#slide-edit-cron-schedule-end-trigger').val(hasDateTimeEnd ? 'datetime' : (hasCronEnd ? 'cron' : (isNotification ? 'duration' : 'stayloop')));
$('#slide-edit-cron-schedule-datetimepicker').toggleClass('hidden', !hasDateTime).val( $('#slide-edit-cron-schedule-datetimepicker').toggleClass('hidden', !hasDateTime).val(
hasDateTime ? getCronDateTime(slide.cron_schedule) : '' hasDateTime ? getCronDateTime(slide.cron_schedule) : ''

View File

@ -3,6 +3,11 @@ const prettyTimestamp = function(timestamp) {
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')} ` return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')} `
}; };
const getCronDateTime = function(cronExpression) {
const [minutes, hours, day, month, _, year] = cronExpression.split(' ');
return `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')} ${hours.padStart(2, '0')}:${minutes.padStart(2, '0')}`;
};
const validateCronDateTime = function(cronExpression) { const validateCronDateTime = function(cronExpression) {
const pattern = /^(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+\*\s+(\d+)$/; const pattern = /^(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+\*\s+(\d+)$/;
return pattern.test(cronExpression); return pattern.test(cronExpression);
@ -28,4 +33,26 @@ const modifyDate = function(date, seconds) {
const clone = new Date(date.getTime()); const clone = new Date(date.getTime());
clone.setSeconds(clone.getSeconds() + seconds); clone.setSeconds(clone.getSeconds() + seconds);
return clone; return clone;
}; };
const recreateSelectOptions = function($selectElement, options) {
if (!$selectElement.is('select')) {
throw new Error("Element is not a <select>");
}
const selectedValue = $selectElement.val();
$selectElement.empty();
$.each(options, function(key, label) {
$selectElement.append($('<option>', {
value: key,
html: label
}));
});
if ($selectElement.find(`option[value="${selectedValue}"]`).length > 0) {
$selectElement.val(selectedValue);
} else {
$selectElement.prop('selectedIndex', 0);
}
};

View File

@ -1,5 +1,5 @@
select.select-item-picker, select.select-item-picker,
a.btn, .btn,
button { button {
background-color: $white; background-color: $white;
border-radius: 5px; border-radius: 5px;

View File

@ -49,6 +49,12 @@
padding: 10px; padding: 10px;
} }
.panel td .td-secondary {
font-size: 14px;
opacity: 0.6;
margin-left: 3px;
}
.panel td a.item.sort { .panel td a.item.sort {
cursor: move; cursor: move;
} }

View File

@ -4,6 +4,7 @@
// Import base styles // Import base styles
@import 'base/html'; @import 'base/html';
@import 'base/tachyons'; @import 'base/tachyons';
// Import layout styles // Import layout styles

View File

@ -15,6 +15,7 @@
"slideshow_slide_panel_th_cron_scheduled": "Scheduled Start", "slideshow_slide_panel_th_cron_scheduled": "Scheduled Start",
"slideshow_slide_panel_th_activity": "Activity", "slideshow_slide_panel_th_activity": "Activity",
"slideshow_slide_panel_td_cron_scheduled_loop": "Loop", "slideshow_slide_panel_td_cron_scheduled_loop": "Loop",
"slideshow_slide_panel_td_cron_scheduled_notify": "Notify",
"slideshow_slide_panel_td_cron_scheduled_bad_cron": "Bad cron value", "slideshow_slide_panel_td_cron_scheduled_bad_cron": "Bad cron value",
"slideshow_slide_form_add_title": "Add Slide", "slideshow_slide_form_add_title": "Add Slide",
"slideshow_slide_form_add_submit": "Add", "slideshow_slide_form_add_submit": "Add",
@ -28,10 +29,12 @@
"slideshow_slide_form_label_object": "Object", "slideshow_slide_form_label_object": "Object",
"slideshow_slide_form_label_duration": "Duration", "slideshow_slide_form_label_duration": "Duration",
"slideshow_slide_form_label_duration_unit": "seconds", "slideshow_slide_form_label_duration_unit": "seconds",
"slideshow_slide_form_label_is_notification": "Act as notification",
"slideshow_slide_form_label_cron_scheduled": "Start", "slideshow_slide_form_label_cron_scheduled": "Start",
"slideshow_slide_form_label_cron_scheduled_end": "End", "slideshow_slide_form_label_cron_scheduled_end": "End",
"slideshow_slide_form_label_cron_scheduled_loop": "In the loop", "slideshow_slide_form_label_cron_scheduled_loop": "Always in loop",
"slideshow_slide_form_label_cron_scheduled_duration": "Duration", "slideshow_slide_form_label_cron_scheduled_duration": "Duration",
"slideshow_slide_form_label_cron_scheduled_stayloop": "Follow the loop",
"slideshow_slide_form_label_cron_scheduled_duration_unit": "seconds", "slideshow_slide_form_label_cron_scheduled_duration_unit": "seconds",
"slideshow_slide_form_label_cron_scheduled_datetime": "Date & Time", "slideshow_slide_form_label_cron_scheduled_datetime": "Date & Time",
"slideshow_slide_form_label_cron_scheduled_datetime_placeholder": "Set a date and time", "slideshow_slide_form_label_cron_scheduled_datetime_placeholder": "Set a date and time",

View File

@ -15,6 +15,7 @@
"slideshow_slide_panel_th_cron_scheduled": "Programmation", "slideshow_slide_panel_th_cron_scheduled": "Programmation",
"slideshow_slide_panel_th_activity": "Options", "slideshow_slide_panel_th_activity": "Options",
"slideshow_slide_panel_td_cron_scheduled_loop": "En boucle", "slideshow_slide_panel_td_cron_scheduled_loop": "En boucle",
"slideshow_slide_panel_td_cron_scheduled_notify": "Notifie",
"slideshow_slide_panel_td_cron_scheduled_bad_cron": "Mauvaise valeur cron", "slideshow_slide_panel_td_cron_scheduled_bad_cron": "Mauvaise valeur cron",
"slideshow_slide_form_add_title": "Ajout d'une slide", "slideshow_slide_form_add_title": "Ajout d'une slide",
"slideshow_slide_form_add_submit": "Ajouter", "slideshow_slide_form_add_submit": "Ajouter",
@ -28,10 +29,12 @@
"slideshow_slide_form_label_object": "Objet", "slideshow_slide_form_label_object": "Objet",
"slideshow_slide_form_label_duration": "Durée", "slideshow_slide_form_label_duration": "Durée",
"slideshow_slide_form_label_duration_unit": "secondes", "slideshow_slide_form_label_duration_unit": "secondes",
"slideshow_slide_form_label_is_notification": "Agit comme une notification",
"slideshow_slide_form_label_cron_scheduled": "Début", "slideshow_slide_form_label_cron_scheduled": "Début",
"slideshow_slide_form_label_cron_scheduled_end": "Fin", "slideshow_slide_form_label_cron_scheduled_end": "Fin",
"slideshow_slide_form_label_cron_scheduled_loop": "Dans la boucle", "slideshow_slide_form_label_cron_scheduled_loop": "Toujours en boucle",
"slideshow_slide_form_label_cron_scheduled_duration": "Durée", "slideshow_slide_form_label_cron_scheduled_duration": "Durée",
"slideshow_slide_form_label_cron_scheduled_stayloop": "Suit la boucle",
"slideshow_slide_form_label_cron_scheduled_duration_unit": "secondes", "slideshow_slide_form_label_cron_scheduled_duration_unit": "secondes",
"slideshow_slide_form_label_cron_scheduled_datetime": "Date & Heure", "slideshow_slide_form_label_cron_scheduled_datetime": "Date & Heure",
"slideshow_slide_form_label_cron_scheduled_datetime_placeholder": "Choisir une date et une heure", "slideshow_slide_form_label_cron_scheduled_datetime_placeholder": "Choisir une date et une heure",

View File

@ -15,6 +15,7 @@
"slideshow_slide_panel_th_cron_scheduled": "Avvia programmazione", "slideshow_slide_panel_th_cron_scheduled": "Avvia programmazione",
"slideshow_slide_panel_th_activity": "Attivita", "slideshow_slide_panel_th_activity": "Attivita",
"slideshow_slide_panel_td_cron_scheduled_loop": "Loop", "slideshow_slide_panel_td_cron_scheduled_loop": "Loop",
"slideshow_slide_panel_td_cron_scheduled_notify": "Notificare",
"slideshow_slide_panel_td_cron_scheduled_bad_cron": "Valore cron errato", "slideshow_slide_panel_td_cron_scheduled_bad_cron": "Valore cron errato",
"slideshow_slide_form_add_title": "Aggiungi Slide", "slideshow_slide_form_add_title": "Aggiungi Slide",
"slideshow_slide_form_add_submit": "Aggiungi", "slideshow_slide_form_add_submit": "Aggiungi",
@ -28,10 +29,12 @@
"slideshow_slide_form_label_object": "Oggetto", "slideshow_slide_form_label_object": "Oggetto",
"slideshow_slide_form_label_duration": "Durata", "slideshow_slide_form_label_duration": "Durata",
"slideshow_slide_form_label_duration_unit": "Secondi", "slideshow_slide_form_label_duration_unit": "Secondi",
"slideshow_slide_form_label_is_notification": "Agisce come una notifica",
"slideshow_slide_form_label_cron_scheduled": "Inizio", "slideshow_slide_form_label_cron_scheduled": "Inizio",
"slideshow_slide_form_label_cron_scheduled_end": "Fine", "slideshow_slide_form_label_cron_scheduled_end": "Fine",
"slideshow_slide_form_label_cron_scheduled_loop": "Ciclo continuo", "slideshow_slide_form_label_cron_scheduled_loop": "Sempre in loop",
"slideshow_slide_form_label_cron_scheduled_duration": "Durata", "slideshow_slide_form_label_cron_scheduled_duration": "Durata",
"slideshow_slide_form_label_cron_scheduled_stayloop": "Seguire il ciclo",
"slideshow_slide_form_label_cron_scheduled_duration_unit": "secondi", "slideshow_slide_form_label_cron_scheduled_duration_unit": "secondi",
"slideshow_slide_form_label_cron_scheduled_datetime": "Data e ora", "slideshow_slide_form_label_cron_scheduled_datetime": "Data e ora",
"slideshow_slide_form_label_cron_scheduled_datetime_placeholder": "Imposta Data e ora", "slideshow_slide_form_label_cron_scheduled_datetime_placeholder": "Imposta Data e ora",

View File

@ -1,12 +1,13 @@
import json import json
import logging import logging
from datetime import datetime
from typing import Optional from typing import Optional
from flask import Flask, render_template, redirect, request, url_for, send_from_directory, jsonify, abort from flask import Flask, render_template, redirect, request, url_for, send_from_directory, jsonify, abort
from src.service.ModelStore import ModelStore from src.service.ModelStore import ModelStore
from src.interface.ObController import ObController from src.interface.ObController import ObController
from src.util.utils import get_safe_cron_descriptor from src.util.utils import get_safe_cron_descriptor, is_valid_cron_date_time, get_cron_date_time
from src.util.UtilNetwork import get_ip_address, get_safe_remote_addr from src.util.UtilNetwork import get_ip_address, get_safe_remote_addr
from src.model.enum.AnimationSpeed import animation_speed_duration from src.model.enum.AnimationSpeed import animation_speed_duration
@ -22,15 +23,25 @@ class PlayerController(ObController):
playlist_notifications = [] playlist_notifications = []
for slide in slides: for slide in slides:
has_valid_start_date = 'cron_schedule' in slide and slide['cron_schedule'] and get_safe_cron_descriptor(slide['cron_schedule']) has_valid_start_date = 'cron_schedule' in slide and slide['cron_schedule'] and get_safe_cron_descriptor(slide['cron_schedule']) and is_valid_cron_date_time(slide['cron_schedule'])
has_valid_end_date = 'cron_schedule_end' in slide and slide['cron_schedule_end'] and get_safe_cron_descriptor(slide['cron_schedule_end']) and is_valid_cron_date_time(slide['cron_schedule_end'])
if slide['is_notification']: if slide['is_notification'] and has_valid_start_date:
if has_valid_start_date: if has_valid_start_date:
playlist_notifications.append(slide) playlist_notifications.append(slide)
else: else:
logging.warn('Slide {} is notification but start date is invalid'.format(slide['name'])) logging.warn('Slide {} is notification but start date is invalid'.format(slide['name']))
else: else:
if has_valid_start_date: if has_valid_start_date:
start_date = get_cron_date_time(slide['cron_schedule'], object=True)
if datetime.now() <= start_date:
continue
if has_valid_end_date:
end_date = get_cron_date_time(slide['cron_schedule_end'], object=True)
if datetime.now() >= end_date:
continue
playlist_loop.append(slide) playlist_loop.append(slide)
else: else:
playlist_loop.append(slide) playlist_loop.append(slide)

View File

@ -47,6 +47,7 @@ class SlideshowController(ObController):
name=request.form['name'], name=request.form['name'],
type=str_to_enum(request.form['type'], SlideType), type=str_to_enum(request.form['type'], SlideType),
duration=request.form['duration'], duration=request.form['duration'],
is_notification=True if 'is_notification' in request.form else False,
playlist_id=request.form['playlist_id'] if 'playlist_id' in request.form and request.form['playlist_id'] else None, playlist_id=request.form['playlist_id'] if 'playlist_id' in request.form and request.form['playlist_id'] else None,
cron_schedule=get_optional_string(request.form['cron_schedule']), cron_schedule=get_optional_string(request.form['cron_schedule']),
cron_schedule_end=get_optional_string(request.form['cron_schedule_end']), cron_schedule_end=get_optional_string(request.form['cron_schedule_end']),
@ -82,6 +83,7 @@ class SlideshowController(ObController):
id=request.form['id'], id=request.form['id'],
name=request.form['name'], name=request.form['name'],
duration=request.form['duration'], duration=request.form['duration'],
is_notification=True if 'is_notification' in request.form else False,
cron_schedule=request.form['cron_schedule'], cron_schedule=request.form['cron_schedule'],
cron_schedule_end=request.form['cron_schedule_end'], cron_schedule_end=request.form['cron_schedule_end'],
location=request.form['location'] if 'location' in request.form and request.form['location'] else None location=request.form['location'] if 'location' in request.form and request.form['location'] else None

View File

@ -117,7 +117,7 @@ class SlideManager(ModelManager):
for slide_id, slide_position in positions.items(): for slide_id, slide_position in positions.items():
self._db.update_by_id(self.TABLE_NAME, slide_id, {"position": slide_position}) self._db.update_by_id(self.TABLE_NAME, slide_id, {"position": slide_position})
def update_form(self, id: int, name: str, duration: int, cron_schedule: Optional[str] = '', cron_schedule_end: Optional[str] = '', location: Optional[str] = None) -> Slide: def update_form(self, id: int, name: str, duration: int, is_notification: bool = False, cron_schedule: Optional[str] = '', cron_schedule_end: Optional[str] = '', location: Optional[str] = None) -> Slide:
slide = self.get(id) slide = self.get(id)
if not slide: if not slide:
@ -126,6 +126,7 @@ class SlideManager(ModelManager):
form = { form = {
"name": name, "name": name,
"duration": duration, "duration": duration,
"is_notification": True if is_notification else False,
"cron_schedule": get_optional_string(cron_schedule), "cron_schedule": get_optional_string(cron_schedule),
"cron_schedule_end": get_optional_string(cron_schedule_end) "cron_schedule_end": get_optional_string(cron_schedule_end)
} }

View File

@ -9,7 +9,7 @@ from src.model.hook.HookRegistration import HookRegistration
from src.model.hook.StaticHookRegistration import StaticHookRegistration from src.model.hook.StaticHookRegistration import StaticHookRegistration
from src.model.hook.FunctionalHookRegistration import FunctionalHookRegistration from src.model.hook.FunctionalHookRegistration import FunctionalHookRegistration
from src.constant.WebDirConstant import WebDirConstant from src.constant.WebDirConstant import WebDirConstant
from src.util.utils import get_safe_cron_descriptor, is_validate_cron_date_time, seconds_to_hhmmss, am_i_in_docker from src.util.utils import get_safe_cron_descriptor, is_valid_cron_date_time, seconds_to_hhmmss, am_i_in_docker
class TemplateRenderer: class TemplateRenderer:
@ -39,7 +39,7 @@ class TemplateRenderer:
cron_descriptor=self.cron_descriptor, cron_descriptor=self.cron_descriptor,
str=str, str=str,
seconds_to_hhmmss=seconds_to_hhmmss, seconds_to_hhmmss=seconds_to_hhmmss,
is_validate_cron_date_time=is_validate_cron_date_time, is_valid_cron_date_time=is_valid_cron_date_time,
l=self._model_store.lang().map(), l=self._model_store.lang().map(),
t=self._model_store.lang().translate, t=self._model_store.lang().translate,
) )

View File

@ -8,6 +8,7 @@ import unicodedata
import platform import platform
from datetime import datetime
from typing import Optional, List, Dict from typing import Optional, List, Dict
from enum import Enum from enum import Enum
from cron_descriptor import ExpressionDescriptor from cron_descriptor import ExpressionDescriptor
@ -58,13 +59,19 @@ def camel_to_snake(camel: str) -> str:
return CAMEL_CASE_TO_SNAKE_CASE_PATTERN.sub('_', camel).lower() return CAMEL_CASE_TO_SNAKE_CASE_PATTERN.sub('_', camel).lower()
def is_validate_cron_date_time(expression) -> bool: def is_valid_cron_date_time(expression: str) -> bool:
pattern = re.compile(r'^(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+\*\s+(\d+)$') pattern = re.compile(r'^(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+\*\s+(\d+)$')
return bool(pattern.match(expression)) return bool(pattern.match(expression))
def get_cron_date_time(cron_expression: str, object: bool) -> str:
minutes, hours, day, month, _, year = cron_expression.split(' ')
formatted_date_time = f"{year}-{month.zfill(2)}-{day.zfill(2)} {hours.zfill(2)}:{minutes.zfill(2)}"
return datetime.strptime(formatted_date_time, '%Y-%m-%d %H:%M') if object else formatted_date_time
def get_safe_cron_descriptor(expression: str, use_24hour_time_format=True, locale_code: Optional[str] = None) -> str: def get_safe_cron_descriptor(expression: str, use_24hour_time_format=True, locale_code: Optional[str] = None) -> str:
if is_validate_cron_date_time(expression): if is_valid_cron_date_time(expression):
[minutes, hours, day, month, _, year] = expression.split(' ') [minutes, hours, day, month, _, year] = expression.split(' ')
return "{}-{}-{} at {}:{}".format( return "{}-{}-{} at {}:{}".format(
year, year,
@ -233,4 +240,4 @@ def restart(debug: bool) -> None:
pass pass
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
pass pass

View File

@ -61,8 +61,12 @@
{% if slide.cron_schedule %} {% if slide.cron_schedule %}
{% set cron_desc = cron_descriptor(slide.cron_schedule) %} {% set cron_desc = cron_descriptor(slide.cron_schedule) %}
{% if cron_desc %} {% if cron_desc %}
{% if is_validate_cron_date_time(slide.cron_schedule) %} {% if is_valid_cron_date_time(slide.cron_schedule) %}
📆 {{ cron_desc }} {% if slide.is_notification %}
🔔 {{ l.slideshow_slide_panel_td_cron_scheduled_notify }} <span class="td-secondary">{{ cron_desc }}</span>
{% else %}
🔄 {{ l.slideshow_slide_panel_td_cron_scheduled_loop }}<span class="td-secondary">{{ cron_desc }}</span>
{% endif %}
{% else %} {% else %}
⏳ {{ cron_desc }} ⏳ {{ cron_desc }}
{% endif %} {% endif %}
@ -77,8 +81,12 @@
{% if slide.cron_schedule_end %} {% if slide.cron_schedule_end %}
{% set cron_desc_end = cron_descriptor(slide.cron_schedule_end) %} {% set cron_desc_end = cron_descriptor(slide.cron_schedule_end) %}
{% if cron_desc_end %} {% if cron_desc_end %}
{% if is_validate_cron_date_time(slide.cron_schedule_end) %} {% if is_valid_cron_date_time(slide.cron_schedule_end) %}
📆 {{ cron_desc_end }} {% if slide.is_notification %}
📆<span class="td-secondary">{{ cron_desc_end }}</span>
{% else %}
⏱️ {{ slide.duration }} {{ l.slideshow_slide_panel_th_duration_unit }}<span class="td-secondary">{{ cron_desc_end }}</span>
{% endif %}
{% else %} {% else %}
⏳ {{ cron_desc_end }} ⏳ {{ cron_desc_end }}
{% endif %} {% endif %}

View File

@ -10,6 +10,18 @@
{% endblock %} {% endblock %}
{% block add_js %} {% block add_js %}
<script type="text/javascript">
var schedule_start_choices = {
'loop': '{{ l.slideshow_slide_form_label_cron_scheduled_loop }}',
'datetime': '{{ l.slideshow_slide_form_label_cron_scheduled_datetime }}',
'cron': '{{ l.slideshow_slide_form_label_cron_scheduled_cron }}',
};
var schedule_end_choices = {
'duration': '{{ l.slideshow_slide_form_label_cron_scheduled_duration }}',
'stayloop': '{{ l.slideshow_slide_form_label_cron_scheduled_stayloop }}',
'datetime': '{{ l.slideshow_slide_form_label_cron_scheduled_datetime }}',
};
</script>
<script src="{{ STATIC_PREFIX }}js/lib/flatpickr.min.js"></script> <script src="{{ STATIC_PREFIX }}js/lib/flatpickr.min.js"></script>
<script src="{{ STATIC_PREFIX }}js/lib/tablednd-fixed.js"></script> <script src="{{ STATIC_PREFIX }}js/lib/tablednd-fixed.js"></script>
<script src="{{ STATIC_PREFIX }}js/slideshow/slides.js"></script> <script src="{{ STATIC_PREFIX }}js/slideshow/slides.js"></script>
@ -31,6 +43,7 @@
<a href="{% if current_playlist %}{{ url_for('player_use', playlist_slug_or_id=current_playlist.slug) }}{% else %}{{ url_for('player') }}{% endif %}" target="_blank" class="btn" title="{{ l.slideshow_goto_player }}"> <a href="{% if current_playlist %}{{ url_for('player_use', playlist_slug_or_id=current_playlist.slug) }}{% else %}{{ url_for('player') }}{% endif %}" target="_blank" class="btn" title="{{ l.slideshow_goto_player }}">
<i class="fa fa-play"></i> <i class="fa fa-play"></i>
</a> </a>
<a href="{{ url_for('slideshow_player_refresh') }}" class="btn" title="{{ l.slideshow_refresh_player }}"> <a href="{{ url_for('slideshow_player_refresh') }}" class="btn" title="{{ l.slideshow_refresh_player }}">
<i class="fa fa-refresh"></i> <i class="fa fa-refresh"></i>
</a> </a>
@ -81,7 +94,6 @@
{% endwith %} {% endwith %}
</div> </div>
</div> </div>
</div>
<div class="modals hidden"> <div class="modals hidden">
<div class="modals-outer"> <div class="modals-outer">

View File

@ -45,6 +45,14 @@
{{ l.slideshow_slide_form_section_scheduling }} {{ l.slideshow_slide_form_section_scheduling }}
</h3> </h3>
<div class="form-group slide-notification-group">
<label for="slide-add-is-notification">{{ l.slideshow_slide_form_label_is_notification }}</label>
<div class="widget">
<input type="checkbox" class="trigger slide-is-notification" name="is_notification" id="slide-add-is-notification" />
</div>
</div>
<div class="form-group slide-schedule-group"> <div class="form-group slide-schedule-group">
<label for="slide-add-cron-schedule">{{ l.slideshow_slide_form_label_cron_scheduled }}</label> <label for="slide-add-cron-schedule">{{ l.slideshow_slide_form_label_cron_scheduled }}</label>
<div class="widget"> <div class="widget">

View File

@ -43,6 +43,13 @@
{{ l.slideshow_slide_form_section_scheduling }} {{ l.slideshow_slide_form_section_scheduling }}
</h3> </h3>
<div class="form-group slide-notification-group">
<label for="slide-edit-is-notification">{{ l.slideshow_slide_form_label_is_notification }}</label>
<div class="widget">
<input type="checkbox" class="trigger slide-is-notification" name="is_notification" id="slide-edit-is-notification" />
</div>
</div>
<div class="form-group slide-schedule-group"> <div class="form-group slide-schedule-group">
<label for="slide-edit-cron-schedule">{{ l.slideshow_slide_form_label_cron_scheduled }}</label> <label for="slide-edit-cron-schedule">{{ l.slideshow_slide_form_label_cron_scheduled }}</label>
<div class="widget"> <div class="widget">
@ -62,7 +69,6 @@
<select id="slide-edit-cron-schedule-end-trigger" class="trigger"> <select id="slide-edit-cron-schedule-end-trigger" class="trigger">
<option value="duration">{{ l.slideshow_slide_form_label_cron_scheduled_duration }}</option> <option value="duration">{{ l.slideshow_slide_form_label_cron_scheduled_duration }}</option>
<option value="datetime">{{ l.slideshow_slide_form_label_cron_scheduled_datetime }}</option> <option value="datetime">{{ l.slideshow_slide_form_label_cron_scheduled_datetime }}</option>
<option value="cron">{{ l.slideshow_slide_form_label_cron_scheduled_cron }}</option>
</select> </select>
<input type="text" id="slide-edit-cron-schedule-end-datetimepicker" class="datetimepicker" value="" placeholder="{{ l.slideshow_slide_form_label_cron_scheduled_datetime_placeholder }}" /> <input type="text" id="slide-edit-cron-schedule-end-datetimepicker" class="datetimepicker" value="" placeholder="{{ l.slideshow_slide_form_label_cron_scheduled_datetime_placeholder }}" />
<input type="text" name="cron_schedule_end" id="slide-edit-cron-schedule-end" class="target hidden" placeholder="{{ l.slideshow_slide_form_widget_cron_scheduled_placeholder }}" /> <input type="text" name="cron_schedule_end" id="slide-edit-cron-schedule-end" class="target hidden" placeholder="{{ l.slideshow_slide_form_widget_cron_scheduled_placeholder }}" />