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;
|
flex: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
form .form-group input[type=checkbox].trigger {
|
form .form-group .trigger {
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
form .form-group select.trigger {
|
||||||
|
max-width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
form .form-group span {
|
form .form-group span {
|
||||||
margin-left: 10px;
|
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 $tableInactive = $('table.inactive-slides');
|
||||||
const $modalsRoot = $('.modals');
|
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) {
|
const getId = function ($el) {
|
||||||
return $el.is('tr') ? $el.attr('data-level') : $el.parents('tr:eq(0)').attr('data-level');
|
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();
|
updateTable();
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).on('change', '.modal-slide input[type=checkbox]', function () {
|
$(document).on('change', '.modal-slide select.trigger', function () {
|
||||||
const $target = $('#'+ $(this).attr('id').replace('-trigger', ''));
|
const $target = $(this).parents('.widget:eq(0)').find('.target');
|
||||||
const hide = !$(this).is(':checked');
|
const $datetimepicker = $(this).parents('.widget:eq(0)').find('.datetimepicker');
|
||||||
$target.toggleClass('hidden', hide);
|
|
||||||
|
|
||||||
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('');
|
$target.val('');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -99,20 +138,28 @@ jQuery(document).ready(function ($) {
|
|||||||
|
|
||||||
$(document).on('click', '.slide-add', function () {
|
$(document).on('click', '.slide-add', function () {
|
||||||
showModal('modal-slide-add');
|
showModal('modal-slide-add');
|
||||||
|
loadDateTimePicker($('.modal-slide-add .datetimepicker'))
|
||||||
$('.modal-slide-add input:eq(0)').focus().select();
|
$('.modal-slide-add input:eq(0)').focus().select();
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).on('click', '.slide-edit', function () {
|
$(document).on('click', '.slide-edit', function () {
|
||||||
const slide = JSON.parse($(this).parents('tr:eq(0)').attr('data-entity'));
|
const slide = JSON.parse($(this).parents('tr:eq(0)').attr('data-entity'));
|
||||||
showModal('modal-slide-edit');
|
showModal('modal-slide-edit');
|
||||||
|
loadDateTimePicker($('.modal-slide-edit .datetimepicker'))
|
||||||
|
|
||||||
const hasCron = slide.cron_schedule && slide.cron_schedule.length > 0;
|
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();
|
$('.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(slide.location);
|
$('#slide-edit-location').val(slide.location);
|
||||||
$('#slide-edit-duration').val(slide.duration);
|
$('#slide-edit-duration').val(slide.duration);
|
||||||
$('#slide-edit-cron-schedule').val(slide.cron_schedule).toggleClass('hidden', !hasCron);
|
$('#slide-edit-cron-schedule').val(slide.cron_schedule).toggleClass('hidden', !hasCron || hasDateTime);
|
||||||
$('#slide-edit-cron-schedule-trigger').prop('checked', hasCron);
|
$('#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);
|
$('#slide-edit-id').val(slide.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -26,6 +26,10 @@
|
|||||||
"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_cron_scheduled": "Scheduled",
|
"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_widget_cron_scheduled_placeholder": "Use crontab format: * * * * *",
|
||||||
"slideshow_slide_form_button_cancel": "Cancel",
|
"slideshow_slide_form_button_cancel": "Cancel",
|
||||||
"js_slideshow_slide_delete_confirmation": "Are you sure?",
|
"js_slideshow_slide_delete_confirmation": "Are you sure?",
|
||||||
|
|||||||
@ -26,6 +26,10 @@
|
|||||||
"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_cron_scheduled": "Programmer",
|
"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_widget_cron_scheduled_placeholder": "Utiliser le format crontab: * * * * *",
|
||||||
"slideshow_slide_form_button_cancel": "Annuler",
|
"slideshow_slide_form_button_cancel": "Annuler",
|
||||||
"js_slideshow_slide_delete_confirmation": "Êtes-vous sûr ?",
|
"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.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.utils import get_safe_cron_descriptor
|
from src.utils import get_safe_cron_descriptor, is_validate_cron_date_time
|
||||||
|
|
||||||
|
|
||||||
class TemplateRenderer:
|
class TemplateRenderer:
|
||||||
@ -29,7 +29,8 @@ class TemplateRenderer:
|
|||||||
VERSION=self._model_store.config().map().get('version'),
|
VERSION=self._model_store.config().map().get('version'),
|
||||||
LANG=self._model_store.variable().map().get('lang').as_string(),
|
LANG=self._model_store.variable().map().get('lang').as_string(),
|
||||||
HOOK=self._render_hook,
|
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:
|
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
|
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:
|
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 = {
|
options = {
|
||||||
"expression": expression,
|
"expression": expression,
|
||||||
"use_24hour_time_format": use_24hour_time_format
|
"use_24hour_time_format": use_24hour_time_format
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
</title>
|
</title>
|
||||||
<meta name="robots" content="noindex, nofollow">
|
<meta name="robots" content="noindex, nofollow">
|
||||||
<meta name="google" content="notranslate">
|
<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="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="60x60" href="{{ STATIC_PREFIX }}favicon/apple-icon-60x60.png">
|
||||||
<link rel="apple-touch-icon" sizes="72x72" href="{{ STATIC_PREFIX }}favicon/apple-icon-72x72.png">
|
<link rel="apple-touch-icon" sizes="72x72" href="{{ STATIC_PREFIX }}favicon/apple-icon-72x72.png">
|
||||||
|
|||||||
@ -4,7 +4,10 @@
|
|||||||
<title>Obscreen</title>
|
<title>Obscreen</title>
|
||||||
<meta name="robots" content="noindex, nofollow">
|
<meta name="robots" content="noindex, nofollow">
|
||||||
<meta name="google" content="notranslate">
|
<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>
|
<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; }
|
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; }
|
.slide { display: flex; flex-direction: row; justify-content: center; align-items: center; background: black; }
|
||||||
|
|||||||
@ -47,7 +47,11 @@
|
|||||||
{% 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) %}
|
||||||
|
📆 {{ cron_desc }}
|
||||||
|
{% else %}
|
||||||
⏳ {{ cron_desc }}
|
⏳ {{ cron_desc }}
|
||||||
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="error">⚠️ {{ l.slideshow_slide_panel_td_cron_scheduled_bad_cron }}</span>
|
<span class="error">⚠️ {{ l.slideshow_slide_panel_td_cron_scheduled_bad_cron }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -5,10 +5,12 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block add_css %}
|
{% block add_css %}
|
||||||
|
<link rel="stylesheet" href="{{ STATIC_PREFIX }}css/flatpickr.min.css" />
|
||||||
{{ HOOK(H_SLIDESHOW_CSS) }}
|
{{ HOOK(H_SLIDESHOW_CSS) }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block add_js %}
|
{% 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/tablednd-fixed.js"></script>
|
||||||
<script src="{{ STATIC_PREFIX }}js/slideshow.js"></script>
|
<script src="{{ STATIC_PREFIX }}js/slideshow.js"></script>
|
||||||
<script src="{{ STATIC_PREFIX }}js/restart.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>
|
<label for="slide-add-duration">{{ l.slideshow_slide_form_label_object }}</label>
|
||||||
<div class="widget">
|
<div class="widget">
|
||||||
<input type="text" name="object" id="slide-add-object-input-text" class="slide-add-object-input" />
|
<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"
|
<input type="file" name="object" id="slide-add-object-input-upload" class="slide-add-object-input hidden" disabled="disabled" />
|
||||||
class="slide-add-object-input hidden" disabled="disabled" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -41,8 +40,13 @@
|
|||||||
<div class="form-group">
|
<div class="form-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">
|
||||||
<input type="checkbox" id="slide-add-cron-schedule-trigger" class="trigger" />
|
<select 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" />
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -41,8 +41,13 @@
|
|||||||
<div class="form-group">
|
<div class="form-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">
|
||||||
<input type="checkbox" id="slide-edit-cron-schedule-trigger" class="trigger" />
|
<select 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" />
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user