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 $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('1');
}
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;
@ -186,17 +230,19 @@ jQuery(document).ready(function ($) {
location = 'https://www.youtube.com/watch?v=' + slide.location;
}
console.log(slide)
$('.modal-slide-edit input:visible:eq(0)').focus().select();
$('#slide-edit-name').val(slide.name);
$('#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,12 +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
@ -22,15 +23,25 @@ class PlayerController(ObController):
playlist_notifications = []
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:
playlist_notifications.append(slide)
else:
logging.warn('Slide {} is notification but start date is invalid'.format(slide['name']))
else:
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)

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

@ -117,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:
@ -126,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

@ -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

@ -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 = {
'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/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 }}" />