Merge pull request #90 from jr-k/develop

Release v1.20.0
This commit is contained in:
JRK 2024-06-30 14:39:29 +02:00 committed by GitHub
commit 03c2a47410
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 245 additions and 82 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 $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 d = new Date();
$els.each(function() {
var $el = $(this);
if (!$el.val()) {
$el.val(prettyTimestamp(Date.now()).slice(0, -4));
}
$el.flatpickr({
enableTime: true,
time_24hr: true,
allowInput: false,
allowInvalidPreload: false,
dateFormat: 'Y-m-d H:i',
defaultHour: d.getHours(),
defaultMinute: d.getMinutes(),
onChange: function(selectedDates, dateStr, instance) {
const d = selectedDates[0];
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 $scheduleEndGroup = $modal.find('.slide-schedule-end-group');
const $durationGroup = $modal.find('.slide-duration-group');
const $isNotificationGroup = $modal.find('.slide-notification-group');
const $triggerStart = $scheduleStartGroup.find('.trigger');
const $triggerEnd = $scheduleEndGroup.find('.trigger');
@ -90,42 +87,88 @@ jQuery(document).ready(function ($) {
const $datetimepickerStart = $scheduleStartGroup.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 isDatetimeStart = $triggerStart.val() === 'datetime';
const isDatetimeEnd = $triggerEnd.val() === 'datetime';
const isLoopStart = $triggerStart.val() === 'loop';
const isStayloopEnd = $triggerEnd.val() === 'stayloop';
const isDurationEnd = $triggerEnd.val() === 'duration';
const flushValueStart = isLoopStart;
const flushValueEnd = isLoopStart || isDurationEnd;
const flushDuration = !isLoopStart && !isDurationEnd;
const flushValueEnd = isLoopStart || isStayloopEnd || isDurationEnd;
const flushDuration = isNotification && isDatetimeEnd;
$targetCronFieldStart.toggleClass('hidden', !isCronStart);
$targetCronFieldEnd.toggleClass('hidden', !isCronEnd);
$datetimepickerStart.toggleClass('hidden', !isDatetimeStart);
$datetimepickerEnd.toggleClass('hidden', !isDatetimeEnd);
function toggleVisibility() {
$targetCronFieldStart.toggleClass('hidden', !isCronStart);
$targetCronFieldEnd.toggleClass('hidden', !isCronEnd);
$datetimepickerStart.toggleClass('hidden', !isDatetimeStart);
$datetimepickerEnd.toggleClass('hidden', !isDatetimeEnd);
$durationGroup.toggleClass('hidden', !isLoopStart && !isDurationEnd);
$scheduleEndGroup.toggleClass('hidden', isLoopStart);
$durationGroup.toggleClass('hidden', isNotification && isDatetimeEnd);
$scheduleEndGroup.toggleClass('hidden', isLoopStart);
$durationGroup.find('.widget input').prop('required', $durationGroup.is(':visible'));
if (flushValueStart) {
$targetCronFieldStart.val('');
$datetimepickerStart.val('');
$durationGroup.find('.widget input').prop('required', $durationGroup.is(':visible'));
}
if (flushValueEnd) {
$targetCronFieldEnd.val('');
$datetimepickerEnd.val('');
function flushValues() {
if (flushValueStart) {
$targetCronFieldStart.val('');
$datetimepickerStart.val('');
}
if (flushValueEnd) {
$targetCronFieldEnd.val('');
$datetimepickerEnd.val('');
}
if (flushDuration) {
$targetDuration.val('1');
}
}
if (flushDuration) {
$targetDuration.val('0');
}
toggleVisibility();
flushValues();
applyChoices();
};
const main = function () {
$("table").tableDnD({
dragHandle: 'td a.slide-sort',
@ -156,7 +199,7 @@ jQuery(document).ready(function ($) {
updateTable();
});
$(document).on('change', '.modal-slide select.trigger', function () {
$(document).on('change', '.modal-slide select.trigger, .modal-slide input.trigger', function () {
inputSchedulerUpdate();
});
@ -179,6 +222,7 @@ jQuery(document).ready(function ($) {
const hasCronEnd = slide.cron_schedule_end && slide.cron_schedule_end.length > 0;
const hasDateTimeEnd = hasCronEnd && validateCronDateTime(slide.cron_schedule_end);
const isNotification = slide.is_notification;
let location = slide.location;
@ -191,12 +235,13 @@ jQuery(document).ready(function ($) {
$('#slide-edit-type').val(slide.type);
$('#slide-edit-location').val(location).prop('disabled', !slide.is_editable);
$('#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-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-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(
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')} `
};
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 pattern = /^(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+\*\s+(\d+)$/;
return pattern.test(cronExpression);
@ -28,4 +33,26 @@ const modifyDate = function(date, seconds) {
const clone = new Date(date.getTime());
clone.setSeconds(clone.getSeconds() + seconds);
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,
a.btn,
.btn,
button {
background-color: $white;
border-radius: 5px;

View File

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

View File

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

View File

@ -15,6 +15,7 @@
"slideshow_slide_panel_th_cron_scheduled": "Scheduled Start",
"slideshow_slide_panel_th_activity": "Activity",
"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_form_add_title": "Add Slide",
"slideshow_slide_form_add_submit": "Add",
@ -28,10 +29,12 @@
"slideshow_slide_form_label_object": "Object",
"slideshow_slide_form_label_duration": "Duration",
"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_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_stayloop": "Follow the loop",
"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_placeholder": "Set a date and time",

View File

@ -15,6 +15,7 @@
"slideshow_slide_panel_th_cron_scheduled": "Programmation",
"slideshow_slide_panel_th_activity": "Options",
"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_form_add_title": "Ajout d'une slide",
"slideshow_slide_form_add_submit": "Ajouter",
@ -28,10 +29,12 @@
"slideshow_slide_form_label_object": "Objet",
"slideshow_slide_form_label_duration": "Durée",
"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_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_stayloop": "Suit la boucle",
"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_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_activity": "Attivita",
"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_form_add_title": "Aggiungi Slide",
"slideshow_slide_form_add_submit": "Aggiungi",
@ -28,10 +29,12 @@
"slideshow_slide_form_label_object": "Oggetto",
"slideshow_slide_form_label_duration": "Durata",
"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_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_stayloop": "Seguire il ciclo",
"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_placeholder": "Imposta Data e ora",

View File

@ -1,11 +1,13 @@
import json
import logging
from datetime import datetime
from typing import Optional
from flask import Flask, render_template, redirect, request, url_for, send_from_directory, jsonify, abort
from src.service.ModelStore import ModelStore
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.model.enum.AnimationSpeed import animation_speed_duration
@ -18,20 +20,37 @@ class PlayerController(ObController):
playlist = self._model_store.playlist().get(playlist_id)
playlist_loop = []
playlist_cron = []
playlist_notifications = []
for slide in slides:
if 'cron_schedule' in slide and slide['cron_schedule']:
if get_safe_cron_descriptor(slide['cron_schedule']):
playlist_cron.append(slide)
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'] and has_valid_start_date:
if has_valid_start_date:
playlist_notifications.append(slide)
else:
logging.warn('Slide {} is notification but start date is invalid'.format(slide['name']))
else:
playlist_loop.append(slide)
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)
else:
playlist_loop.append(slide)
playlists = {
'playlist_id': playlist.id if playlist else None,
'time_sync': playlist.time_sync if playlist else self._model_store.variable().get_one_by_name("playlist_default_time_sync").as_bool(),
'loop': playlist_loop,
'cron': playlist_cron,
'notifications': playlist_notifications,
'hard_refresh_request': self._model_store.variable().get_one_by_name("refresh_player_request").as_int()
}

View File

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

View File

@ -20,6 +20,7 @@ class SlideManager(ModelManager):
"name CHAR(255)",
"type CHAR(30)",
"enabled INTEGER DEFAULT 0",
"is_notification INTEGER DEFAULT 0",
"playlist_id INTEGER",
"duration INTEGER",
"position INTEGER",
@ -116,7 +117,7 @@ class SlideManager(ModelManager):
for slide_id, slide_position in positions.items():
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)
if not slide:
@ -125,6 +126,7 @@ class SlideManager(ModelManager):
form = {
"name": name,
"duration": duration,
"is_notification": True if is_notification else False,
"cron_schedule": get_optional_string(cron_schedule),
"cron_schedule_end": get_optional_string(cron_schedule_end)
}

View File

@ -8,13 +8,14 @@ from src.util.utils import str_to_enum
class Slide:
def __init__(self, location: str = '', playlist_id: Optional[int] = None, duration: int = 3, type: Union[SlideType, str] = SlideType.URL, enabled: bool = False, name: str = 'Untitled', position: int = 999, id: Optional[int] = None, cron_schedule: Optional[str] = None, cron_schedule_end: Optional[str] = None, created_by: Optional[str] = None, updated_by: Optional[str] = None, created_at: Optional[int] = None, updated_at: Optional[int] = None):
def __init__(self, location: str = '', playlist_id: Optional[int] = None, duration: int = 3, type: Union[SlideType, str] = SlideType.URL, is_notification: bool = False, enabled: bool = False, name: str = 'Untitled', position: int = 999, id: Optional[int] = None, cron_schedule: Optional[str] = None, cron_schedule_end: Optional[str] = None, created_by: Optional[str] = None, updated_by: Optional[str] = None, created_at: Optional[int] = None, updated_at: Optional[int] = None):
self._id = id if id else None
self._location = location
self._playlist_id = playlist_id
self._duration = duration
self._type = str_to_enum(type, SlideType) if isinstance(type, str) else type
self._enabled = enabled
self._is_notification = is_notification
self._name = name
self._position = position
self._cron_schedule = cron_schedule
@ -116,6 +117,14 @@ class Slide:
def enabled(self, value: bool):
self._enabled = bool(value)
@property
def is_notification(self) -> bool:
return bool(self._is_notification)
@is_notification.setter
def is_notification(self, value: bool):
self._is_notification = bool(value)
@property
def name(self) -> str:
return self._name
@ -138,6 +147,7 @@ class Slide:
f"name='{self.name}',\n" \
f"type='{self.type}',\n" \
f"enabled='{self.enabled}',\n" \
f"is_notification='{self.is_notification}',\n" \
f"duration='{self.duration}',\n" \
f"position='{self.position}',\n" \
f"location='{self.location}',\n" \
@ -163,6 +173,7 @@ class Slide:
"id": self.id,
"name": self.name,
"enabled": self.enabled,
"is_notification": self.is_notification,
"position": self.position,
"type": self.type.value,
"duration": self.duration,

View File

@ -9,7 +9,7 @@ from src.model.hook.HookRegistration import HookRegistration
from src.model.hook.StaticHookRegistration import StaticHookRegistration
from src.model.hook.FunctionalHookRegistration import FunctionalHookRegistration
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:
@ -39,7 +39,7 @@ class TemplateRenderer:
cron_descriptor=self.cron_descriptor,
str=str,
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(),
t=self._model_store.lang().translate,
)

View File

@ -8,6 +8,7 @@ import unicodedata
import platform
from datetime import datetime
from typing import Optional, List, Dict
from enum import Enum
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()
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+)$')
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:
if is_validate_cron_date_time(expression):
if is_valid_cron_date_time(expression):
[minutes, hours, day, month, _, year] = expression.split(' ')
return "{}-{}-{} at {}:{}".format(
year,
@ -233,4 +240,4 @@ def restart(debug: bool) -> None:
pass
except subprocess.CalledProcessError:
pass

View File

@ -1 +1 @@
1.19.5
1.20.0

View File

@ -24,7 +24,7 @@
<iframe src="/player/default"></iframe>
{% endif %}
</div>
<div id="CronSlide" class="slide" style="z-index: 0;">
<div id="NotificationSlide" class="slide" style="z-index: 0;">
</div>
<div id="FirstSlide" class="slide slide-loop" style="z-index: 500;">
@ -74,12 +74,12 @@
let curItemIndex = -1;
let secondsBeforeNext = 0;
const introSlide = document.getElementById('IntroSlide');
const cronSlide = document.getElementById('CronSlide');
const notificationSlide = document.getElementById('NotificationSlide');
const firstSlide = document.getElementById('FirstSlide');
const secondSlide = document.getElementById('SecondSlide');
let curSlide = secondSlide;
let nextSlide = firstSlide;
let cronItemIndex = null;
let notificationItemIndex = null;
// Functions
const itemCheck = setInterval(function() {
@ -189,7 +189,7 @@
};
const main = function() {
setInterval(checkAndMoveCron, 1000);
setInterval(checkAndMoveNotifications, 1000);
setTimeout(function() {
if (items.loop.length === 0) {
return setTimeout(main, 5000);
@ -372,9 +372,9 @@
setTimeout(autoplayLoader, delayNoisyContentJIT);
}
const checkAndMoveCron = function() {
for (let i = 0; i < items.cron.length; i++) {
const item = items.cron[i];
const checkAndMoveNotifications = function() {
for (let i = 0; i < items.notifications.length; i++) {
const item = items.notifications[i];
const now = new Date();
const isFullyElapsedMinute = (new Date()).getSeconds() === 0;
@ -383,52 +383,52 @@
const hasDateTime = hasCron && validateCronDateTime(item.cron_schedule);
const hasDateTimeEnd = hasCronEnd && validateCronDateTime(item.cron_schedule_end);
if (cronItemIndex !== i && hasDateTime) {
if (notificationItemIndex !== i && hasDateTime) {
const startDate = cronToDateTimeObject(item.cron_schedule);
const endDate = hasDateTimeEnd ? cronToDateTimeObject(item.cron_schedule_end) : modifyDate(startDate, item.duration);
if (now >= startDate && now < endDate) {
moveToCronSlide(i);
moveToNotificationSlide(i);
}
}
if (cronItemIndex === i && hasDateTime) {
if (notificationItemIndex === i && hasDateTime) {
const startDate = cronToDateTimeObject(item.cron_schedule);
const endDate = hasDateTimeEnd ? cronToDateTimeObject(item.cron_schedule_end) : modifyDate(startDate, item.duration);
if (now >= endDate) {
stopCronSlide();
stopNotificationSlide();
}
}
if (cronItemIndex !== i && isFullyElapsedMinute && hasCron && cron.isActive(item.cron_schedule)) {
moveToCronSlide(i);
if (notificationItemIndex !== i && isFullyElapsedMinute && hasCron && cron.isActive(item.cron_schedule)) {
moveToNotificationSlide(i);
}
if (cronItemIndex === i && isFullyElapsedMinute && hasCronEnd && cron.isActive(item.cron_schedule_end)) {
stopCronSlide();
if (notificationItemIndex === i && isFullyElapsedMinute && hasCronEnd && cron.isActive(item.cron_schedule_end)) {
stopNotificationSlide();
}
}
};
const moveToCronSlide = function(cronSlideIndex) {
const item = items.cron[cronSlideIndex];
cronItemIndex = cronSlideIndex;
const moveToNotificationSlide = function(notificationSlideIndex) {
const item = items.notifications[notificationSlideIndex];
notificationItemIndex = notificationSlideIndex;
pause();
const callbackReady = function() {
cronSlide.style.zIndex = '2000';
notificationSlide.style.zIndex = '2000';
if (!item.cron_schedule_end) {
setTimeout(function() {
stopCronSlide();
stopNotificationSlide();
}, safe_duration(item) * 1000);
}
};
loadContent(cronSlide, callbackReady, item);
loadContent(notificationSlide, callbackReady, item);
};
const stopCronSlide = function() {
cronItemIndex = null;
cronSlide.style.zIndex = '0';
const stopNotificationSlide = function() {
notificationItemIndex = null;
notificationSlide.style.zIndex = '0';
play();
};

View File

@ -61,8 +61,12 @@
{% if slide.cron_schedule %}
{% set cron_desc = cron_descriptor(slide.cron_schedule) %}
{% if cron_desc %}
{% if is_validate_cron_date_time(slide.cron_schedule) %}
📆 {{ cron_desc }}
{% if is_valid_cron_date_time(slide.cron_schedule) %}
{% 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 %}
⏳ {{ cron_desc }}
{% endif %}
@ -77,8 +81,12 @@
{% if slide.cron_schedule_end %}
{% set cron_desc_end = cron_descriptor(slide.cron_schedule_end) %}
{% if cron_desc_end %}
{% if is_validate_cron_date_time(slide.cron_schedule_end) %}
📆 {{ cron_desc_end }}
{% if is_valid_cron_date_time(slide.cron_schedule_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 %}
⏳ {{ cron_desc_end }}
{% endif %}

View File

@ -10,6 +10,18 @@
{% endblock %}
{% 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 = {
'stayloop': '{{ l.slideshow_slide_form_label_cron_scheduled_stayloop }}',
'datetime': '{{ l.slideshow_slide_form_label_cron_scheduled_datetime }}',
'duration': '{{ l.slideshow_slide_form_label_cron_scheduled_duration }}',
};
</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/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 }}">
<i class="fa fa-play"></i>
</a>
<a href="{{ url_for('slideshow_player_refresh') }}" class="btn" title="{{ l.slideshow_refresh_player }}">
<i class="fa fa-refresh"></i>
</a>
@ -81,7 +94,6 @@
{% endwith %}
</div>
</div>
</div>
<div class="modals hidden">
<div class="modals-outer">

View File

@ -45,6 +45,14 @@
{{ l.slideshow_slide_form_section_scheduling }}
</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">
<label for="slide-add-cron-schedule">{{ l.slideshow_slide_form_label_cron_scheduled }}</label>
<div class="widget">

View File

@ -43,6 +43,13 @@
{{ l.slideshow_slide_form_section_scheduling }}
</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">
<label for="slide-edit-cron-schedule">{{ l.slideshow_slide_form_label_cron_scheduled }}</label>
<div class="widget">
@ -62,7 +69,6 @@
<select id="slide-edit-cron-schedule-end-trigger" class="trigger">
<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="cron">{{ l.slideshow_slide_form_label_cron_scheduled_cron }}</option>
</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" name="cron_schedule_end" id="slide-edit-cron-schedule-end" class="target hidden" placeholder="{{ l.slideshow_slide_form_widget_cron_scheduled_placeholder }}" />