Merge pull request #21 from jr-k/feature/datetimepicker-slide-scheduler
Time and date picker for schedule feature
This commit is contained in:
commit
290b23d1fc
13
data/www/css/flatpickr.min.css
vendored
Executable file
13
data/www/css/flatpickr.min.css
vendored
Executable file
File diff suppressed because one or more lines are too long
@ -516,10 +516,14 @@ form .form-group input[type=checkbox] {
|
||||
flex: 0;
|
||||
}
|
||||
|
||||
form .form-group input[type=checkbox].trigger {
|
||||
form .form-group .trigger {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
form .form-group select.trigger {
|
||||
max-width: 120px;
|
||||
}
|
||||
|
||||
form .form-group span {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
2
data/www/js/flatpickr.min.js
vendored
Normal file
2
data/www/js/flatpickr.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -3,6 +3,36 @@ jQuery(document).ready(function ($) {
|
||||
const $tableInactive = $('table.inactive-slides');
|
||||
const $modalsRoot = $('.modals');
|
||||
|
||||
const validateCronDateTime = function(cronExpression) {
|
||||
const pattern = /^(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+\*\s+(\d+)$/;
|
||||
return pattern.test(cronExpression);
|
||||
};
|
||||
|
||||
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($el) {
|
||||
$el.val('');
|
||||
const pickr = $el.flatpickr({
|
||||
enableTime: true,
|
||||
time_24hr: true,
|
||||
allowInput: false,
|
||||
allowInvalidPreload: false,
|
||||
dateFormat: 'Y-m-d H:i',
|
||||
onChange: function(selectedDates, dateStr, instance) {
|
||||
const d = selectedDates[0];
|
||||
const $target = $el.parents('.widget:eq(0)').find('.target');
|
||||
$target.val(
|
||||
d ? `${d.getMinutes()} ${d.getHours()} ${d.getDate()} ${(d.getMonth() + 1)} * ${d.getFullYear()}` : ''
|
||||
);
|
||||
}
|
||||
});
|
||||
$el.addClass('hidden');
|
||||
};
|
||||
|
||||
const getId = function ($el) {
|
||||
return $el.is('tr') ? $el.attr('data-level') : $el.parents('tr:eq(0)').attr('data-level');
|
||||
};
|
||||
@ -68,12 +98,21 @@ jQuery(document).ready(function ($) {
|
||||
updateTable();
|
||||
});
|
||||
|
||||
$(document).on('change', '.modal-slide input[type=checkbox]', function () {
|
||||
const $target = $('#'+ $(this).attr('id').replace('-trigger', ''));
|
||||
const hide = !$(this).is(':checked');
|
||||
$target.toggleClass('hidden', hide);
|
||||
$(document).on('change', '.modal-slide select.trigger', function () {
|
||||
const $target = $(this).parents('.widget:eq(0)').find('.target');
|
||||
const $datetimepicker = $(this).parents('.widget:eq(0)').find('.datetimepicker');
|
||||
|
||||
if (hide) {
|
||||
const isDateTime = $(this).val() === 'datetime';
|
||||
const isLoop = $(this).val() === 'loop';
|
||||
const flushValue = isLoop;
|
||||
|
||||
const hideCronField = isLoop || isDateTime;
|
||||
const hideDateTimeField = !isDateTime;
|
||||
|
||||
$target.toggleClass('hidden', hideCronField);
|
||||
$datetimepicker.toggleClass('hidden', hideDateTimeField)
|
||||
|
||||
if (flushValue) {
|
||||
$target.val('');
|
||||
}
|
||||
});
|
||||
@ -99,20 +138,28 @@ jQuery(document).ready(function ($) {
|
||||
|
||||
$(document).on('click', '.slide-add', function () {
|
||||
showModal('modal-slide-add');
|
||||
loadDateTimePicker($('.modal-slide-add .datetimepicker'))
|
||||
$('.modal-slide-add input:eq(0)').focus().select();
|
||||
});
|
||||
|
||||
$(document).on('click', '.slide-edit', function () {
|
||||
const slide = JSON.parse($(this).parents('tr:eq(0)').attr('data-entity'));
|
||||
showModal('modal-slide-edit');
|
||||
loadDateTimePicker($('.modal-slide-edit .datetimepicker'))
|
||||
|
||||
const hasCron = slide.cron_schedule && slide.cron_schedule.length > 0;
|
||||
const hasDateTime = hasCron && validateCronDateTime(slide.cron_schedule);
|
||||
|
||||
$('.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(slide.location);
|
||||
$('#slide-edit-duration').val(slide.duration);
|
||||
$('#slide-edit-cron-schedule').val(slide.cron_schedule).toggleClass('hidden', !hasCron);
|
||||
$('#slide-edit-cron-schedule-trigger').prop('checked', hasCron);
|
||||
$('#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-datetimepicker').toggleClass('hidden', !hasDateTime).val(
|
||||
hasDateTime ? getCronDateTime(slide.cron_schedule) : ''
|
||||
);
|
||||
$('#slide-edit-id').val(slide.id);
|
||||
});
|
||||
|
||||
@ -137,4 +184,4 @@ jQuery(document).ready(function ($) {
|
||||
});
|
||||
|
||||
main();
|
||||
});
|
||||
});
|
||||
|
||||
@ -26,6 +26,10 @@
|
||||
"slideshow_slide_form_label_duration": "Duration",
|
||||
"slideshow_slide_form_label_duration_unit": "seconds",
|
||||
"slideshow_slide_form_label_cron_scheduled": "Scheduled",
|
||||
"slideshow_slide_form_label_cron_scheduled_loop": "In the loop",
|
||||
"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_cron": "Cron",
|
||||
"slideshow_slide_form_widget_cron_scheduled_placeholder": "Use crontab format: * * * * *",
|
||||
"slideshow_slide_form_button_cancel": "Cancel",
|
||||
"js_slideshow_slide_delete_confirmation": "Are you sure?",
|
||||
|
||||
@ -26,6 +26,10 @@
|
||||
"slideshow_slide_form_label_duration": "Durée",
|
||||
"slideshow_slide_form_label_duration_unit": "secondes",
|
||||
"slideshow_slide_form_label_cron_scheduled": "Programmer",
|
||||
"slideshow_slide_form_label_cron_scheduled_loop": "Dans la boucle",
|
||||
"slideshow_slide_form_label_cron_scheduled_datetime": "Date & Heure",
|
||||
"slideshow_slide_form_label_cron_scheduled_datetime_placeholder": "Choisir une date et un heure",
|
||||
"slideshow_slide_form_label_cron_scheduled_cron": "Cron",
|
||||
"slideshow_slide_form_widget_cron_scheduled_placeholder": "Utiliser le format crontab: * * * * *",
|
||||
"slideshow_slide_form_button_cancel": "Annuler",
|
||||
"js_slideshow_slide_delete_confirmation": "Êtes-vous sûr ?",
|
||||
|
||||
@ -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.utils import get_safe_cron_descriptor
|
||||
from src.utils import get_safe_cron_descriptor, is_validate_cron_date_time
|
||||
|
||||
|
||||
class TemplateRenderer:
|
||||
@ -29,7 +29,8 @@ class TemplateRenderer:
|
||||
VERSION=self._model_store.config().map().get('version'),
|
||||
LANG=self._model_store.variable().map().get('lang').as_string(),
|
||||
HOOK=self._render_hook,
|
||||
cron_descriptor=self.cron_descriptor
|
||||
cron_descriptor=self.cron_descriptor,
|
||||
is_validate_cron_date_time=is_validate_cron_date_time
|
||||
)
|
||||
|
||||
for hook in HookType:
|
||||
|
||||
15
src/utils.py
15
src/utils.py
@ -8,7 +8,22 @@ from cron_descriptor import ExpressionDescriptor
|
||||
from cron_descriptor.Exception import FormatException, WrongArgumentException, MissingFieldException
|
||||
|
||||
|
||||
def is_validate_cron_date_time(expression) -> bool:
|
||||
pattern = re.compile(r'^(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+\*\s+(\d+)$')
|
||||
return bool(pattern.match(expression))
|
||||
|
||||
|
||||
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):
|
||||
[minutes, hours, day, month, _, year] = expression.split(' ')
|
||||
return "{}-{}-{} at {}:{}".format(
|
||||
year,
|
||||
month.zfill(2),
|
||||
day.zfill(2),
|
||||
hours.zfill(2),
|
||||
minutes.zfill(2)
|
||||
)
|
||||
|
||||
options = {
|
||||
"expression": expression,
|
||||
"use_24hour_time_format": use_24hour_time_format
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
</title>
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="google" content="notranslate">
|
||||
<link rel="shortcut icon" href="{{ STATIC_PREFIX }}/favicon.ico">
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="{{ STATIC_PREFIX }}favicon/apple-icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="{{ STATIC_PREFIX }}favicon/apple-icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="{{ STATIC_PREFIX }}favicon/apple-icon-72x72.png">
|
||||
|
||||
@ -4,7 +4,10 @@
|
||||
<title>Obscreen</title>
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="google" content="notranslate">
|
||||
<link rel="stylesheet" href="{{ STATIC_PREFIX }}css/animate.min.css" />
|
||||
<link rel="shortcut icon" href="{{ STATIC_PREFIX }}/favicon.ico">
|
||||
{% if slide_animation_enabled.eval() %}
|
||||
<link rel="stylesheet" href="{{ STATIC_PREFIX }}css/animate.min.css" />
|
||||
{% endif %}
|
||||
<style>
|
||||
html, body { margin: 0; padding: 0; height: 100%; overflow: hidden; background-color: white; display: flex; flex-direction: row; justify-content: center; align-items: center; }
|
||||
.slide { display: flex; flex-direction: row; justify-content: center; align-items: center; background: black; }
|
||||
|
||||
@ -47,7 +47,11 @@
|
||||
{% 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 }}
|
||||
{% else %}
|
||||
⏳ {{ cron_desc }}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="error">⚠️ {{ l.slideshow_slide_panel_td_cron_scheduled_bad_cron }}</span>
|
||||
{% endif %}
|
||||
|
||||
@ -5,10 +5,12 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block add_css %}
|
||||
<link rel="stylesheet" href="{{ STATIC_PREFIX }}css/flatpickr.min.css" />
|
||||
{{ HOOK(H_SLIDESHOW_CSS) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block add_js %}
|
||||
<script src="{{ STATIC_PREFIX }}js/flatpickr.min.js"></script>
|
||||
<script src="{{ STATIC_PREFIX }}js/tablednd-fixed.js"></script>
|
||||
<script src="{{ STATIC_PREFIX }}js/slideshow.js"></script>
|
||||
<script src="{{ STATIC_PREFIX }}js/restart.js"></script>
|
||||
|
||||
@ -25,8 +25,7 @@
|
||||
<label for="slide-add-duration">{{ l.slideshow_slide_form_label_object }}</label>
|
||||
<div class="widget">
|
||||
<input type="text" name="object" id="slide-add-object-input-text" class="slide-add-object-input" />
|
||||
<input type="file" name="object" id="slide-add-object-input-upload"
|
||||
class="slide-add-object-input hidden" disabled="disabled" />
|
||||
<input type="file" name="object" id="slide-add-object-input-upload" class="slide-add-object-input hidden" disabled="disabled" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -41,8 +40,13 @@
|
||||
<div class="form-group">
|
||||
<label for="slide-add-cron-schedule">{{ l.slideshow_slide_form_label_cron_scheduled }}</label>
|
||||
<div class="widget">
|
||||
<input type="checkbox" id="slide-add-cron-schedule-trigger" class="trigger" />
|
||||
<input type="text" name="cron_schedule" id="slide-add-cron-schedule" placeholder="{{ l.slideshow_slide_form_widget_cron_scheduled_placeholder }}" class="hidden" />
|
||||
<select id="slide-add-cron-schedule-trigger" class="trigger">
|
||||
<option value="loop">{{ l.slideshow_slide_form_label_cron_scheduled_loop }}</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-add-cron-schedule-datetimepicker" class="datetimepicker" value="" placeholder="{{ l.slideshow_slide_form_label_cron_scheduled_datetime_placeholder }}" />
|
||||
<input type="text" name="cron_schedule" id="slide-add-cron-schedule" class="target hidden" placeholder="{{ l.slideshow_slide_form_widget_cron_scheduled_placeholder }}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -41,8 +41,13 @@
|
||||
<div class="form-group">
|
||||
<label for="slide-edit-cron-schedule">{{ l.slideshow_slide_form_label_cron_scheduled }}</label>
|
||||
<div class="widget">
|
||||
<input type="checkbox" id="slide-edit-cron-schedule-trigger" class="trigger" />
|
||||
<input type="text" name="cron_schedule" id="slide-edit-cron-schedule" placeholder="{{ l.slideshow_slide_form_widget_cron_scheduled_placeholder }}" class="hidden" />
|
||||
<select id="slide-edit-cron-schedule-trigger" class="trigger">
|
||||
<option value="loop">{{ l.slideshow_slide_form_label_cron_scheduled_loop }}</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-datetimepicker" class="datetimepicker" value="" placeholder="{{ l.slideshow_slide_form_label_cron_scheduled_datetime_placeholder }}" />
|
||||
<input type="text" name="cron_schedule" id="slide-edit-cron-schedule" class="target hidden" placeholder="{{ l.slideshow_slide_form_widget_cron_scheduled_placeholder }}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user