This commit is contained in:
jr-k 2024-07-15 23:43:18 +02:00
parent e2ba157fdc
commit d3ef3c16a2
56 changed files with 1949 additions and 1610 deletions

File diff suppressed because one or more lines are too long

BIN
data/www/img/logo3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@ -105,7 +105,7 @@ jQuery(function ($) {
route = $(this).attr('data-entity-route') + '?id=' + $item.attr('data-id'); route = $(this).attr('data-entity-route') + '?id=' + $item.attr('data-id');
} }
if (confirm(l.common_are_you_sure)) { if (confirm(l.js_common_are_you_sure)) {
document.location.href = route; document.location.href = route;
} }
}); });
@ -134,8 +134,14 @@ jQuery(function ($) {
selectEpxlrLink(verticalNeighbors.above.find('.explr-link')); selectEpxlrLink(verticalNeighbors.above.find('.explr-link'));
} else if (e.key === "ArrowDown" && verticalNeighbors.below) { } else if (e.key === "ArrowDown" && verticalNeighbors.below) {
selectEpxlrLink(verticalNeighbors.below.find('.explr-link')); selectEpxlrLink(verticalNeighbors.below.find('.explr-link'));
} else if (e.key === "Backspace") {
if ($('.explr-item-delete:visible').length) {
$('.explr-item-delete:visible').click();
} }
} }
} else if (e.key.indexOf('Arrow') === 0) {
selectEpxlrLink($('.explr-dirview li:visible:eq(0)').find('.explr-link'));
}
}); });
// Explorer item selection // Explorer item selection

View File

@ -122,5 +122,17 @@ jQuery(document).ready(function ($) {
$firstInputText.focus(); $firstInputText.focus();
} }
} }
$(document).on('click', '.copy-link', function (e) {
e.preventDefault();
const $input = $('#' + $(this).attr('data-target-id'));
$input.select();
$input[0].setSelectionRange(0, 99999);
document.execCommand("copy");
if (navigator.clipboard) {
navigator.clipboard.writeText($input.val());
}
});
}); });

View File

@ -159,10 +159,8 @@
.find('ul') // hide every ul .find('ul') // hide every ul
.hide() .hide()
.end() .end()
.find('.explr-expand') // unless explicitly set to expand .find('.explr-toggler')
.show() .addClass('explr-plus '+opts.classesPlus);
.siblings('.explr-toggler')
.addClass('explr-minus '+opts.classesMinus);
} else { } else {
$tree $tree
.find('.explr-collapse') // hide every element set to collapse .find('.explr-collapse') // hide every element set to collapse

1
data/www/js/lib/qrcode.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,76 +1,24 @@
jQuery(document).ready(function ($) { jQuery(document).ready(function ($) {
const $tableActive = $('table.active-playlists');
const $tableInactive = $('table.inactive-playlists');
const getId = function ($el) {
return $el.is('tr') ? $el.attr('data-level') : $el.parents('tr:eq(0)').attr('data-level');
};
const updateTable = function () {
$('table').each(function () {
if ($(this).find('tbody tr.playlist-item:visible').length === 0) {
$(this).find('tr.empty-tr').removeClass('hidden');
} else {
$(this).find('tr.empty-tr').addClass('hidden');
}
});
};
const main = function () { const main = function () {
const qrcodeElement = document.getElementById('qrcode');
}; if (qrcodeElement) {
new QRCode(qrcodeElement, {
$(document).on('change', 'input[type=checkbox]', function () { text: qrcodeElement.attributes['data-qrcode-payload'].value,
$.ajax({ width: 128,
url: '/playlist/toggle', height: 128,
headers: {'Content-Type': 'application/json'}, colorDark: '#222',
data: JSON.stringify({id: getId($(this)), enabled: $(this).is(':checked')}), colorLight: '#fff',
method: 'POST', correctLevel: QRCode.CorrectLevel.H
}); });
const $tr = $(this).parents('tr:eq(0)').remove().clone();
if ($(this).is(':checked')) {
$tableActive.append($tr);
} else {
$tableInactive.append($tr);
} }
};
updateTable();
});
$(document).on('click', '.playlist-add', function () { $(document).on('click', '.playlist-add', function () {
showModal('modal-playlist-add'); showModal('modal-playlist-add');
$('.modal-playlist-add input:eq(0)').focus().select(); $('.modal-playlist-add input:eq(0)').focus().select();
}); });
$(document).on('click', '.playlist-edit', function () {
const playlist = JSON.parse($(this).parents('tr:eq(0)').attr('data-entity'));
showModal('modal-playlist-edit');
$('.modal-playlist-edit input:visible:eq(0)').focus().select();
$('#playlist-edit-name').val(playlist.name);
$('#playlist-edit-time-sync').val(playlist.time_sync ? '1' : '0');
$('#playlist-edit-id').val(playlist.id);
});
$(document).on('click', '.playlist-delete', function () {
if (confirm(l.js_playlist_delete_confirmation)) {
const $tr = $(this).parents('tr:eq(0)');
$.ajax({
method: 'DELETE',
url: '/playlist/delete',
headers: {'Content-Type': 'application/json'},
data: JSON.stringify({id: getId($(this))}),
success: function(data) {
$tr.remove();
updateTable();
},
error: function(data) {
$('.alert-error').html(data.responseJSON.message).removeClass('hidden');
}
});
}
});
main(); main();
}); });

View File

@ -36,7 +36,7 @@ main {
margin-right: 20px; margin-right: 20px;
.trigger { .trigger {
color: white; color: $white;
.avatar { .avatar {
width: 32px; width: 32px;

View File

@ -28,6 +28,15 @@ body, html {
align-self: stretch; align-self: stretch;
} }
.vertical {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
flex: 1;
align-self: stretch;
}
main { main {
flex: 1; flex: 1;
display: flex; display: flex;
@ -51,7 +60,7 @@ main {
border-bottom: $layoutBorder; border-bottom: $layoutBorder;
h1 { h1 {
color: white; color: $white;
font-weight: 600; font-weight: 600;
font-size: 24px; font-size: 24px;
} }
@ -63,16 +72,13 @@ main {
justify-content: flex-end; justify-content: flex-end;
align-items: center; align-items: center;
.btn,
button { button {
margin-left: 10px; margin-left: 10px;
} }
} }
} }
.alert {
}
.bottom-content { .bottom-content {
display: flex; display: flex;
flex-direction: row; flex-direction: row;

View File

@ -103,7 +103,7 @@ menu {
padding-left: 10px; padding-left: 10px;
i { i {
color: white; color: $white;
opacity: .2; opacity: .2;
background: transparent; background: transparent;
display: flex; display: flex;
@ -119,7 +119,7 @@ menu {
} }
&:after { &:after {
background: white; background: $white;
content: ""; content: "";
height: 195px; height: 195px;
left: -200px; left: -200px;
@ -156,11 +156,11 @@ menu {
} }
a { a {
color: white; color: $white;
font-weight: bold; font-weight: bold;
i { i {
color: white; color: $white;
opacity: 1; opacity: 1;
} }
} }

View File

@ -6,6 +6,13 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
border-radius: 4px; border-radius: 4px;
a {
color: inherit;
margin-left: 4px;
margin-right: 4px;
text-decoration: underline;
}
} }
.alert-info { .alert-info {

View File

@ -38,7 +38,7 @@
flex-direction: row; flex-direction: row;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
color: white; color: $white;
text-align: center; text-align: center;
padding: 0 3px; padding: 0 3px;

View File

@ -4,7 +4,7 @@ button,
$shadowOffset: 2px; $shadowOffset: 2px;
position: relative; position: relative;
padding: 10px 13px 8px 10px; padding: 10px 10px 8px 10px;
font-size: 14px; font-size: 14px;
color: #fff; color: #fff;
cursor: pointer; cursor: pointer;
@ -35,15 +35,48 @@ button,
color: #AAA; color: #AAA;
background: $neutralGrey; background: $neutralGrey;
box-shadow: 0 $shadowOffset 0 0 darken($neutralGrey, 10%); box-shadow: 0 $shadowOffset 0 0 darken($neutralGrey, 10%);
&:hover { box-shadow: 0 $shadowOffset 0 1px #222 inset; background: darken($neutralGrey, 10%); }
&:focus { background: darken($neutralGrey, 20%); } &:hover {
box-shadow: 0 $shadowOffset 0 1px #222 inset;
background: darken($neutralGrey, 10%);
}
&:focus {
background: darken($neutralGrey, 20%);
}
}
.btn-wire-neutral {
background: transparent;
border: 2px solid $neutralGrey;
color: rgba($white, .8);
box-shadow: none;
&:hover {
background: rgba($neutralGrey, 0.05);
border-color: darken($neutralGrey, 10%);
color: darken($neutralGrey, 10%);
box-shadow: none;
}
&:focus {
border-color: darken($neutralGrey, 20%);
background: transparent;
}
} }
&.btn-naked { &.btn-naked {
background: transparent; background: transparent;
box-shadow: none; box-shadow: none;
&:hover { box-shadow: 0 $shadowOffset 0 1px #222 inset; background: darken($neutralGrey, 10%); }
&:focus { background: darken($neutralGrey, 20%); } &:hover {
box-shadow: 0 $shadowOffset 0 1px #222 inset;
background: darken($neutralGrey, 10%);
}
&:focus {
background: darken($neutralGrey, 20%);
}
} }
} }

View File

@ -52,7 +52,7 @@
a { a {
padding: 8px 16px 8px 8px; padding: 8px 16px 8px 8px;
color: white; color: $white;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: flex-start; justify-content: flex-start;

View File

@ -7,3 +7,18 @@ span.empty {
padding: 2px 4px; padding: 2px 4px;
font-weight: bold; font-weight: bold;
} }
.inner-empty {
display: flex;
flex: 1;
align-self: stretch;
justify-content: center;
align-items: center;
i {
font-size: 90px;
opacity: 0.3;
text-shadow: 0 -1px #333, 0 0px .5px #444;
}
}

View File

@ -19,11 +19,11 @@ ul.explr-tree {
} }
a { a {
color: white; color: $white;
padding-right: 80px; padding-right: 80px;
&:hover { &:hover {
color: white; color: $white;
} }
&.active { &.active {

View File

@ -36,9 +36,21 @@
h3 { h3 {
align-self: stretch; align-self: stretch;
border-bottom: 1px solid $lightGrey;
padding: 15px 15px;
margin: 0; margin: 0;
font-size: 14px;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
color: white;
padding-bottom: 10px;
text-decoration: none;
&.divide {
border-top: 1px solid #222;
margin-top: 20px;
padding-top: 20px;
}
} }
} }
} }

View File

@ -118,16 +118,16 @@ table.panes {
td { td {
font-weight: bold; font-weight: bold;
color: white; color: $white;
i.icon-legend { i.icon-legend {
color: white; color: $white;
} }
span, span,
i.icon-value { i.icon-value {
background-color: rgba($white, .3); background-color: rgba($white, .3);
color: white; color: $white;
} }
&.description { &.description {

View File

@ -31,7 +31,7 @@ ul.pills {
flex-direction: row; flex-direction: row;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
color: white; color: $white;
overflow: hidden; overflow: hidden;
padding-right: 30px; padding-right: 30px;
text-align: center; text-align: center;

View File

@ -1,116 +1,66 @@
.pure-material-switch { $toggleActiveColor: $limeGreen;
z-index: 0; $containerWidth: 42px;
$containerHeight: 26px;
$containerRadius: 15px;
$containerShadowActive: 0 2px 2px #222 inset;
$containerShadowInactive: 0 2px 2px #111 inset;
$backgroundColorInactive: #222;
$backgroundColorActive: darken($toggleActiveColor, 30%);
$thumbColorActive: $toggleActiveColor;
$thumbColorInactive: #777;
$thumbWidth: 18px;
$thumbHeight: $thumbWidth;
$borderSize: 1px;
$animationSpeed: 0.2s;
.toggle {
position: relative; position: relative;
display: inline-block; display: flex;
} flex-direction: row;
justify-content: flex-start;
align-items: center;
.pure-material-switch > input { input {
appearance: none; display: none;
-moz-appearance: none;
-webkit-appearance: none; &:checked + label {
z-index: -1; background: darken($thumbColorActive, 30%);
position: absolute; border: $borderSize solid rgba($white, .1);
right: 6px; box-shadow: $containerShadowActive;
top: -8px;
&::after {
content: "";
display: block; display: block;
margin: 0;
border-radius: 50%; border-radius: 50%;
width: 40px; margin-left: calc($containerWidth - 21px);
height: 40px; width: $thumbWidth;
background-color: rgba($black, 0.38); height: $thumbHeight;
outline: none; transition: $animationSpeed;
opacity: 0; background: $thumbColorActive;
transform: scale(1); box-shadow: 0 2px darken($thumbColorActive, 40%);
pointer-events: none; }
transition: opacity 0.3s 0.1s, transform 0.2s 0.1s; }
} }
.pure-material-switch > span { label {
display: inline-block; width: $containerWidth + ($borderSize * 2);
width: 100%; height: $containerHeight;
border-radius: $containerRadius;
background: $backgroundColorInactive;
cursor: pointer; cursor: pointer;
} border: $borderSize solid rgba($white, .1);
box-shadow: $containerShadowInactive;
.pure-material-switch > span::before { &::after {
content: ""; content: "";
float: right; display: block;
display: inline-block;
margin: 5px 0 5px 10px;
border-radius: 7px;
width: 36px;
height: 14px;
background-color: rgba($black, 0.38);
vertical-align: top;
transition: background-color 0.2s, opacity 0.2s;
}
.pure-material-switch > span::after {
content: "";
position: absolute;
top: 2px;
right: 16px;
border-radius: 50%; border-radius: 50%;
width: 20px; width: $thumbWidth;
height: 20px; height: $thumbHeight;
background-color: $white; margin: 3px;
box-shadow: 0 3px 1px -2px rgba($black, 0.2), 0 2px 2px 0 rgba($black, 0.14), 0 1px 5px 0 rgba($black, 0.12); background: $thumbColorInactive;
transition: background-color 0.2s, transform 0.2s; box-shadow: 0 2px rgba(0,0,0,0.9);
transition: $animationSpeed;
} }
.pure-material-switch > input:checked {
right: -10px;
background-color: $limeGreen;
} }
.pure-material-switch > input:checked + span::before {
background-color: rgba($limeGreen, 0.6);
}
.pure-material-switch > input:checked + span::after {
background-color: $limeGreen;
transform: translateX(16px);
}
.pure-material-switch:hover > input {
opacity: 0.04;
}
.pure-material-switch > input:focus {
opacity: 0.12;
}
.pure-material-switch:hover > input:focus {
opacity: 0.16;
}
.pure-material-switch > input:active {
opacity: 1;
transform: scale(0);
transition: transform 0s, opacity 0s;
}
.pure-material-switch > input:active + span::before {
background-color: rgba($limeGreen, 0.6);
}
.pure-material-switch > input:checked:active + span::before {
background-color: rgba($black, 0.38);
}
.pure-material-switch > input:disabled {
opacity: 0;
}
.pure-material-switch > input:disabled + span {
color: $black;
opacity: 0.38;
cursor: default;
}
.pure-material-switch > input:disabled + span::before {
background-color: rgba($black, 0.38);
}
.pure-material-switch > input:checked:disabled + span::before {
background-color: rgba($limeGreen, 0.6);
} }

View File

@ -0,0 +1,117 @@
.tiles {
flex: 1;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: flex-start;
align-self: stretch;
.tiles-inner {
display: flex;
flex: 1;
flex-direction: column;
flex-wrap: nowrap;
justify-content: flex-start;
align-items: flex-start;
align-self: stretch;
padding: 2px;
.tiles-empty {
}
.tile-item {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
background: #222;
align-self: stretch;
color: $white;
margin: 1px;
padding: 15px;
&:hover,
&.active {
background: #111;
&:hover {
opacity: 1;
}
}
&.disabled {
.tile-header {
.head-icon {
i {
color: #222;
}
}
}
}
.tile-header {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
align-self: stretch;
.head-icon {
flex: 1;
i {
font-size: 6px;
color: white;
opacity: .8;
display: flex;
}
}
.status-icons {
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
i {
font-size: 16px;
margin-left: 10px;
}
}
}
.tile-body {
flex: 1;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
font-size: 15px;
font-weight: normal;
letter-spacing: 0.8px;
line-height: 22px;
margin: 0 0 0 10px;
flex-wrap: nowrap;
}
.tile-footer {
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
margin: 0;
.foot-span {
opacity: .8;
font-size: 13px;
font-family: "Courier New";
}
}
}
}
}

View File

@ -1,10 +1,10 @@
.form-holder { .form-holder {
min-width: 686px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: flex-start; justify-content: flex-start;
align-items: flex-start; align-items: flex-start;
align-self: stretch; align-self: stretch;
flex: 1;
form { form {
max-width: 434px; max-width: 434px;
@ -24,6 +24,7 @@ form {
font-size: 14px; font-size: 14px;
margin: 0 0 25px 0; margin: 0 0 25px 0;
} }
}
.form-group { .form-group {
display: flex; display: flex;
@ -47,7 +48,7 @@ form {
color: #666666; color: #666666;
&.btn-upload { &.btn-upload {
color: white; color: $white;
font-size: 14px; font-size: 14px;
flex: 0; flex: 0;
flex-basis: auto; flex-basis: auto;
@ -169,6 +170,21 @@ form {
} }
} }
} }
&.form-group-horizontal {
margin: 10px 0 20px 0;
flex-direction: row;
justify-content: flex-start;
align-items: center;
.widget {
margin: 0;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
}
}
} }
.actions { .actions {
@ -191,5 +207,13 @@ form {
margin-right: 25px; margin-right: 25px;
} }
} }
&.actions-center {
justify-content: center;
button {
margin-left: 0;
margin-right: 0;
}
} }
} }

View File

@ -84,5 +84,5 @@ header nav ul li a {
header nav ul li a:hover, header nav ul li a:hover,
header nav ul li.active a { header nav ul li.active a {
color: white; color: $white;
} }

View File

@ -23,8 +23,9 @@
// Legacy // Legacy
@import 'components/panes'; @import 'components/panes';
@import 'components/tiles';
@import 'components/empty'; @import 'components/empty';
//@import 'components/switches'; @import 'components/switches';
//@import 'components/cards'; //@import 'components/cards';
//@import 'components/badges'; //@import 'components/badges';

View File

@ -19,7 +19,7 @@
} }
.page-panel.right-panel { .page-panel.right-panel {
flex: 1; flex: 2;
align-self: stretch; align-self: stretch;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -29,7 +29,7 @@
padding: 20px; padding: 20px;
h3 { h3 {
color: white; color: $white;
padding: 10px 10px 10px 0; padding: 10px 10px 10px 0;
margin-bottom: 20px; margin-bottom: 20px;
font-size: 16px; font-size: 16px;

View File

@ -1,28 +1,155 @@
.view-playlist-list main .main-container { .view-playlist-list main .main-container {
.modal-playlist-qrcode {
h2 {
text-align: center;
} }
.view-playlist-edit main .main-container { .qrcode-pic {
text-align: center;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
img {
border: 4px solid #555;
border-radius: $baseRadius;
}
}
}
.bottom-content { .bottom-content {
.page-content { .page-content {
flex: 1; flex: 1;
&.with-right-panel {
flex: 0.5;
}
.inner {
padding: 0;
h3 {
font-size: 16px;
font-weight: 500;
color: #DDD;
text-decoration: none;
margin: 0 0 20px 0;
}
.form-holder { .form-holder {
margin: 20px 10px 20px 20px;
border-right: 1px solid #222;
padding-right: 20px;
flex: 1.3;
form {
max-width: initial;
}
.form-group {
flex-grow: 0;
}
}
.preview-holder {
margin: 20px 20px 20px 10px; margin: 20px 20px 20px 10px;
flex: 1;
.form-group {
flex-grow: 0;
margin-bottom: 0;
.widget {
a,
.btn {
margin-left: 10px;
}
input[type=text] {
border: none;
background: #000;
border-radius: $baseRadius;
}
} }
} }
.page-panel.right-panel { h4 {
font-size: 14px;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
align-self: stretch;
color: white;
padding-bottom: 10px;
text-decoration: none;
&.divide {
border-top: 1px solid #222;
margin-top: 20px;
padding-top: 20px;
}
}
p {
font-size: 12px;
line-height: 18px;
display: flex;
margin-bottom: 5px;
flex-direction: row;
justify-content: flex-start;
align-items: center;
align-self: stretch;
color: #666666;
}
.qrcode-pic {
margin-top: 10px;
img {
border: 1px dashed #555;
padding: 5px;
border-radius: $baseRadius;
}
}
.preview {
background: black;
border: 1px solid rgba($white, .3);
border-radius: $baseRadius;
justify-content: center;
align-items: center;
align-self: stretch;
display: flex;
margin: 10px 0 20px 0;
height: 300px;
iframe {
flex: 1;
align-self: stretch;
}
}
}
.slides-holder {
margin-top: 40px;
border-top: 1px solid #222;
align-self: stretch;
padding-top: 20px;
}
}
}
.page-panel.left-panel {
flex: 0.3;
max-width: initial;
justify-content: center;
align-items: center;
display: flex;
} }
} }
} }

View File

@ -58,6 +58,29 @@
background: darken($color, 20%); background: darken($color, 20%);
} }
} }
&.btn-wire-#{"#{$name}"} {
background: transparent;
box-shadow: none;
border: 2px solid $color;
color: rgba($white, .8);
i.btn-match {
color: $color;
}
&:hover {
background: rgba($color, 0.05);
border-color: darken($color, 10%);
color: darken($color, 10%);
box-shadow: none;
}
&:focus {
border-color: darken($color, 20%);
background: transparent;
}
}
} }
} }
} }

View File

@ -68,8 +68,12 @@
"js_slideshow_content_delete_confirmation": "Are you sure?", "js_slideshow_content_delete_confirmation": "Are you sure?",
"playlist_page_title": "Playlists", "playlist_page_title": "Playlists",
"playlist_button_add": "Add a playlist", "playlist_button_add": "Add Playlist",
"playlist_panel_active": "Active playlists", "playlist_button_delete": "Delete Playlist",
"playlist_panel_about_playlist": "About playlist",
"playlist_panel_content_management": "Content management",
"playlist_panel_preview": "Playlist preview",
"playlist_panel_preview_action": "Preview",
"playlist_panel_inactive": "Inactive playlists", "playlist_panel_inactive": "Inactive playlists",
"playlist_panel_empty": "Currently, there are no playlists. %link% now.", "playlist_panel_empty": "Currently, there are no playlists. %link% now.",
"playlist_panel_th_name": "Name", "playlist_panel_th_name": "Name",
@ -78,10 +82,12 @@
"playlist_panel_th_activity": "Options", "playlist_panel_th_activity": "Options",
"playlist_form_add_title": "Add Playlist", "playlist_form_add_title": "Add Playlist",
"playlist_form_add_submit": "Add", "playlist_form_add_submit": "Add",
"playlist_form_edit_title": "Edit Playlist", "playlist_form_preview_url_desc": "You can use this link to play this playlist on any browser you want. Use copy button to get that in your clipboard.",
"playlist_form_edit_submit": "Save", "playlist_form_preview_qrcode_desc": "You can easily access your playlist using a tablet or phone. Just scan the QR code to begin.",
"playlist_form_preview_iframe_desc": "You can view the playlist without leaving this screen by starting the preview from here.",
"playlist_form_label_name": "Enter playlist name", "playlist_form_label_name": "Enter playlist name",
"playlist_form_label_time_sync": "Sync slides across players", "playlist_form_label_time_sync": "Sync slides across players",
"playlist_form_label_enabled": "Enable/Disable playlist",
"playlist_form_button_cancel": "Cancel", "playlist_form_button_cancel": "Cancel",
"js_playlist_delete_confirmation": "Are you sure?", "js_playlist_delete_confirmation": "Are you sure?",
"playlist_delete_has_slides": "Playlist has slides, please remove them before and retry", "playlist_delete_has_slides": "Playlist has slides, please remove them before and retry",
@ -165,7 +171,7 @@
"settings_variable_desc_edition_auth_enabled": "Default user credentials will be admin/admin", "settings_variable_desc_edition_auth_enabled": "Default user credentials will be admin/admin",
"settings_variable_desc_external_url": "External url (i.e: https://studio-01.company.com or http://10.10.3.100)", "settings_variable_desc_external_url": "External url (i.e: https://studio-01.company.com or http://10.10.3.100)",
"settings_variable_desc_slide_upload_limit": "Slide upload limit (in megabytes)", "settings_variable_desc_slide_upload_limit": "Slide upload limit (in megabytes)",
"settings_variable_desc_default_slide_duration": "Introduction slide duration (in seconds)", "settings_variable_desc_intro_slide_duration": "Introduction slide duration (in seconds)",
"settings_variable_desc_default_slide_time_with_seconds": "Show the seconds on the clock in the introduction slide", "settings_variable_desc_default_slide_time_with_seconds": "Show the seconds on the clock in the introduction slide",
"settings_variable_desc_polling_interval": "Refresh interval applied for settings to the player (in seconds)", "settings_variable_desc_polling_interval": "Refresh interval applied for settings to the player (in seconds)",
"settings_variable_desc_playlist_default_time_sync": "Sync slides across players for default playlist", "settings_variable_desc_playlist_default_time_sync": "Sync slides across players for default playlist",
@ -175,6 +181,7 @@
"settings_variable_desc_slide_animation_exit_effect": "Slide animation exit effect (generally better off without it)", "settings_variable_desc_slide_animation_exit_effect": "Slide animation exit effect (generally better off without it)",
"settings_variable_desc_slide_animation_speed": "Slide animation speed", "settings_variable_desc_slide_animation_speed": "Slide animation speed",
"settings_variable_desc_ro_start_counter": "Start counter",
"settings_variable_desc_ro_last_folder_content": "Current folder in content explorer", "settings_variable_desc_ro_last_folder_content": "Current folder in content explorer",
"settings_variable_desc_ro_last_folder_node_player": "Current folder in player explorer", "settings_variable_desc_ro_last_folder_node_player": "Current folder in player explorer",
"settings_variable_desc_ro_editable": "Last application reboot datetime", "settings_variable_desc_ro_editable": "Last application reboot datetime",
@ -214,6 +221,7 @@
"basic_month_11": "November", "basic_month_11": "November",
"basic_month_12": "December", "basic_month_12": "December",
"common_untitled": "<untitled>",
"common_loading": "Loading...", "common_loading": "Loading...",
"common_default_node_player_group": "Default Playgroup", "common_default_node_player_group": "Default Playgroup",
"common_default_playlist": "Default Playlist", "common_default_playlist": "Default Playlist",

View File

@ -1,6 +1,5 @@
{ {
"dynmenu_content": "Contenido", "dynmenu_content": "Contenido",
"slideshow_slide_page_title": "Descripción General del Programa", "slideshow_slide_page_title": "Descripción General del Programa",
"slideshow_slide_goto_player": "Ir al reproductor", "slideshow_slide_goto_player": "Ir al reproductor",
"slideshow_slide_refresh_player": "Actualizar reproductor", "slideshow_slide_refresh_player": "Actualizar reproductor",
@ -46,7 +45,6 @@
"slideshow_slide_form_widget_cron_scheduled_placeholder": "Usar formato crontab: * * * * *", "slideshow_slide_form_widget_cron_scheduled_placeholder": "Usar formato crontab: * * * * *",
"slideshow_slide_form_button_cancel": "Cancelar", "slideshow_slide_form_button_cancel": "Cancelar",
"js_slideshow_slide_delete_confirmation": "¿Estás seguro?", "js_slideshow_slide_delete_confirmation": "¿Estás seguro?",
"slideshow_content_page_title": "Biblioteca de contenidos", "slideshow_content_page_title": "Biblioteca de contenidos",
"slideshow_content_button_add": "Nuevo Contenido", "slideshow_content_button_add": "Nuevo Contenido",
"slideshow_content_referenced_in_slide_error": "Se hace referencia al contenido en una diapositiva; elimine la diapositiva primero", "slideshow_content_referenced_in_slide_error": "Se hace referencia al contenido en una diapositiva; elimine la diapositiva primero",
@ -66,10 +64,13 @@
"slideshow_content_form_button_upload": "Subir un archivo", "slideshow_content_form_button_upload": "Subir un archivo",
"slideshow_content_form_button_upload_choosen": "No hay archivos seleccionados", "slideshow_content_form_button_upload_choosen": "No hay archivos seleccionados",
"js_slideshow_content_delete_confirmation": "¿Estás seguro?", "js_slideshow_content_delete_confirmation": "¿Estás seguro?",
"playlist_page_title": "Playlists",
"playlist_page_title": "Playlist", "playlist_button_add": "Agregar Playlist",
"playlist_button_add": "Agregar una lista de reproducción", "playlist_button_delete": "Eliminar Playlist",
"playlist_panel_active": "Playlist activas", "playlist_panel_about_playlist": "Acerca de la playlist",
"playlist_panel_content_management": "Gestión de contenido",
"playlist_panel_preview": "Vista previa de la playlist",
"playlist_panel_preview_action": "Avance",
"playlist_panel_inactive": "Playlist inactivas", "playlist_panel_inactive": "Playlist inactivas",
"playlist_panel_empty": "Actualmente, no hay playlist. %link% ahora.", "playlist_panel_empty": "Actualmente, no hay playlist. %link% ahora.",
"playlist_panel_th_name": "Nombre", "playlist_panel_th_name": "Nombre",
@ -78,15 +79,16 @@
"playlist_panel_th_activity": "Opciones", "playlist_panel_th_activity": "Opciones",
"playlist_form_add_title": "Agregar Playlist", "playlist_form_add_title": "Agregar Playlist",
"playlist_form_add_submit": "Agregar", "playlist_form_add_submit": "Agregar",
"playlist_form_edit_title": "Editar Playlist", "playlist_form_preview_url_desc": "Puedes usar este enlace para reproducir esta playlist en cualquier navegador que desees. Usa el botón Copiar para guardarla en tu portapapeles.",
"playlist_form_edit_submit": "Guardar", "playlist_form_preview_qrcode_desc": "Puedes acceder fácilmente a tu playlist usando una tableta o un teléfono. Simplemente escanea el código QR para comenzar.",
"playlist_form_preview_iframe_desc": "Puedes ver la playlist sin salir de esta pantalla iniciando la vista previa desde aquí.",
"playlist_form_label_name": "Introduce el nombre de la playlist", "playlist_form_label_name": "Introduce el nombre de la playlist",
"playlist_form_label_time_sync": "Sincronizar diapositivas entre reproductores", "playlist_form_label_time_sync": "Sincronizar diapositivas entre reproductores",
"playlist_form_label_enabled": "Activar/Desactivar playlist",
"playlist_form_button_cancel": "Cancelar", "playlist_form_button_cancel": "Cancelar",
"js_playlist_delete_confirmation": "¿Estás seguro?", "js_playlist_delete_confirmation": "¿Estás seguro?",
"playlist_delete_has_slides": "La lista de reproducción tiene diapositivas, por favor elimínelas antes y reintente", "playlist_delete_has_slides": "La playlist tiene diapositivas, por favor elimínelas antes y reintente",
"playlist_delete_has_node_player_groups": "La lista de reproducción está asignada a un playgroup", "playlist_delete_has_node_player_groups": "La playlist está asignada a un playgroup",
"fleet_node_player_page_title": "Reproductores", "fleet_node_player_page_title": "Reproductores",
"fleet_node_player_button_add": "Agregar un reproductor", "fleet_node_player_button_add": "Agregar un reproductor",
"fleet_node_player_panel_active": "Reproductores activos", "fleet_node_player_panel_active": "Reproductores activos",
@ -107,7 +109,6 @@
"fleet_node_player_form_label_operating_system": "OS", "fleet_node_player_form_label_operating_system": "OS",
"fleet_node_player_form_button_cancel": "Cancelar", "fleet_node_player_form_button_cancel": "Cancelar",
"js_fleet_node_player_delete_confirmation": "¿Estás seguro?", "js_fleet_node_player_delete_confirmation": "¿Estás seguro?",
"fleet_node_player_group_page_title": "Playgroups", "fleet_node_player_group_page_title": "Playgroups",
"fleet_node_player_group_button_add": "Agregar Playgroup", "fleet_node_player_group_button_add": "Agregar Playgroup",
"fleet_node_player_group_panel_active": "Playgroup activos", "fleet_node_player_group_panel_active": "Playgroup activos",
@ -124,7 +125,6 @@
"fleet_node_player_group_form_button_cancel": "Cancelar", "fleet_node_player_group_form_button_cancel": "Cancelar",
"js_fleet_node_player_group_delete_confirmation": "¿Estás seguro?", "js_fleet_node_player_group_delete_confirmation": "¿Estás seguro?",
"node_player_group_delete_has_node_player": "El playgroup tiene reproductores, por favor elimínelos o desasígnelos antes y reintente", "node_player_group_delete_has_node_player": "El playgroup tiene reproductores, por favor elimínelos o desasígnelos antes y reintente",
"login_page_title": "Iniciar Sesión", "login_page_title": "Iniciar Sesión",
"auth_page_title": "Usuarios", "auth_page_title": "Usuarios",
"auth_user_button_add": "Agregar un usuario", "auth_user_button_add": "Agregar un usuario",
@ -143,7 +143,6 @@
"auth_user_form_button_cancel": "Cancelar", "auth_user_form_button_cancel": "Cancelar",
"auth_user_delete_at_least_one_account": "Debe tener al menos un usuario activo mientras usa la función de autenticación", "auth_user_delete_at_least_one_account": "Debe tener al menos un usuario activo mientras usa la función de autenticación",
"js_auth_user_delete_confirmation": "¿Estás seguro?", "js_auth_user_delete_confirmation": "¿Estás seguro?",
"settings_page_title": "Configuración", "settings_page_title": "Configuración",
"settings_plugin_page_title": "Plugins", "settings_plugin_page_title": "Plugins",
"settings_variable_panel_system_variables": "Configuración general", "settings_variable_panel_system_variables": "Configuración general",
@ -165,22 +164,20 @@
"settings_variable_desc_edition_auth_enabled": "Las credenciales predeterminadas del usuario serán admin/admin", "settings_variable_desc_edition_auth_enabled": "Las credenciales predeterminadas del usuario serán admin/admin",
"settings_variable_desc_external_url": "URL externa (ej.: https://studio-01.company.com o http://10.10.3.100)", "settings_variable_desc_external_url": "URL externa (ej.: https://studio-01.company.com o http://10.10.3.100)",
"settings_variable_desc_slide_upload_limit": "Límite de carga de diapositivas (en megabytes)", "settings_variable_desc_slide_upload_limit": "Límite de carga de diapositivas (en megabytes)",
"settings_variable_desc_default_slide_duration": "Duración de la diapositiva de introducción (en segundos)", "settings_variable_desc_intro_slide_duration": "Duración de la diapositiva de introducción (en segundos)",
"settings_variable_desc_default_slide_time_with_seconds": "Mostrar los segundos en el reloj de la diapositiva de introducción", "settings_variable_desc_default_slide_time_with_seconds": "Mostrar los segundos en el reloj de la diapositiva de introducción",
"settings_variable_desc_polling_interval": "Intervalo de actualización aplicado para configuraciones del reproductor (en segundos)", "settings_variable_desc_polling_interval": "Intervalo de actualización aplicado para configuraciones del reproductor (en segundos)",
"settings_variable_desc_playlist_default_time_sync": "Sincronizar diapositivas entre reproductores para la lista de reproducción predeterminada", "settings_variable_desc_playlist_default_time_sync": "Sincronizar diapositivas entre reproductores para la playlist predeterminada",
"settings_variable_desc_slide_animation_enabled": "Habilitar efecto de animación entre diapositivas", "settings_variable_desc_slide_animation_enabled": "Habilitar efecto de animación entre diapositivas",
"settings_variable_desc_slide_animation_entrance_effect": "Efecto de entrada de animación de diapositiva", "settings_variable_desc_slide_animation_entrance_effect": "Efecto de entrada de animación de diapositiva",
"settings_variable_desc_slide_animation_exit_effect": "Efecto de salida de animación de diapositiva (generalmente mejor sin él)", "settings_variable_desc_slide_animation_exit_effect": "Efecto de salida de animación de diapositiva (generalmente mejor sin él)",
"settings_variable_desc_slide_animation_speed": "Velocidad de animación de diapositiva", "settings_variable_desc_slide_animation_speed": "Velocidad de animación de diapositiva",
"settings_variable_desc_ro_start_counter": "Contador de inicio",
"settings_variable_desc_ro_last_folder_content": "Carpeta actual en el explorador de contenidos", "settings_variable_desc_ro_last_folder_content": "Carpeta actual en el explorador de contenidos",
"settings_variable_desc_ro_last_folder_node_player": "Carpeta actual en el explorador del reproductor", "settings_variable_desc_ro_last_folder_node_player": "Carpeta actual en el explorador del reproductor",
"settings_variable_desc_ro_editable": "Fecha y hora del último reinicio de la aplicación", "settings_variable_desc_ro_editable": "Fecha y hora del último reinicio de la aplicación",
"settings_variable_desc_ro_last_slide_update": "Fecha y hora de la última actualización de diapositiva", "settings_variable_desc_ro_last_slide_update": "Fecha y hora de la última actualización de diapositiva",
"settings_variable_desc_ro_refresh_player_request": "Fecha y hora de la última solicitud de actualización del reproductor", "settings_variable_desc_ro_refresh_player_request": "Fecha y hora de la última solicitud de actualización del reproductor",
"sysinfo_page_title": "Información del sistema", "sysinfo_page_title": "Información del sistema",
"sysinfo_panel_button_restart": "Reiniciar", "sysinfo_panel_button_restart": "Reiniciar",
"sysinfo_panel_table_section_system": "Sistema", "sysinfo_panel_table_section_system": "Sistema",
@ -193,7 +190,6 @@
"logs_panel_last_logs": "Registros (últimas 100 líneas)", "logs_panel_last_logs": "Registros (últimas 100 líneas)",
"js_sysinfo_restart_confirmation": "¿Estás seguro?", "js_sysinfo_restart_confirmation": "¿Estás seguro?",
"js_sysinfo_restart_loading": "Reiniciando, por favor espera...", "js_sysinfo_restart_loading": "Reiniciando, por favor espera...",
"basic_day_1": "Lunes", "basic_day_1": "Lunes",
"basic_day_2": "Martes", "basic_day_2": "Martes",
"basic_day_3": "Miércoles", "basic_day_3": "Miércoles",
@ -213,7 +209,7 @@
"basic_month_10": "Octubre", "basic_month_10": "Octubre",
"basic_month_11": "Noviembre", "basic_month_11": "Noviembre",
"basic_month_12": "Diciembre", "basic_month_12": "Diciembre",
"common_untitled": "<sin-título>",
"common_loading": "Cargando...", "common_loading": "Cargando...",
"common_default_node_player_group": "Playgroup predeterminado", "common_default_node_player_group": "Playgroup predeterminado",
"common_default_playlist": "Lista de reproducción predeterminada", "common_default_playlist": "Lista de reproducción predeterminada",
@ -245,7 +241,6 @@
"updated_by": "Última actualización por", "updated_by": "Última actualización por",
"close": "Cerrar", "close": "Cerrar",
"anonymous": "Anónimo", "anonymous": "Anónimo",
"enum_animation_speed_slower": "Más lento", "enum_animation_speed_slower": "Más lento",
"enum_animation_speed_slow": "Lento", "enum_animation_speed_slow": "Lento",
"enum_animation_speed_normal": "Normal", "enum_animation_speed_normal": "Normal",
@ -279,7 +274,6 @@
"enum_operating_system_redhat": "RedHat", "enum_operating_system_redhat": "RedHat",
"enum_operating_system_centos": "CentOS", "enum_operating_system_centos": "CentOS",
"enum_operating_system_other": "Otro", "enum_operating_system_other": "Otro",
"sysinfo_rpi_model": "Modelo de Raspberry Pi", "sysinfo_rpi_model": "Modelo de Raspberry Pi",
"sysinfo_rpi_model_unknown": "No es una Raspberry Pi o la información del modelo no está disponible", "sysinfo_rpi_model_unknown": "No es una Raspberry Pi o la información del modelo no está disponible",
"sysinfo_storage_free_space": "Espacio de almacenamiento libre", "sysinfo_storage_free_space": "Espacio de almacenamiento libre",
@ -289,6 +283,5 @@
"sysinfo_network_interface": "Interfaz de red", "sysinfo_network_interface": "Interfaz de red",
"sysinfo_mac_address": "Dirección MAC", "sysinfo_mac_address": "Dirección MAC",
"sysinfo_ip_address": "Dirección IP", "sysinfo_ip_address": "Dirección IP",
"player_default_welcome_message": "Para gestionar este reproductor, ve a un navegador en %link%" "player_default_welcome_message": "Para gestionar este reproductor, ve a un navegador en %link%"
} }

View File

@ -1,6 +1,5 @@
{ {
"dynmenu_content": "Contenu", "dynmenu_content": "Contenu",
"slideshow_slide_page_title": "Vue Planning", "slideshow_slide_page_title": "Vue Planning",
"slideshow_slide_goto_player": "Voir le lecteur", "slideshow_slide_goto_player": "Voir le lecteur",
"slideshow_slide_refresh_player": "Rafraîchir le lecteur", "slideshow_slide_refresh_player": "Rafraîchir le lecteur",
@ -46,7 +45,6 @@
"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 ?",
"slideshow_content_page_title": "Bibliothèque de contenus", "slideshow_content_page_title": "Bibliothèque de contenus",
"slideshow_content_button_add": "Nouveau Contenu", "slideshow_content_button_add": "Nouveau Contenu",
"slideshow_content_referenced_in_slide_error": "Le contenu est référencé dans une slide, supprimez d'abord la slide", "slideshow_content_referenced_in_slide_error": "Le contenu est référencé dans une slide, supprimez d'abord la slide",
@ -66,10 +64,13 @@
"slideshow_content_form_button_upload": "Uploader un fichier", "slideshow_content_form_button_upload": "Uploader un fichier",
"slideshow_content_form_button_upload_choosen": "Aucun fichier sélectionné", "slideshow_content_form_button_upload_choosen": "Aucun fichier sélectionné",
"js_slideshow_content_delete_confirmation": "Êtes-vous sûr ?", "js_slideshow_content_delete_confirmation": "Êtes-vous sûr ?",
"playlist_page_title": "Playlists",
"playlist_page_title": "Playlist", "playlist_button_add": "Ajouter Playlist",
"playlist_button_add": "Ajouter une playlist", "playlist_button_delete": "Supprimer Playlist",
"playlist_panel_active": "Playlist actives", "playlist_panel_about_playlist": "À propos de la playlist",
"playlist_panel_content_management": "Elements de la playlist",
"playlist_panel_preview": "Playlist preview",
"playlist_panel_preview_action": "Prévisualiser",
"playlist_panel_inactive": "Playlist inactives", "playlist_panel_inactive": "Playlist inactives",
"playlist_panel_empty": "Actuellement, il n'y a pas de playlist. %link% maintenant.", "playlist_panel_empty": "Actuellement, il n'y a pas de playlist. %link% maintenant.",
"playlist_panel_th_name": "Nom", "playlist_panel_th_name": "Nom",
@ -78,15 +79,17 @@
"playlist_panel_th_activity": "Options", "playlist_panel_th_activity": "Options",
"playlist_form_add_title": "Ajout d'une Playlist", "playlist_form_add_title": "Ajout d'une Playlist",
"playlist_form_add_submit": "Ajouter", "playlist_form_add_submit": "Ajouter",
"playlist_form_edit_title": "Modification d'une Playlist", "playlist_form_preview_url_desc": "Vous pouvez utiliser ce lien pour lire cette playlist sur n'importe quel navigateur de votre choix. Utilisez le bouton Copier pour l'obtenir dans votre presse-papiers.",
"playlist_form_preview_qrcode_desc": "Vous pouvez facilement accéder à votre playlist à l'aide d'une tablette ou d'un téléphone. Scannez simplement le code QR pour commencer.",
"playlist_form_preview_iframe_desc": "Vous pouvez visualiser la playlist sans quitter cet écran en démarrant l'aperçu à partir d'ici.",
"playlist_form_edit_submit": "Enregistrer", "playlist_form_edit_submit": "Enregistrer",
"playlist_form_label_name": "Entrez le nom de la playlist", "playlist_form_label_name": "Entrez le nom de la playlist",
"playlist_form_label_time_sync": "Synchroniser les slides des lecteurs", "playlist_form_label_time_sync": "Synchroniser les slides des lecteurs",
"playlist_form_label_enabled": "Activer/Désactiver la playlist",
"playlist_form_button_cancel": "Annuler", "playlist_form_button_cancel": "Annuler",
"js_playlist_delete_confirmation": "Êtes-vous sûr ?", "js_playlist_delete_confirmation": "Êtes-vous sûr ?",
"playlist_delete_has_slides": "La playlist contient des slides, supprimez-les avant et réessayez", "playlist_delete_has_slides": "La playlist contient des slides, supprimez-les avant et réessayez",
"playlist_delete_has_node_player_groups": "La playlist est attribuée à un playgroup", "playlist_delete_has_node_player_groups": "La playlist est attribuée à un playgroup",
"fleet_node_player_page_title": "Lecteurs", "fleet_node_player_page_title": "Lecteurs",
"fleet_node_player_button_add": "Ajouter un lecteur", "fleet_node_player_button_add": "Ajouter un lecteur",
"fleet_node_player_panel_active": "Players actifs", "fleet_node_player_panel_active": "Players actifs",
@ -107,7 +110,6 @@
"fleet_node_player_form_label_operating_system": "OS", "fleet_node_player_form_label_operating_system": "OS",
"fleet_node_player_form_button_cancel": "Annuler", "fleet_node_player_form_button_cancel": "Annuler",
"js_fleet_node_player_delete_confirmation": "Êtes-vous sûr ?", "js_fleet_node_player_delete_confirmation": "Êtes-vous sûr ?",
"fleet_node_player_group_page_title": "Playgroups", "fleet_node_player_group_page_title": "Playgroups",
"fleet_node_player_group_button_add": "Ajouter un Playgroup", "fleet_node_player_group_button_add": "Ajouter un Playgroup",
"fleet_node_player_group_panel_active": "Playgroups", "fleet_node_player_group_panel_active": "Playgroups",
@ -124,7 +126,6 @@
"fleet_node_player_group_form_button_cancel": "Annuler", "fleet_node_player_group_form_button_cancel": "Annuler",
"js_fleet_node_player_group_delete_confirmation": "Êtes-vous sûr ?", "js_fleet_node_player_group_delete_confirmation": "Êtes-vous sûr ?",
"node_player_group_delete_has_node_player": "Le playgroup a des lecteurs, supprimez-les ou réassignez-les avant de le supprimer", "node_player_group_delete_has_node_player": "Le playgroup a des lecteurs, supprimez-les ou réassignez-les avant de le supprimer",
"login_page_title": "Connexion", "login_page_title": "Connexion",
"auth_page_title": "Utilisateurs", "auth_page_title": "Utilisateurs",
"auth_user_button_add": "Ajouter un utilisateur", "auth_user_button_add": "Ajouter un utilisateur",
@ -143,7 +144,6 @@
"auth_user_form_button_cancel": "Annuler", "auth_user_form_button_cancel": "Annuler",
"auth_user_delete_at_least_one_account": "Vous devez avoir au moins un utilisateur actif lorsque vous activez la gestion de l'authentification", "auth_user_delete_at_least_one_account": "Vous devez avoir au moins un utilisateur actif lorsque vous activez la gestion de l'authentification",
"js_auth_user_delete_confirmation": "Êtes-vous sûr ?", "js_auth_user_delete_confirmation": "Êtes-vous sûr ?",
"settings_page_title": "Paramètres", "settings_page_title": "Paramètres",
"settings_plugin_page_title": "Plugins", "settings_plugin_page_title": "Plugins",
"settings_variable_panel_system_variables": "Paramètres généraux", "settings_variable_panel_system_variables": "Paramètres généraux",
@ -165,22 +165,20 @@
"settings_variable_desc_edition_auth_enabled": "Les identifiants de l'utilisateur par défaut seront admin/admin", "settings_variable_desc_edition_auth_enabled": "Les identifiants de l'utilisateur par défaut seront admin/admin",
"settings_variable_desc_external_url": "URL externe (i.e: https://studio-01.company.com or http://10.10.3.100)", "settings_variable_desc_external_url": "URL externe (i.e: https://studio-01.company.com or http://10.10.3.100)",
"settings_variable_desc_slide_upload_limit": "Limite d'upload du fichier d'une slide (en mégaoctets)", "settings_variable_desc_slide_upload_limit": "Limite d'upload du fichier d'une slide (en mégaoctets)",
"settings_variable_desc_default_slide_duration": "Durée de la slide d'introduction (en secondes)", "settings_variable_desc_intro_slide_duration": "Durée de la slide d'introduction (en secondes)",
"settings_variable_desc_default_slide_time_with_seconds": "Afficher les secondes de l'horloge de la slide d'introduction", "settings_variable_desc_default_slide_time_with_seconds": "Afficher les secondes de l'horloge de la slide d'introduction",
"settings_variable_desc_polling_interval": "Intervalle de rafraîchissement des paramètres à appliquer au lecteur (en secondes)", "settings_variable_desc_polling_interval": "Intervalle de rafraîchissement des paramètres à appliquer au lecteur (en secondes)",
"settings_variable_desc_playlist_default_time_sync": "Synchroniser les slides des lecteurs pour la playlist par défaut", "settings_variable_desc_playlist_default_time_sync": "Synchroniser les slides des lecteurs pour la playlist par défaut",
"settings_variable_desc_slide_animation_enabled": "Activer les effets d'animation entre les slides", "settings_variable_desc_slide_animation_enabled": "Activer les effets d'animation entre les slides",
"settings_variable_desc_slide_animation_entrance_effect": "Effet d'animation d'arrivée de la slide", "settings_variable_desc_slide_animation_entrance_effect": "Effet d'animation d'arrivée de la slide",
"settings_variable_desc_slide_animation_exit_effect": "Effet d'animation de sortie de la slide (généralement mieux sans)", "settings_variable_desc_slide_animation_exit_effect": "Effet d'animation de sortie de la slide (généralement mieux sans)",
"settings_variable_desc_slide_animation_speed": "Vitesse de l'animation de la slide", "settings_variable_desc_slide_animation_speed": "Vitesse de l'animation de la slide",
"settings_variable_desc_ro_start_counter": "Compteur de démarrage",
"settings_variable_desc_ro_last_folder_content": "Dossier courant dans l'explorateur de contenu", "settings_variable_desc_ro_last_folder_content": "Dossier courant dans l'explorateur de contenu",
"settings_variable_desc_ro_last_folder_node_player": "Dossier courant dans l'explorateur du lecteur", "settings_variable_desc_ro_last_folder_node_player": "Dossier courant dans l'explorateur du lecteur",
"settings_variable_desc_ro_editable": "Date de dernier redémarrage de l'application", "settings_variable_desc_ro_editable": "Date de dernier redémarrage de l'application",
"settings_variable_desc_ro_last_slide_update": "Date de dernière modification d'une slide", "settings_variable_desc_ro_last_slide_update": "Date de dernière modification d'une slide",
"settings_variable_desc_ro_refresh_player_request": "Date de dernière demande de rafraîchissement du lecteur", "settings_variable_desc_ro_refresh_player_request": "Date de dernière demande de rafraîchissement du lecteur",
"sysinfo_page_title": "Système", "sysinfo_page_title": "Système",
"sysinfo_panel_button_restart": "Redémarrer", "sysinfo_panel_button_restart": "Redémarrer",
"sysinfo_panel_table_section_system": "Système", "sysinfo_panel_table_section_system": "Système",
@ -193,7 +191,6 @@
"logs_panel_last_logs": "Journaux (100 dernières lignes)", "logs_panel_last_logs": "Journaux (100 dernières lignes)",
"js_sysinfo_restart_confirmation": "Êtes-vous sûr ?", "js_sysinfo_restart_confirmation": "Êtes-vous sûr ?",
"js_sysinfo_restart_loading": "Redémarrage en cours, veuillez patienter...", "js_sysinfo_restart_loading": "Redémarrage en cours, veuillez patienter...",
"basic_day_1": "Lundi", "basic_day_1": "Lundi",
"basic_day_2": "Mardi", "basic_day_2": "Mardi",
"basic_day_3": "Mercredi", "basic_day_3": "Mercredi",
@ -213,7 +210,7 @@
"basic_month_10": "Octobre", "basic_month_10": "Octobre",
"basic_month_11": "Novembre", "basic_month_11": "Novembre",
"basic_month_12": "Décembre", "basic_month_12": "Décembre",
"common_untitled": "<sans-titre>",
"common_loading": "Chargement...", "common_loading": "Chargement...",
"common_default_node_player_group": "Playgroup par défaut", "common_default_node_player_group": "Playgroup par défaut",
"common_default_playlist": "Playlist par défaut", "common_default_playlist": "Playlist par défaut",
@ -245,7 +242,6 @@
"updated_by": "Dernière modification par", "updated_by": "Dernière modification par",
"close": "Fermer", "close": "Fermer",
"anonymous": "Anon", "anonymous": "Anon",
"enum_animation_speed_slower": "Très lent", "enum_animation_speed_slower": "Très lent",
"enum_animation_speed_slow": "Lent", "enum_animation_speed_slow": "Lent",
"enum_animation_speed_normal": "Normal", "enum_animation_speed_normal": "Normal",
@ -279,7 +275,6 @@
"enum_operating_system_redhat": "RedHat", "enum_operating_system_redhat": "RedHat",
"enum_operating_system_centos": "CentOS", "enum_operating_system_centos": "CentOS",
"enum_operating_system_other": "Autre", "enum_operating_system_other": "Autre",
"sysinfo_rpi_model": "Modèle du Raspberry Pi", "sysinfo_rpi_model": "Modèle du Raspberry Pi",
"sysinfo_rpi_model_unknown": "Le modèle n'est pas un Raspberry Pi", "sysinfo_rpi_model_unknown": "Le modèle n'est pas un Raspberry Pi",
"sysinfo_storage_free_space": "Stockage Disponible", "sysinfo_storage_free_space": "Stockage Disponible",
@ -289,6 +284,5 @@
"sysinfo_network_interface": "Interface Réseau", "sysinfo_network_interface": "Interface Réseau",
"sysinfo_mac_address": "Addresse MAC", "sysinfo_mac_address": "Addresse MAC",
"sysinfo_ip_address": "Addresse IP", "sysinfo_ip_address": "Addresse IP",
"player_default_welcome_message": "Pour gérer ce lecteur, allez sur un navigateur à l'adresse %link%" "player_default_welcome_message": "Pour gérer ce lecteur, allez sur un navigateur à l'adresse %link%"
} }

View File

@ -1,6 +1,5 @@
{ {
"dynmenu_content": "Contenuti", "dynmenu_content": "Contenuti",
"slideshow_slide_page_title": "Programmazione", "slideshow_slide_page_title": "Programmazione",
"slideshow_slide_goto_player": "Vai al player", "slideshow_slide_goto_player": "Vai al player",
"slideshow_slide_refresh_player": "Aggiorna player", "slideshow_slide_refresh_player": "Aggiorna player",
@ -46,7 +45,6 @@
"slideshow_slide_form_widget_cron_scheduled_placeholder": "Utilizza formato crontab: * * * * *", "slideshow_slide_form_widget_cron_scheduled_placeholder": "Utilizza formato crontab: * * * * *",
"slideshow_slide_form_button_cancel": "Annulla", "slideshow_slide_form_button_cancel": "Annulla",
"js_slideshow_slide_delete_confirmation": "Sei sicuro?", "js_slideshow_slide_delete_confirmation": "Sei sicuro?",
"slideshow_content_page_title": "Libreria dei contenuti", "slideshow_content_page_title": "Libreria dei contenuti",
"slideshow_content_button_add": "Nuovo Contenuto", "slideshow_content_button_add": "Nuovo Contenuto",
"slideshow_content_referenced_in_slide_error": "Si fa riferimento al contenuto in una diapositiva, rimuovere prima la diapositiva", "slideshow_content_referenced_in_slide_error": "Si fa riferimento al contenuto in una diapositiva, rimuovere prima la diapositiva",
@ -66,10 +64,13 @@
"slideshow_content_form_button_upload": "Carica un file", "slideshow_content_form_button_upload": "Carica un file",
"slideshow_content_form_button_upload_choosen": "Nessun file selezionato", "slideshow_content_form_button_upload_choosen": "Nessun file selezionato",
"js_slideshow_content_delete_confirmation": "Sei sicuro?", "js_slideshow_content_delete_confirmation": "Sei sicuro?",
"playlist_page_title": "Playlists",
"playlist_page_title": "Playlist", "playlist_button_add": "Aggiungi Playlist",
"playlist_button_add": "Aggiungi alla playlist", "playlist_button_delete": "Elimina Playlist",
"playlist_panel_active": "Attiva playlist", "playlist_panel_about_playlist": "Informazioni sulla playlist",
"playlist_panel_content_management": "Gestione dei contenuti",
"playlist_panel_preview": "Anteprima della playlist",
"playlist_panel_preview_action": "Anteprima",
"playlist_panel_inactive": "Playlist inattive", "playlist_panel_inactive": "Playlist inattive",
"playlist_panel_empty": "Attualmente, non ci sono playlist. %link% adesso.", "playlist_panel_empty": "Attualmente, non ci sono playlist. %link% adesso.",
"playlist_panel_th_name": "Nome", "playlist_panel_th_name": "Nome",
@ -78,15 +79,16 @@
"playlist_panel_th_activity": "Opzioni", "playlist_panel_th_activity": "Opzioni",
"playlist_form_add_title": "Aggiungi Playlist", "playlist_form_add_title": "Aggiungi Playlist",
"playlist_form_add_submit": "Aggiungi", "playlist_form_add_submit": "Aggiungi",
"playlist_form_edit_title": "Modifica Playlist", "playlist_form_preview_url_desc": "Puoi utilizzare questo collegamento per riprodurre questa playlist su qualsiasi browser desideri. Utilizza il pulsante Copia per inserirla negli appunti.",
"playlist_form_edit_submit": "Salva", "playlist_form_preview_qrcode_desc": "Puoi accedere facilmente alla tua playlist utilizzando un tablet o un telefono. Basta scansionare il codice QR per iniziare.",
"playlist_form_preview_iframe_desc": "Puoi visualizzare la playlist senza uscire da questa schermata avviando l'anteprima da qui.",
"playlist_form_label_name": "Inserisci il nome della playlist", "playlist_form_label_name": "Inserisci il nome della playlist",
"playlist_form_label_time_sync": "Sincronizza le slide tra gli schermi", "playlist_form_label_time_sync": "Sincronizza le slide tra gli schermi",
"playlist_form_label_enabled": "Abilita/Disabilita playlist",
"playlist_form_button_cancel": "Cancella", "playlist_form_button_cancel": "Cancella",
"js_playlist_delete_confirmation": "Sei sicuro?", "js_playlist_delete_confirmation": "Sei sicuro?",
"playlist_delete_has_slides": "Sono presenti slide nella playlist, annullale e riprova", "playlist_delete_has_slides": "Sono presenti slide nella playlist, annullale e riprova",
"playlist_delete_has_node_player_groups": "La playlist è collegata ad un playgroup", "playlist_delete_has_node_player_groups": "La playlist è collegata ad un playgroup",
"fleet_node_player_page_title": "Schermi", "fleet_node_player_page_title": "Schermi",
"fleet_node_player_button_add": "Aggiungi allo schermo", "fleet_node_player_button_add": "Aggiungi allo schermo",
"fleet_node_player_panel_active": "Schermi attivi", "fleet_node_player_panel_active": "Schermi attivi",
@ -107,7 +109,6 @@
"fleet_node_player_form_label_operating_system": "OS", "fleet_node_player_form_label_operating_system": "OS",
"fleet_node_player_form_button_cancel": "Cancella", "fleet_node_player_form_button_cancel": "Cancella",
"js_fleet_node_player_delete_confirmation": "Sei sicuro?", "js_fleet_node_player_delete_confirmation": "Sei sicuro?",
"fleet_node_player_group_page_title": "Playgroups", "fleet_node_player_group_page_title": "Playgroups",
"fleet_node_player_group_button_add": "Aggiungi Playgroup", "fleet_node_player_group_button_add": "Aggiungi Playgroup",
"fleet_node_player_group_panel_active": "Playgroup attivi", "fleet_node_player_group_panel_active": "Playgroup attivi",
@ -124,7 +125,6 @@
"fleet_node_player_group_form_button_cancel": "Cancella", "fleet_node_player_group_form_button_cancel": "Cancella",
"js_fleet_node_player_group_delete_confirmation": "Sei sicuro?", "js_fleet_node_player_group_delete_confirmation": "Sei sicuro?",
"node_player_group_delete_has_node_player": "Lo playgroup ha una playlist, rumuovila o riassegnala e riprova", "node_player_group_delete_has_node_player": "Lo playgroup ha una playlist, rumuovila o riassegnala e riprova",
"login_page_title": "Login", "login_page_title": "Login",
"auth_page_title": "Utente", "auth_page_title": "Utente",
"auth_user_button_add": "Aggiungi utente", "auth_user_button_add": "Aggiungi utente",
@ -143,7 +143,6 @@
"auth_user_form_button_cancel": "Cancella", "auth_user_form_button_cancel": "Cancella",
"auth_user_delete_at_least_one_account": "È necessario avere almeno un utente attivo durante l'utilizzo della funzione di autenticazione", "auth_user_delete_at_least_one_account": "È necessario avere almeno un utente attivo durante l'utilizzo della funzione di autenticazione",
"js_auth_user_delete_confirmation": "Sei sicuro?", "js_auth_user_delete_confirmation": "Sei sicuro?",
"settings_page_title": "Impostazioni", "settings_page_title": "Impostazioni",
"settings_plugin_page_title": "Plugins", "settings_plugin_page_title": "Plugins",
"settings_variable_panel_system_variables": "Impostazioni generali", "settings_variable_panel_system_variables": "Impostazioni generali",
@ -165,22 +164,20 @@
"settings_variable_desc_edition_auth_enabled": "Le credenziali utente predefinite sono admin/admin", "settings_variable_desc_edition_auth_enabled": "Le credenziali utente predefinite sono admin/admin",
"settings_variable_desc_external_url": "Url esterno (esempio: https://studio-01.company.com or http://10.10.3.100)", "settings_variable_desc_external_url": "Url esterno (esempio: https://studio-01.company.com or http://10.10.3.100)",
"settings_variable_desc_slide_upload_limit": "Limite upload slide (in megabytes)", "settings_variable_desc_slide_upload_limit": "Limite upload slide (in megabytes)",
"settings_variable_desc_default_slide_duration": "Durata introduzione slide (in secondi)", "settings_variable_desc_intro_slide_duration": "Durata introduzione slide (in secondi)",
"settings_variable_desc_default_slide_time_with_seconds": "Mostra secondi introduzione slide", "settings_variable_desc_default_slide_time_with_seconds": "Mostra secondi introduzione slide",
"settings_variable_desc_polling_interval": "Intervallo di aggiornamento applicato per le impostazioni del monitor (in secondi)", "settings_variable_desc_polling_interval": "Intervallo di aggiornamento applicato per le impostazioni del monitor (in secondi)",
"settings_variable_desc_playlist_default_time_sync": "Sincronizza le diapositive tra i lettori per la playlist predefinita", "settings_variable_desc_playlist_default_time_sync": "Sincronizza le diapositive tra i lettori per la playlist predefinita",
"settings_variable_desc_slide_animation_enabled": "Abilita l'effetto di animazione tra le diapositive", "settings_variable_desc_slide_animation_enabled": "Abilita l'effetto di animazione tra le diapositive",
"settings_variable_desc_slide_animation_entrance_effect": "Effetto ingresso diapositiva", "settings_variable_desc_slide_animation_entrance_effect": "Effetto ingresso diapositiva",
"settings_variable_desc_slide_animation_exit_effect": "Effetto di uscita della diapositiva (meglio senza)", "settings_variable_desc_slide_animation_exit_effect": "Effetto di uscita della diapositiva (meglio senza)",
"settings_variable_desc_slide_animation_speed": "Velicita animazione slide", "settings_variable_desc_slide_animation_speed": "Velicita animazione slide",
"settings_variable_desc_ro_start_counter": "Avvia contatore",
"settings_variable_desc_ro_last_folder_content": "Cartella corrente in Esplora contenuti", "settings_variable_desc_ro_last_folder_content": "Cartella corrente in Esplora contenuti",
"settings_variable_desc_ro_last_folder_node_player": "Cartella corrente in Player Explorer", "settings_variable_desc_ro_last_folder_node_player": "Cartella corrente in Player Explorer",
"settings_variable_desc_ro_editable": "Data/ora dell'ultimo riavvio dell'applicazione", "settings_variable_desc_ro_editable": "Data/ora dell'ultimo riavvio dell'applicazione",
"settings_variable_desc_ro_last_slide_update": "Data e ora dell'ultimo aggiornamento della diapositiva", "settings_variable_desc_ro_last_slide_update": "Data e ora dell'ultimo aggiornamento della diapositiva",
"settings_variable_desc_ro_refresh_player_request": "Data e ora della richiesta di aggiornamento dell monitor", "settings_variable_desc_ro_refresh_player_request": "Data e ora della richiesta di aggiornamento dell monitor",
"sysinfo_page_title": "Informazione sistema", "sysinfo_page_title": "Informazione sistema",
"sysinfo_panel_button_restart": "Riavvia", "sysinfo_panel_button_restart": "Riavvia",
"sysinfo_panel_table_section_system": "Sistema", "sysinfo_panel_table_section_system": "Sistema",
@ -193,7 +190,6 @@
"logs_panel_last_logs": "Logs (ultime 100 righe)", "logs_panel_last_logs": "Logs (ultime 100 righe)",
"js_sysinfo_restart_confirmation": "Sei sicuro?", "js_sysinfo_restart_confirmation": "Sei sicuro?",
"js_sysinfo_restart_loading": "Riavvio in corso, attendi...", "js_sysinfo_restart_loading": "Riavvio in corso, attendi...",
"basic_day_1": "Lunedi", "basic_day_1": "Lunedi",
"basic_day_2": "Martedi", "basic_day_2": "Martedi",
"basic_day_3": "Mercoledi", "basic_day_3": "Mercoledi",
@ -213,7 +209,7 @@
"basic_month_10": "Ottobre", "basic_month_10": "Ottobre",
"basic_month_11": "Novembre", "basic_month_11": "Novembre",
"basic_month_12": "Dicembre", "basic_month_12": "Dicembre",
"common_untitled": "<senza-titolo>",
"common_loading": "Caricamento...", "common_loading": "Caricamento...",
"common_default_node_player_group": "Playgroup di default", "common_default_node_player_group": "Playgroup di default",
"common_default_playlist": "Default playlist", "common_default_playlist": "Default playlist",
@ -245,7 +241,6 @@
"updated_by": "Aggiornato da", "updated_by": "Aggiornato da",
"close": "Chiuso", "close": "Chiuso",
"anonymous": "Anonimo", "anonymous": "Anonimo",
"enum_animation_speed_slower": "Lentamente", "enum_animation_speed_slower": "Lentamente",
"enum_animation_speed_slow": "Lento", "enum_animation_speed_slow": "Lento",
"enum_animation_speed_normal": "Normale", "enum_animation_speed_normal": "Normale",
@ -279,7 +274,6 @@
"enum_operating_system_redhat": "RedHat", "enum_operating_system_redhat": "RedHat",
"enum_operating_system_centos": "CentOS", "enum_operating_system_centos": "CentOS",
"enum_operating_system_other": "Altro", "enum_operating_system_other": "Altro",
"sysinfo_rpi_model": "Raspberry Pi Model", "sysinfo_rpi_model": "Raspberry Pi Model",
"sysinfo_rpi_model_unknown": "Informazioni Raspberry Pi non disponibili", "sysinfo_rpi_model_unknown": "Informazioni Raspberry Pi non disponibili",
"sysinfo_storage_free_space": "Spazio libero", "sysinfo_storage_free_space": "Spazio libero",
@ -289,6 +283,5 @@
"sysinfo_network_interface": "interfaccia di rete", "sysinfo_network_interface": "interfaccia di rete",
"sysinfo_mac_address": "Indirizzo MAC", "sysinfo_mac_address": "Indirizzo MAC",
"sysinfo_ip_address": "indirizzo IP", "sysinfo_ip_address": "indirizzo IP",
"player_default_welcome_message": "Per gestire questo lettore, vai al browser all'indirizzo %link%" "player_default_welcome_message": "Per gestire questo lettore, vai al browser all'indirizzo %link%"
} }

View File

@ -24,6 +24,11 @@ class Application:
signal.signal(signal.SIGINT, self.signal_handler) signal.signal(signal.SIGINT, self.signal_handler)
def start(self) -> None: def start(self) -> None:
variable = self._model_store.variable().get_one_by_name('start_counter')
if variable:
self._model_store.variable().update_by_name(variable.name, variable.as_int() + 1)
self._web_server.run() self._web_server.run()
def signal_handler(self, signal, frame) -> None: def signal_handler(self, signal, frame) -> None:

View File

@ -30,17 +30,17 @@ class AuthController(ObController):
login_error = None login_error = None
if current_user.is_authenticated: if current_user.is_authenticated:
return redirect(url_for('slideshow_slide_list')) return redirect(url_for('playlist'))
if not self._model_store.variable().map().get('auth_enabled').as_bool(): if not self._model_store.variable().map().get('auth_enabled').as_bool():
return redirect(url_for('slideshow_slide_list')) return redirect(url_for('playlist'))
if len(request.form): if len(request.form):
user = self._model_store.user().get_one_by_username(request.form['username'], enabled=True) user = self._model_store.user().get_one_by_username(request.form['username'], enabled=True)
if user: if user:
if user.password == self._model_store.user().encode_password(request.form['password']): if user.password == self._model_store.user().encode_password(request.form['password']):
login_user(user) login_user(user)
return redirect(url_for('slideshow_slide_list')) return redirect(url_for('playlist'))
else: else:
login_error = 'bad_credentials' login_error = 'bad_credentials'
else: else:

View File

@ -37,13 +37,22 @@ class PlayerController(ObController):
playlist_id = current_playlist.id if current_playlist else None playlist_id = current_playlist.id if current_playlist else None
items = self._get_playlist(playlist_id=playlist_id, preview_content_id=preview_content_id) items = self._get_playlist(playlist_id=playlist_id, preview_content_id=preview_content_id)
intro_slide_duration = self._model_store.variable().get_one_by_name('intro_slide_duration').eval()
if items['preview_mode'] or request.args.get('intro', '1') == '0':
intro_slide_duration = 0
animation_enabled = self._model_store.variable().get_one_by_name('slide_animation_enabled').eval()
if request.args.get('animation', '1') == '0':
animation_enabled = False
return render_template( return render_template(
'player/player.jinja.html', 'player/player.jinja.html',
items=items, items=items,
default_slide_duration=0 if items['preview_mode'] else self._model_store.variable().get_one_by_name('default_slide_duration').eval(), intro_slide_duration=intro_slide_duration,
polling_interval=self._model_store.variable().get_one_by_name('polling_interval'), polling_interval=self._model_store.variable().get_one_by_name('polling_interval'),
slide_animation_enabled=self._model_store.variable().get_one_by_name('slide_animation_enabled'), slide_animation_enabled=animation_enabled,
slide_animation_entrance_effect=self._model_store.variable().get_one_by_name('slide_animation_entrance_effect'), slide_animation_entrance_effect=self._model_store.variable().get_one_by_name('slide_animation_entrance_effect'),
slide_animation_exit_effect=self._model_store.variable().get_one_by_name('slide_animation_exit_effect'), slide_animation_exit_effect=self._model_store.variable().get_one_by_name('slide_animation_exit_effect'),
slide_animation_speed=self._model_store.variable().get_one_by_name('slide_animation_speed'), slide_animation_speed=self._model_store.variable().get_one_by_name('slide_animation_speed'),

View File

@ -1,6 +1,7 @@
import json import json
from flask import Flask, render_template, redirect, request, url_for, jsonify from flask import Flask, render_template, redirect, request, url_for, jsonify, abort
from src.exceptions.PlaylistSlugAlreadyExist import PlaylistSlugAlreadyExist
from src.service.ModelStore import ModelStore from src.service.ModelStore import ModelStore
from src.model.entity.Playlist import Playlist from src.model.entity.Playlist import Playlist
from src.interface.ObController import ObController from src.interface.ObController import ObController
@ -17,20 +18,31 @@ class PlaylistController(ObController):
return decorated_function return decorated_function
def register(self): def register(self):
self._app.add_url_rule('/playlist/list', 'playlist_list', self.guard_playlist(self._auth(self.playlist_list)), methods=['GET']) self._app.add_url_rule('/playlist', 'playlist', self.guard_playlist(self._auth(self.playlist)), methods=['GET'])
self._app.add_url_rule('/playlist/list/<playlist_id>', 'playlist_list', self.guard_playlist(self._auth(self.playlist_list)), methods=['GET'])
self._app.add_url_rule('/playlist/add', 'playlist_add', self.guard_playlist(self._auth(self.playlist_add)), methods=['POST']) self._app.add_url_rule('/playlist/add', 'playlist_add', self.guard_playlist(self._auth(self.playlist_add)), methods=['POST'])
self._app.add_url_rule('/playlist/edit', 'playlist_edit', self.guard_playlist(self._auth(self.playlist_edit)), methods=['POST']) self._app.add_url_rule('/playlist/save', 'playlist_save', self.guard_playlist(self._auth(self.playlist_save)), methods=['POST'])
self._app.add_url_rule('/playlist/toggle', 'playlist_toggle', self.guard_playlist(self._auth(self.playlist_toggle)), methods=['POST']) self._app.add_url_rule('/playlist/delete/<playlist_id>', 'playlist_delete', self.guard_playlist(self._auth(self.playlist_delete)), methods=['GET'])
self._app.add_url_rule('/playlist/delete', 'playlist_delete', self.guard_playlist(self._auth(self.playlist_delete)), methods=['DELETE'])
def playlist_list(self): def playlist(self):
return redirect(url_for('playlist_list', playlist_id=0))
def playlist_list(self, playlist_id: int = 0):
current_playlist = self._model_store.playlist().get(playlist_id)
playlists = self._model_store.playlist().get_all(sort="created_at", ascending=False)
durations = self._model_store.playlist().get_durations_by_playlists() durations = self._model_store.playlist().get_durations_by_playlists()
if not current_playlist and len(playlists) > 0:
current_playlist = playlists[0]
return render_template( return render_template(
'playlist/list.jinja.html', 'playlist/list.jinja.html',
playlists=self._model_store.playlist().get_all(ascending=True), error=request.args.get('error', None),
enabled_playlists=self._model_store.playlist().get_enabled_playlists(with_default=True), current_playlist=current_playlist,
disabled_playlists=self._model_store.playlist().get_disabled_playlists(), playlists=playlists,
durations=durations durations=durations,
slides=self._model_store.slide().get_slides(playlist_id=current_playlist.id),
contents={content.id: content for content in self._model_store.content().get_contents()},
) )
def playlist_add(self): def playlist_add(self):
@ -40,32 +52,28 @@ class PlaylistController(ObController):
time_sync=False time_sync=False
) )
self._model_store.playlist().add_form(playlist) try:
playlist = self._model_store.playlist().add_form(playlist)
except PlaylistSlugAlreadyExist as e:
abort(409)
return redirect(url_for('playlist_list')) return redirect(url_for('playlist_list', playlist_id=playlist.id))
def playlist_edit(self): def playlist_save(self):
self._model_store.playlist().update_form( self._model_store.playlist().update_form(
id=request.form['id'], id=request.form['id'],
name=request.form['name'], name=request.form['name'],
time_sync=request.form['time_sync'], time_sync=True if 'time_sync' in request.form else False,
enabled=True if 'enabled' in request.form else False
) )
return redirect(url_for('playlist_list')) return redirect(url_for('playlist_list', playlist_id=request.form['id']))
def playlist_toggle(self): def playlist_delete(self, playlist_id: int):
data = request.get_json() if self._model_store.slide().count_slides_for_playlist(playlist_id) > 0:
self._model_store.playlist().update_enabled(data.get('id'), data.get('enabled')) return redirect(url_for('playlist_list', playlist_id=playlist_id, error='playlist_delete_has_slides'))
return jsonify({'status': 'ok'})
def playlist_delete(self): if self._model_store.node_player_group().count_node_player_groups_for_playlist(playlist_id) > 0:
data = request.get_json() return redirect(url_for('playlist_list', playlist_id=playlist_id, error='playlist_delete_has_node_player_groups'))
id = data.get('id')
if self._model_store.slide().count_slides_for_playlist(id) > 0: self._model_store.playlist().delete(playlist_id)
return jsonify({'status': 'error', 'message': self.t('playlist_delete_has_slides')}), 400 return redirect(url_for('playlist'))
if self._model_store.node_player_group().count_node_player_groups_for_playlist(id) > 0:
return jsonify({'status': 'error', 'message': self.t('playlist_delete_has_node_player_groups')}), 400
self._model_store.playlist().delete(id)
return jsonify({'status': 'ok'})

View File

@ -16,31 +16,14 @@ class SlideController(ObController):
def register(self): def register(self):
self._app.add_url_rule('/manage', 'manage', self.manage, methods=['GET']) self._app.add_url_rule('/manage', 'manage', self.manage, methods=['GET'])
self._app.add_url_rule('/slideshow', 'slideshow_slide_list', self._auth(self.slideshow), methods=['GET'])
self._app.add_url_rule('/slideshow/playlist/set/<playlist_id>', 'slideshow_slide_list_playlist_use', self._auth(self.slideshow), methods=['GET'])
self._app.add_url_rule('/slideshow/slide/add', 'slideshow_slide_add', self._auth(self.slideshow_slide_add), methods=['POST']) self._app.add_url_rule('/slideshow/slide/add', 'slideshow_slide_add', self._auth(self.slideshow_slide_add), methods=['POST'])
self._app.add_url_rule('/slideshow/slide/edit', 'slideshow_slide_edit', self._auth(self.slideshow_slide_edit), methods=['POST']) self._app.add_url_rule('/slideshow/slide/edit', 'slideshow_slide_edit', self._auth(self.slideshow_slide_edit), methods=['POST'])
self._app.add_url_rule('/slideshow/slide/toggle', 'slideshow_slide_toggle', self._auth(self.slideshow_slide_toggle), methods=['POST'])
self._app.add_url_rule('/slideshow/slide/delete', 'slideshow_slide_delete', self._auth(self.slideshow_slide_delete), methods=['DELETE']) self._app.add_url_rule('/slideshow/slide/delete', 'slideshow_slide_delete', self._auth(self.slideshow_slide_delete), methods=['DELETE'])
self._app.add_url_rule('/slideshow/slide/position', 'slideshow_slide_position', self._auth(self.slideshow_slide_position), methods=['POST']) self._app.add_url_rule('/slideshow/slide/position', 'slideshow_slide_position', self._auth(self.slideshow_slide_position), methods=['POST'])
self._app.add_url_rule('/slideshow/player-refresh', 'slideshow_player_refresh', self._auth(self.slideshow_player_refresh), methods=['GET']) self._app.add_url_rule('/slideshow/player-refresh', 'slideshow_player_refresh/<playlist_id>', self._auth(self.slideshow_player_refresh), methods=['GET'])
def manage(self): def manage(self):
return redirect(url_for('slideshow_slide_list')) return redirect(url_for('playlist'))
def slideshow(self, playlist_id: int = 0):
current_playlist = self._model_store.playlist().get(playlist_id)
playlist_id = current_playlist.id if current_playlist else None
return render_template(
'slideshow/slides/list.jinja.html',
current_playlist=current_playlist,
playlists=self._model_store.playlist().get_enabled_playlists(),
enabled_slides=self._model_store.slide().get_slides(playlist_id=playlist_id, enabled=True),
disabled_slides=self._model_store.slide().get_slides(playlist_id=playlist_id, enabled=False),
var_last_restart=self._model_store.variable().get_one_by_name('last_restart'),
contents={content.id: content.name for content in self._model_store.content().get_contents()},
enum_content_type=ContentType
)
def slideshow_slide_add(self): def slideshow_slide_add(self):
content = None content = None
@ -67,9 +50,9 @@ class SlideController(ObController):
self._post_update() self._post_update()
if slide.playlist_id: if slide.playlist_id:
return redirect(url_for('slideshow_slide_list_playlist_use', playlist_id=slide.playlist_id)) return redirect(url_for('playlist_list', playlist_id=slide.playlist_id))
return redirect(url_for('slideshow_slide_list')) return redirect(url_for('playlist'))
def slideshow_slide_edit(self): def slideshow_slide_edit(self):
slide = self._model_store.slide().update_form( slide = self._model_store.slide().update_form(
@ -83,15 +66,9 @@ class SlideController(ObController):
self._post_update() self._post_update()
if slide.playlist_id: if slide.playlist_id:
return redirect(url_for('slideshow_slide_list_playlist_use', playlist_id=slide.playlist_id)) return redirect(url_for('playlist_list', playlist_id=slide.playlist_id))
return redirect(url_for('slideshow_slide_list')) return redirect(url_for('playlist'))
def slideshow_slide_toggle(self):
data = request.get_json()
self._model_store.slide().update_enabled(data.get('id'), data.get('enabled'))
self._post_update()
return jsonify({'status': 'ok'})
def slideshow_slide_delete(self): def slideshow_slide_delete(self):
data = request.get_json() data = request.get_json()
@ -105,11 +82,12 @@ class SlideController(ObController):
self._post_update() self._post_update()
return jsonify({'status': 'ok'}) return jsonify({'status': 'ok'})
def slideshow_player_refresh(self): def slideshow_player_refresh(self, playlist_id: int):
self._model_store.variable().update_by_name("refresh_player_request", time.time()) self._model_store.variable().update_by_name("refresh_player_request", time.time())
return redirect( return redirect(
url_for( url_for(
'slideshow_slide_list', 'playlist_list',
playlist_id=playlist_id,
refresh_player=self._model_store.variable().get_one_by_name('polling_interval').as_int() refresh_player=self._model_store.variable().get_one_by_name('polling_interval').as_int()
) )
) )

View File

@ -0,0 +1,2 @@
class PlaylistSlugAlreadyExist(Exception):
pass

View File

@ -120,8 +120,13 @@ class DatabaseManager:
params=tuple(v for v in values.values()) params=tuple(v for v in values.values())
) )
def get_one_by_query(self, table_name: str, query: str = "1=1", sort: Optional[str] = None, ascending=True, values: dict = {}) -> list: def get_one_by_query(self, table_name: str, query: str = "1=1", values: dict = {}, sort: Optional[str] = None, ascending=True, limit: Optional[int] = None) -> list:
query = "select * from {} where {} {}".format(table_name, query, "ORDER BY {} {}".format(sort, "ASC" if ascending else "DESC") if sort else "") query = "select * from {} where {} {} {}".format(
table_name,
query,
"ORDER BY {} {}".format(sort, "ASC" if ascending else "DESC") if sort else "",
"LIMIT {}".format(limit) if limit else ""
)
lines = self.execute_read_query(query=query, params=tuple(v for v in values.values())) lines = self.execute_read_query(query=query, params=tuple(v for v in values.values()))
count = len(lines) count = len(lines)
@ -216,6 +221,7 @@ class DatabaseManager:
"DROP TABLE IF EXISTS fleet_studio", "DROP TABLE IF EXISTS fleet_studio",
"ALTER TABLE slideshow RENAME TO slides", "ALTER TABLE slideshow RENAME TO slides",
"DELETE FROM settings WHERE name = 'fleet_studio_enabled'", "DELETE FROM settings WHERE name = 'fleet_studio_enabled'",
"DELETE FROM settings WHERE name = 'default_slide_duration'",
"UPDATE content SET uuid = id WHERE uuid = '' or uuid is null", "UPDATE content SET uuid = id WHERE uuid = '' or uuid is null",
] ]

View File

@ -72,10 +72,9 @@ class NodePlayerGroupManager(ModelManager):
def get_node_players_groups(self, playlist_id: Optional[int] = None) -> List[NodePlayerGroup]: def get_node_players_groups(self, playlist_id: Optional[int] = None) -> List[NodePlayerGroup]:
query = " 1=1 " query = " 1=1 "
if playlist_id: if playlist_id:
query = "{} {}".format(query, "AND playlist_id = {}".format(playlist_id)) query = "{} {}".format(query, "AND playlist_id = {}".format(playlist_id))
else:
query = "{} {}".format(query, "AND playlist_id is NULL")
return self.get_by(query=query, sort="name") return self.get_by(query=query, sort="name")

View File

@ -3,7 +3,8 @@ import os
from typing import Dict, Optional, List, Tuple, Union from typing import Dict, Optional, List, Tuple, Union
from src.model.entity.Playlist import Playlist from src.model.entity.Playlist import Playlist
from src.util.utils import get_optional_string, get_yt_video_id, slugify from src.util.utils import get_optional_string, get_yt_video_id, slugify, slugify_next
from src.exceptions.PlaylistSlugAlreadyExist import PlaylistSlugAlreadyExist
from src.manager.DatabaseManager import DatabaseManager from src.manager.DatabaseManager import DatabaseManager
from src.manager.SlideManager import SlideManager from src.manager.SlideManager import SlideManager
from src.manager.LangManager import LangManager from src.manager.LangManager import LangManager
@ -54,8 +55,8 @@ class PlaylistManager(ModelManager):
def get_by(self, query, sort: Optional[str] = None, values: dict = {}) -> List[Playlist]: def get_by(self, query, sort: Optional[str] = None, values: dict = {}) -> List[Playlist]:
return self.hydrate_list(self._db.get_by_query(self.TABLE_NAME, query=query, sort=sort, values=values)) return self.hydrate_list(self._db.get_by_query(self.TABLE_NAME, query=query, sort=sort, values=values))
def get_one_by(self, query, values: dict = {}) -> Optional[Playlist]: def get_one_by(self, query, values: dict = {}, sort: Optional[str] = None, ascending=True, limit: Optional[int] = None) -> Optional[Playlist]:
object = self._db.get_one_by_query(self.TABLE_NAME, query=query, values=values) object = self._db.get_one_by_query(self.TABLE_NAME, query=query, values=values, sort=sort, ascending=ascending, limit=limit)
if not object: if not object:
return None return None
@ -105,8 +106,25 @@ class PlaylistManager(ModelManager):
for playlist_id, edits in edits_playlists.items(): for playlist_id, edits in edits_playlists.items():
self._db.update_by_id(self.TABLE_NAME, playlist_id, edits) self._db.update_by_id(self.TABLE_NAME, playlist_id, edits)
def get_available_slug(self, slug) -> str:
known_playlist = {"slug": slug}
next_slug = slug
while known_playlist is not None:
next_slug = slugify_next(next_slug)
known_playlist = self.get_one_by(query="slug = ?", values={"slug": next_slug}, sort="created_at", ascending=False, limit=1)
return next_slug
def pre_add(self, playlist: Dict) -> Dict: def pre_add(self, playlist: Dict) -> Dict:
playlist["slug"] = slugify(playlist["name"]) playlist["slug"] = slugify(playlist["name"])
known_playlist = self.get_one_by(query="slug = ?", values={
"slug": playlist["slug"]
}, sort="created_at", ascending=False, limit=1)
if known_playlist:
playlist["slug"] = self.get_available_slug(playlist["slug"])
self.user_manager.track_user_on_create(playlist) self.user_manager.track_user_on_create(playlist)
self.user_manager.track_user_on_update(playlist) self.user_manager.track_user_on_update(playlist)
return playlist return playlist
@ -131,7 +149,7 @@ class PlaylistManager(ModelManager):
def post_delete(self, playlist_id: str) -> str: def post_delete(self, playlist_id: str) -> str:
return playlist_id return playlist_id
def update_form(self, id: int, name: str, time_sync: bool) -> None: def update_form(self, id: int, name: str, time_sync: bool, enabled: bool) -> None:
playlist = self.get(id) playlist = self.get(id)
if not playlist: if not playlist:
@ -139,13 +157,14 @@ class PlaylistManager(ModelManager):
form = { form = {
"name": name, "name": name,
"time_sync": time_sync "time_sync": time_sync,
"enabled": enabled
} }
self._db.update_by_id(self.TABLE_NAME, id, self.pre_update(form)) self._db.update_by_id(self.TABLE_NAME, id, self.pre_update(form))
self.post_update(id) self.post_update(id)
def add_form(self, playlist: Union[Playlist, Dict]) -> None: def add_form(self, playlist: Union[Playlist, Dict]) -> Playlist:
form = playlist form = playlist
if not isinstance(playlist, dict): if not isinstance(playlist, dict):
@ -153,7 +172,11 @@ class PlaylistManager(ModelManager):
del form['id'] del form['id']
self._db.add(self.TABLE_NAME, self.pre_add(form)) self._db.add(self.TABLE_NAME, self.pre_add(form))
playlist = self.get_one_by(query="slug = ?", values={
"slug": form["slug"]
})
self.post_add(playlist.id) self.post_add(playlist.id)
return playlist
def delete(self, id: int) -> None: def delete(self, id: int) -> None:
playlist = self.get(id) playlist = self.get(id)

View File

@ -73,13 +73,14 @@ class SlideManager(ModelManager):
for slide_id, edits in edits_slides.items(): for slide_id, edits in edits_slides.items():
self._db.update_by_id(self.TABLE_NAME, slide_id, edits) self._db.update_by_id(self.TABLE_NAME, slide_id, edits)
def get_slides(self, playlist_id: Optional[int] = None, content_id: Optional[int] = None, enabled: bool = True) -> List[Slide]: def get_slides(self, playlist_id: Optional[int] = None, content_id: Optional[int] = None, enabled: Optional[bool] = None) -> List[Slide]:
query = "enabled = {}".format("1" if enabled else "0") query = " 1=1 "
if enabled is not None:
query = "{} AND enabled = {} ".format(query, "1" if enabled else "0")
if playlist_id: if playlist_id:
query = "{} {}".format(query, "AND playlist_id = {}".format(playlist_id)) query = "{} {}".format(query, "AND playlist_id = {}".format(playlist_id))
else:
query = "{} {}".format(query, "AND playlist_id is NULL")
if content_id: if content_id:
query = "{} {}".format(query, "AND content_id = {}".format(content_id)) query = "{} {}".format(query, "AND content_id = {}".format(content_id))

View File

@ -1,5 +1,6 @@
import json import json
import time import time
import math
from typing import Dict, Optional, List, Tuple, Union from typing import Dict, Optional, List, Tuple, Union
from src.manager.DatabaseManager import DatabaseManager from src.manager.DatabaseManager import DatabaseManager
@ -111,7 +112,7 @@ class VariableManager:
{"name": "slide_upload_limit", "section": self.t(VariableSection.GENERAL), "value": 32, "unit": VariableUnit.MEGABYTE, "type": VariableType.INT, "editable": True, "description": self.t('settings_variable_desc_slide_upload_limit'), "refresh_player": False}, {"name": "slide_upload_limit", "section": self.t(VariableSection.GENERAL), "value": 32, "unit": VariableUnit.MEGABYTE, "type": VariableType.INT, "editable": True, "description": self.t('settings_variable_desc_slide_upload_limit'), "refresh_player": False},
### Player Options ### Player Options
{"name": "default_slide_duration", "section": self.t(VariableSection.PLAYER_OPTIONS), "value": 3, "unit": VariableUnit.SECOND, "type": VariableType.INT, "editable": True, "description": self.t('settings_variable_desc_default_slide_duration'), "refresh_player": False}, {"name": "intro_slide_duration", "section": self.t(VariableSection.PLAYER_OPTIONS), "value": 3, "unit": VariableUnit.SECOND, "type": VariableType.INT, "editable": True, "description": self.t('settings_variable_desc_intro_slide_duration'), "refresh_player": False},
{"name": "default_slide_time_with_seconds", "section": self.t(VariableSection.PLAYER_OPTIONS), "value": False, "type": VariableType.BOOL, "editable": True, "description": self.t('settings_variable_desc_default_slide_time_with_seconds'), "refresh_player": False}, {"name": "default_slide_time_with_seconds", "section": self.t(VariableSection.PLAYER_OPTIONS), "value": False, "type": VariableType.BOOL, "editable": True, "description": self.t('settings_variable_desc_default_slide_time_with_seconds'), "refresh_player": False},
{"name": "polling_interval", "section": self.t(VariableSection.PLAYER_OPTIONS), "value": 5, "unit": VariableUnit.SECOND, "type": VariableType.INT, "editable": True, "description": self.t('settings_variable_desc_polling_interval'), "refresh_player": True}, {"name": "polling_interval", "section": self.t(VariableSection.PLAYER_OPTIONS), "value": 5, "unit": VariableUnit.SECOND, "type": VariableType.INT, "editable": True, "description": self.t('settings_variable_desc_polling_interval'), "refresh_player": True},
@ -132,6 +133,7 @@ class VariableManager:
{"name": "auth_enabled", "section": self.t(VariableSection.SECURITY), "value": False, "type": VariableType.BOOL, "editable": True, "description": self.t('settings_variable_desc_auth_enabled'), "description_edition": self.t('settings_variable_desc_edition_auth_enabled'), "refresh_player": False}, {"name": "auth_enabled", "section": self.t(VariableSection.SECURITY), "value": False, "type": VariableType.BOOL, "editable": True, "description": self.t('settings_variable_desc_auth_enabled'), "description_edition": self.t('settings_variable_desc_edition_auth_enabled'), "refresh_player": False},
# Not editable (System information) # Not editable (System information)
{"name": "start_counter", "value": 0, "type": VariableType.INT, "editable": False, "description": self.t('settings_variable_desc_ro_start_counter')},
{"name": "last_folder_content", "value": FOLDER_ROOT_PATH, "type": VariableType.STRING, "editable": False, "description": self.t('settings_variable_desc_ro_last_folder_content')}, {"name": "last_folder_content", "value": FOLDER_ROOT_PATH, "type": VariableType.STRING, "editable": False, "description": self.t('settings_variable_desc_ro_last_folder_content')},
{"name": "last_folder_node_player", "value": FOLDER_ROOT_PATH, "type": VariableType.STRING, "editable": False, "description": self.t('settings_variable_desc_ro_last_folder_node_player')}, {"name": "last_folder_node_player", "value": FOLDER_ROOT_PATH, "type": VariableType.STRING, "editable": False, "description": self.t('settings_variable_desc_ro_last_folder_node_player')},
{"name": "last_restart", "value": time.time(), "type": VariableType.TIMESTAMP, "editable": False, "description": self.t('settings_variable_desc_ro_editable')}, {"name": "last_restart", "value": time.time(), "type": VariableType.TIMESTAMP, "editable": False, "description": self.t('settings_variable_desc_ro_editable')},

View File

@ -89,7 +89,7 @@ class Playlist:
return f"Playlist(" \ return f"Playlist(" \
f"id='{self.id}',\n" \ f"id='{self.id}',\n" \
f"name='{self.name}',\n" \ f"name='{self.name}',\n" \
f"nameslug='{self.slug}',\n" \ f"slug='{self.slug}',\n" \
f"enabled='{self.enabled}',\n" \ f"enabled='{self.enabled}',\n" \
f"time_sync='{self.time_sync}',\n" \ f"time_sync='{self.time_sync}',\n" \
f"created_by='{self.created_by}',\n" \ f"created_by='{self.created_by}',\n" \

View File

@ -3,11 +3,6 @@ from enum import Enum
class HookType(Enum): class HookType(Enum):
H_SLIDESHOW_SLIDES_TOOLBAR_ACTIONS_START = 'h_slideshow_slides_toolbar_actions_start'
H_SLIDESHOW_SLIDES_TOOLBAR_ACTIONS_END = 'h_slideshow_slides_toolbar_actions_end'
H_SLIDESHOW_SLIDES_CSS = 'h_slideshow_slides_css'
H_SLIDESHOW_SLIDES_JAVASCRIPT = 'h_slideshow_slides_javascript'
H_SLIDESHOW_CONTENT_TOOLBAR_ACTIONS_START = 'h_slideshow_toolbar_actions_start' H_SLIDESHOW_CONTENT_TOOLBAR_ACTIONS_START = 'h_slideshow_toolbar_actions_start'
H_SLIDESHOW_CONTENT_TOOLBAR_ACTIONS_END = 'h_slideshow_toolbar_actions_end' H_SLIDESHOW_CONTENT_TOOLBAR_ACTIONS_END = 'h_slideshow_toolbar_actions_end'
H_SLIDESHOW_CONTENT_CSS = 'h_slideshow_css' H_SLIDESHOW_CONTENT_CSS = 'h_slideshow_css'

View File

@ -255,3 +255,13 @@ def merge_dicts(dict1, dict2):
def dictsort(dict1, attribute="position"): def dictsort(dict1, attribute="position"):
return dict(sorted(dict1.items(), key=lambda item: item[1][attribute])) return dict(sorted(dict1.items(), key=lambda item: item[1][attribute]))
def slugify_next(slug: str) -> str:
parts = slug.rsplit('-', 1)
if len(parts) > 1 and parts[-1].isdigit():
next_number = int(parts[-1]) + 1
return f"{parts[0]}-{next_number}"
else:
return f"{slug}-1"

View File

@ -59,7 +59,7 @@
"position": 0, "position": 0,
"pills": [ "pills": [
{"name": "Bibliothèque", "route": "slideshow_content_list", "icon": "fa-image"}, {"name": "Bibliothèque", "route": "slideshow_content_list", "icon": "fa-image"},
{"name": "Playlists", "route": "playlist_list", "icon": "fa-play"}, {"name": "Playlists", "route": "playlist", "icon": "fa-play"},
] ]
}, },
"configuration": { "configuration": {
@ -115,7 +115,7 @@
{% block header %} {% block header %}
<menu> <menu>
<h1 class="logo"> <h1 class="logo">
<a href="{{ url_for('slideshow_slide_list') }}"> <a href="{{ url_for('playlist') }}">
<img src="{{ STATIC_PREFIX }}img/logo2.png" class="before"/> <img src="{{ STATIC_PREFIX }}img/logo2.png" class="before"/>
<img src="{{ STATIC_PREFIX }}img/logo2white.png" class="after"/> <img src="{{ STATIC_PREFIX }}img/logo2white.png" class="after"/>
Obscreen Obscreen
@ -164,7 +164,8 @@
<ul class="pills"> <ul class="pills">
{% for menu in current_dynmenu.pills %} {% for menu in current_dynmenu.pills %}
<li class="{{ 'active' if active_route == menu.route }}"> <li class="{{ 'active' if active_route == menu.route }}">
<a href="{{ url_for(menu.route) }}"> {% set href = menu.url_for if menu.url_for else url_for(menu.route) %}
<a href="{{ href }}">
<span class="icon"> <span class="icon">
<i class="fa {{ menu.icon }}"></i> <i class="fa {{ menu.icon }}"></i>
</span> </span>

View File

@ -6,7 +6,7 @@
{% block add_css %} {% block add_css %}
<link rel="stylesheet" href="{{ STATIC_PREFIX }}css/lib/jquery-explr-1.4.css"/> <link rel="stylesheet" href="{{ STATIC_PREFIX }}css/lib/jquery-explr-1.4.css"/>
{{ HOOK(H_SLIDESHOW_CONTENT_CSS) }} {{ HOOK(H_FLEET_NODE_PLAYER_CSS) }}
{% endblock %} {% endblock %}
{% block add_js %} {% block add_js %}
@ -14,8 +14,7 @@
<script src="{{ STATIC_PREFIX }}js/explorer.js"></script> <script src="{{ STATIC_PREFIX }}js/explorer.js"></script>
<script src="{{ STATIC_PREFIX }}js/fleet/node-players.js"></script> <script src="{{ STATIC_PREFIX }}js/fleet/node-players.js"></script>
<script src="{{ STATIC_PREFIX }}js/lib/jquery-ui.min.js"></script> <script src="{{ STATIC_PREFIX }}js/lib/jquery-ui.min.js"></script>
{{ HOOK(H_FLEET_NODE_PLAYER_JAVASCRIPT) }}
{{ HOOK(H_SLIDESHOW_CONTENT_JAVASCRIPT) }}
{% endblock %} {% endblock %}
{% block body_class %}view-node-player-list{% endblock %} {% block body_class %}view-node-player-list{% endblock %}
@ -27,7 +26,7 @@
</h1> </h1>
<div class="top-actions"> <div class="top-actions">
{{ HOOK(H_SLIDESHOW_CONTENT_TOOLBAR_ACTIONS_START) }} {{ HOOK(H_FLEET_NODE_PLAYER_TOOLBAR_ACTIONS_START) }}
<div class="explr-selection-actions"> <div class="explr-selection-actions">
<button class="explr-item-edit explr-selection-entity btn-info" data-entity-route="{{ url_for('fleet_node_player_edit', node_player_id='!c!') }}"> <button class="explr-item-edit explr-selection-entity btn-info" data-entity-route="{{ url_for('fleet_node_player_edit', node_player_id='!c!') }}">
@ -49,7 +48,7 @@
<i class="fa fa-folder-plus icon-left"></i> <i class="fa fa-folder-plus icon-left"></i>
{{ l.common_new_folder }} {{ l.common_new_folder }}
</button> </button>
{{ HOOK(H_SLIDESHOW_CONTENT_TOOLBAR_ACTIONS_END) }} {{ HOOK(H_FLEET_NODE_PLAYER_TOOLBAR_ACTIONS_END) }}
</div> </div>
</div> </div>
@ -74,7 +73,7 @@
{% set node_player_children = node_players[folder.id]|default([]) %} {% set node_player_children = node_players[folder.id]|default([]) %}
{% set has_children = folder.children or node_player_children %} {% set has_children = folder.children or node_player_children %}
<li class="icon-folder li-explr-folder li-explr-folder-{{ folder.id }}"> <li class="icon-folder li-explr-folder li-explr-folder-{{ folder.id if folder.id else 0 }}">
<a href="{{ url_for('fleet_node_player_cd') }}?path={{ folder.path }}" class="{% if folder.path == working_folder_path %}active{% endif %}"> <a href="{{ url_for('fleet_node_player_cd') }}?path={{ folder.path }}" class="{% if folder.path == working_folder_path %}active{% endif %}">
{{ folder.name }} {{ folder.name }}
</a> </a>

View File

@ -5,7 +5,7 @@
<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="shortcut icon" href="{{ STATIC_PREFIX }}/favicon.ico">
{% if slide_animation_enabled.eval() %} {% if slide_animation_enabled %}
<link rel="stylesheet" href="{{ STATIC_PREFIX }}css/lib/animate.min.css" /> <link rel="stylesheet" href="{{ STATIC_PREFIX }}css/lib/animate.min.css" />
{% endif %} {% endif %}
<style> <style>
@ -20,7 +20,7 @@
</head> </head>
<body> <body>
<div id="IntroSlide" class="slide" style="z-index: 10000;"> <div id="IntroSlide" class="slide" style="z-index: 10000;">
{% if default_slide_duration > 0 %} {% if intro_slide_duration > 0 %}
<iframe src="/player/default"></iframe> <iframe src="/player/default"></iframe>
{% endif %} {% endif %}
</div> </div>
@ -37,7 +37,7 @@
<script type="text/javascript"> <script type="text/javascript">
// Backend config // Backend config
let items = {{ json_dumps(items) | safe}}; let items = {{ json_dumps(items) | safe}};
const introDuration = {{ default_slide_duration * 1000 }}; const introDuration = {{ intro_slide_duration * 1000 }};
const playlistCheckResolutionMs = {{ polling_interval.eval() * 1000 }}; const playlistCheckResolutionMs = {{ polling_interval.eval() * 1000 }};
// Backend flag updates // Backend flag updates
@ -59,9 +59,9 @@
let pauseClockValue = null; let pauseClockValue = null;
// Animations // Animations
const animate = {{ 'true' if slide_animation_enabled.eval() and not items.preview_mode else 'false' }}; const animate = {{ 'true' if slide_animation_enabled and not items.preview_mode else 'false' }};
const animate_speed = "animate__{{ slide_animation_speed.eval()|default("normal") }}"; const animate_speed = "animate__{{ slide_animation_speed.eval()|default("normal") }}";
const animation_speed_duration = {{ animation_speed_duration[slide_animation_speed.eval()] if slide_animation_enabled.eval() else 0 }}; const animation_speed_duration = {{ animation_speed_duration[slide_animation_speed.eval()] if slide_animation_enabled else 0 }};
const animate_transitions = [ const animate_transitions = [
"animate__{{ slide_animation_entrance_effect.eval()|default("fadeIn") }}", "animate__{{ slide_animation_entrance_effect.eval()|default("fadeIn") }}",
"animate__{{ slide_animation_exit_effect.eval()|default("none") }}" "animate__{{ slide_animation_exit_effect.eval()|default("none") }}"

View File

@ -0,0 +1,113 @@
<div class="horizontal">
<div class="form-holder vertical">
<h3>
{{ l.playlist_panel_about_playlist }}
</h3>
<form class="form" action="{{ url_for('playlist_save') }}" method="POST">
<input type="hidden" name="id" id="playlist-edit-id" value="{{ current_playlist.id }}"/>
<div class="form-group">
<label for="playlist-edit-name">{{ l.playlist_form_label_name }}</label>
<div class="widget">
<input type="text" name="name" id="playlist-edit-name" required="required"
value="{{ current_playlist.name }}"/>
</div>
</div>
<div class="form-group form-group-horizontal">
<label for="playlist-edit-enabled">{{ l.playlist_form_label_enabled }}</label>
<div class="widget">
<div class="toggle">
<input type="checkbox" id="playlist-edit-enabled" name="enabled"
{% if current_playlist.enabled %}checked="checked"{% endif %}>
<label for="playlist-edit-enabled"></label>
</div>
</div>
</div>
<div class="form-group form-group-horizontal">
<label for="playlist-edit-time-sync">{{ l.playlist_form_label_time_sync }}</label>
<div class="widget">
<div class="toggle">
<input type="checkbox" id="playlist-edit-time-sync" name="time_sync"
{% if current_playlist.time_sync %}checked="checked"{% endif %}>
<label for="playlist-edit-time-sync"></label>
</div>
</div>
</div>
<div class="actions actions-right">
<button type="submit" class="btn-success-alt">
<i class="fa fa-save icon-left"></i>
{{ l.common_save }}
</button>
</div>
</form>
<div class="slides-holder">
<h3>
{{ l.playlist_panel_content_management }}
<div>
<button class="btn btn-info slide-add">
<i class="fa fa-plus"></i>
</button>
</div>
</h3>
{% with slides=slides %}
{% include 'slideshow/slides/component/table.jinja.html' %}
{% endwith %}
</div>
</div>
<div class="preview-holder vertical">
<div class="form-group">
<h4>
URL
</h4>
<p>
{{ l.playlist_form_preview_url_desc }}
</p>
{% set preview_url = request.scheme ~ '://' ~ request.headers.get('host') ~ url_for('player_use', playlist_slug_or_id=current_playlist.slug) %}
<div class="widget">
<input type="text" name="name" id="playlist-preview-url" required="required" value="{{ preview_url }}"
class="disabled" disabled="disabled"/>
<button class="btn btn-naked copy-link" data-target-id="playlist-preview-url">
<i class="fa fa-copy"></i>
</button>
<a href="{{ preview_url }}" class="btn btn-neutral" target="_blank">
<i class="fa-solid fa-up-right-from-square"></i>
</a>
</div>
<h4 class="divide">
QR Code
</h4>
<p>
{{ l.playlist_form_preview_qrcode_desc }}
</p>
<div id="qrcode" class="qrcode-pic" data-qrcode-payload="{{ preview_url }}"></div>
</div>
<h4 class="divide">
Iframe
</h4>
<p>
{{ l.playlist_form_preview_iframe_desc }}
</p>
<div class="preview">
<button class="btn btn-info btn-naked"
onclick="$(this).parent().find('iframe').removeClass('hidden');$(this).addClass('hidden');">
<i class="fa fa-eye icon-left"></i> {{ l.playlist_panel_preview_action }}
</button>
<iframe src="{{ preview_url }}?intro=0&animation=0" frameborder="0" class="hidden"></iframe>
</div>
</div>
</div>

View File

@ -1,86 +1,46 @@
<table class="{{ tclass }}-playlists"> <div class="tiles playlists">
<thead> <div class="tiles-inner">
<tr>
<th>{{ l.playlist_panel_th_name }}</th>
{% if AUTH_ENABLED %}
<th class="tac">
<i class="fa fa-user"></i>
</th>
{% endif %}
<th class="tac"><i class="fa fa-compass"></i></th>
<th class="tac">{{ l.playlist_panel_th_enabled }}</th>
<th class="tac">{{ l.playlist_panel_th_duration }}</th>
<th class="tac">{{ l.playlist_panel_th_activity }}</th>
</tr>
</thead>
<tbody>
<tr class="empty-tr {% if playlists|length != 0 %}hidden{% endif %}">
<td colspan="4">
{{ l.playlist_panel_empty|replace(
'%link%',
('<a href="javascript:void(0);" class="item-add playlist-add">'~l.playlist_button_add~'</a>')|safe
) }}
</td>
</tr>
{% for playlist in playlists %} {% for playlist in playlists %}
<tr class="playlist-item" data-level="{{ playlist.id }}" data-entity="{{ playlist.to_json({"created_by": track_created(playlist).username, "updated_by": track_updated(playlist).username}) }}"> {% set active = current_playlist and ''~playlist.id == ''~current_playlist.id %}
<td class="infos"> <a href="{{ url_for('playlist_list', playlist_id=playlist.id) }}"
<div class="inner"> class="{% if active %}active{% endif %} tile-item {% if not playlist.enabled %}disabled{% endif %} playlist-item"
{% if playlist.id %} data-level="{{ playlist.id }}"
<div class="badge"><i class="fa fa-key icon-left"></i> {{ playlist.id }}</div> data-entity="{{ playlist.to_json({"created_by": track_created(playlist).username, "updated_by": track_updated(playlist).username}) }}">
{% else %} <div class="tile-header">
<div class="badge"><i class="fa fa-lock"></i></div> <div class="head-icon">
{% endif %} <i class="fa {{ 'fa fa-circle' if active else 'fa fa-circle' }}"></i>
<i class="fa fa-bars-staggered icon-left"></i>
{{ playlist.name }}
</div> </div>
</td> </div>
{% if AUTH_ENABLED %} <div class="tile-body">
<td class="tac"> {% set title = playlist.name|trim %}
{% if playlist.id %} {% set title = title if title|length > 0 %}
{% set creator = track_created(playlist) %} {{ truncate((title)|default(l.common_untitled), 35, '...') }}
{% if creator.username %} </div>
<a href="javascript:void(0);" class="badge item-utrack playlist-utrack {% if not creator.enabled %}anonymous{% endif %}"> <div class="tile-footer">
{{ creator.username }} <div class="foot-span">
</a>
{% endif %}
{% endif %}
</td>
{% endif %}
<td class="tac">
{% if playlist.time_sync %}
<i class="fa fa-check"></i>
{% else %}
<i class="fa fa-times"></i>
{% endif %}
</td>
<td class="tac">
{% if playlist.id %}
<label class="pure-material-switch">
<input type="checkbox" {% if playlist.enabled %}checked="checked"{% endif %}><span></span>
</label>
{% endif %}
</td>
<td class="tac">
{% set total_duration = seconds_to_hhmmss(durations[playlist.id]) %} {% set total_duration = seconds_to_hhmmss(durations[playlist.id]) %}
{% if total_duration %} {% if total_duration %}
{{ total_duration }} {{ total_duration }}
{% else %} {% else %}
{{ l.common_empty }} {{ l.common_empty }}
{% endif %} {% endif %}
</td> </div>
<td class="actions tac"> {% if AUTH_ENABLED %}
{% if playlist.id %} {% if playlist.id %}
<a href="javascript:void(0);" class="item-edit playlist-edit"> {% set creator = track_created(playlist) %}
<i class="fa fa-pencil"></i> {% if creator.username %}
</a> <div class="foot-span {% if not creator.enabled %}anonymous{% endif %}">
<a href="javascript:void(0);" class="item-delete playlist-delete"> {{ creator.username }}
<i class="fa fa-trash"></i> </div>
</a>
{% endif %} {% endif %}
</td> {% endif %}
</tr> {% endif %}
</div>
</a>
{% endfor %} {% endfor %}
</tbody>
</table> <div class="inner-empty empty-flag {% if playlists|length != 0 %}hidden{% endif %}">
<i class="fa fa-play"></i>
</div>
</div>
</div>

View File

@ -6,13 +6,34 @@
{% block add_css %} {% block add_css %}
{{ HOOK(H_PLAYLIST_CSS) }} {{ HOOK(H_PLAYLIST_CSS) }}
<link rel="stylesheet" href="{{ STATIC_PREFIX }}css/lib/flatpickr.min.css" />
{% 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 = {
'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 }}',
};
var contents = {{ json_dumps(contents) | safe }}
</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/qrcode.min.js"></script>
<script src="{{ STATIC_PREFIX }}js/slideshow/slides.js"></script>
<script src="{{ STATIC_PREFIX }}js/playlist/playlists.js"></script> <script src="{{ STATIC_PREFIX }}js/playlist/playlists.js"></script>
<script src="{{ STATIC_PREFIX }}js/restart.js"></script>
{{ HOOK(H_PLAYLIST_JAVASCRIPT) }} {{ HOOK(H_PLAYLIST_JAVASCRIPT) }}
{% endblock %} {% endblock %}
{% block body_class %}view-playlist-list{% endblock %}
{% block page %} {% block page %}
<div class="top-content"> <div class="top-content">
@ -26,21 +47,49 @@
<i class="fa fa-play icon-left"></i> <i class="fa fa-play icon-left"></i>
{{ l.playlist_button_add }} {{ l.playlist_button_add }}
</button> </button>
{% if current_playlist %}
<a href="{{ url_for('playlist_delete', playlist_id=current_playlist.id) }}" class="btn btn-danger-alt protected">
<i class="fa fa-trash"></i>
</a>
{% endif %}
{{ HOOK(H_PLAYLIST_TOOLBAR_ACTIONS_END) }} {{ HOOK(H_PLAYLIST_TOOLBAR_ACTIONS_END) }}
</div> </div>
</div> </div>
<div class="alert alert-info tiles-empty empty-flag {% if playlists|length != 0 %}hidden{% endif %}">
{{ l.playlist_panel_empty|replace(
'%link%',
('<a href="javascript:void(0);" class="item-add playlist-add">'~l.playlist_button_add~'</a>')|safe
) }}
</div>
{% if request.args.get('refresh_player') %}
<div class="alert alert-success">
<i class="fa fa-refresh icon-left"></i>
{{ l.slideshow_slide_refresh_player_success|replace('%time%', request.args.get('refresh_player')) }}
</div>
{% endif %}
{% if error %}
<div class="alert alert-danger">
{{ l[error] }}
</div>
{% endif %}
<div class="bottom-content"> <div class="bottom-content">
<div class="page-content"> <div class="page-panel left-panel">
<div class="inner">
<div class="panel">
<div class="panel-body">
<h3>{{ l.sysinfo_panel_title }}</h3>
{% with playlists=playlists %} {% with playlists=playlists %}
{% include 'playlist/component/table.jinja.html' %} {% include 'playlist/component/table.jinja.html' %}
{% endwith %} {% endwith %}
</div> </div>
</div>
<div class="page-content">
<div class="inner">
{% with playlists=playlists %}
{% include 'playlist/component/edit.jinja.html' %}
{% endwith %}
</div> </div>
</div> </div>
</div> </div>
@ -49,6 +98,9 @@
<div class="modals-outer"> <div class="modals-outer">
<div class="modals-inner"> <div class="modals-inner">
{% include 'playlist/modal/add.jinja.html' %} {% include 'playlist/modal/add.jinja.html' %}
{% include 'playlist/modal/qrcode.jinja.html' %}
{% include 'slideshow/slides/modal/add.jinja.html' %}
{% include 'slideshow/slides/modal/edit.jinja.html' %}
{% include 'core/utrack.jinja.html' %} {% include 'core/utrack.jinja.html' %}
</div> </div>
</div> </div>

View File

@ -1,37 +0,0 @@
<div class="modal modal-playlist-edit modal-playlist hidden">
<h2>
{{ l.playlist_form_edit_title }}
</h2>
<form class="form" action="/playlist/edit" method="POST">
<input type="hidden" name="id" id="playlist-edit-id" />
<div class="form-group">
<label for="playlist-edit-name">{{ l.playlist_form_label_name }}</label>
<div class="widget">
<input type="text" name="name" id="playlist-edit-name" required="required" />
</div>
</div>
<div class="form-group">
<label for="playlist-edit-time-sync">{{ l.playlist_form_label_time_sync }}</label>
<div class="widget">
<select name="time_sync" type="text" id="playlist-edit-time-sync" required="required">
<option value="1"></option>
<option value="0"></option>
</select>
</div>
</div>
<div class="actions">
<button type="button" class="btn-normal modal-close">
{{ l.playlist_form_button_cancel }}
</button>
<button type="submit" class="green">
<i class="fa fa-save icon-left"></i>{{ l.playlist_form_edit_submit }}
</button>
</div>
</form>
</div>

View File

@ -0,0 +1,13 @@
<div class="modal modal-playlist-qrcode modal-playlist">
<h2>
{{ l.playlist_form_show_qrcode }}
</h2>
<div id="qrcode" class="qrcode-pic"></div>
<div class="actions actions-center">
<button type="button" class="btn btn-naked modal-close">
<i class="fa fa-close icon-left"></i>{{ l.common_close }}
</button>
</div>
</div>

View File

@ -1,77 +1,15 @@
{% extends 'base.jinja.html' %}
{% block page_title %}
{{ l.slideshow_slide_page_title }}
{% endblock %}
{% block add_css %}
<link rel="stylesheet" href="{{ STATIC_PREFIX }}css/lib/flatpickr.min.css" />
{{ HOOK(H_SLIDESHOW_SLIDES_CSS) }}
{% 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 }}',
};
var contents = {{ json_dumps(contents) | safe }}
</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>
<script src="{{ STATIC_PREFIX }}js/restart.js"></script>
{{ HOOK(H_SLIDESHOW_SLIDES_JAVASCRIPT) }}
{% endblock %}
{% block page %} {% block page %}
<div class="toolbar"> <div class="toolbar">
<h2><i class="fa-regular fa-clock icon-left"></i>{{ l.slideshow_slide_page_title }}</h2>
<div class="toolbar-actions"> <div class="toolbar-actions">
{{ HOOK(H_SLIDESHOW_SLIDES_TOOLBAR_ACTIONS_START) }}
<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_slide_goto_player }}">
<i class="fa fa-eye"></i>
</a>
<a href="{{ url_for('slideshow_player_refresh') }}" class="btn" title="{{ l.slideshow_slide_refresh_player }}"> <a href="{{ url_for('slideshow_player_refresh') }}" class="btn" title="{{ l.slideshow_slide_refresh_player }}">
<i class="fa fa-refresh"></i> <i class="fa fa-refresh"></i>
</a> </a>
<button class="purple slide-add item-add"><i class="fa fa-plus icon-left"></i>{{ l.slideshow_slide_button_add }}</button> <button class="purple slide-add item-add"><i class="fa fa-plus icon-left"></i>{{ l.slideshow_slide_button_add }}</button>
{% if PLAYLIST_ENABLED %}
<select class="select-item-picker playlist-picker">
<option value="{{ url_for('slideshow_slide_list') }}" {% if not current_playlist %}selected="selected"{% endif %}>
{{ l.common_default_playlist }}
</option>
{% for playlist in playlists %}
{% set is_active_playlist = str(current_playlist.id) == str(playlist.id) %}
<option value="{{ url_for('slideshow_slide_list_playlist_use', playlist_id=playlist.id) }}" {% if is_active_playlist %}selected="selected"{% endif %}>
{{ playlist.name }}
</option>
{% endfor %}
</select>
{% endif %}
{{ HOOK(H_SLIDESHOW_SLIDES_TOOLBAR_ACTIONS_END) }}
</div> </div>
</div> </div>
{% if request.args.get('refresh_player') %}
<div class="alert alert-success">
<i class="fa fa-refresh icon-left"></i>
{{ l.slideshow_slide_refresh_player_success|replace('%time%', request.args.get('refresh_player')) }}
</div>
{% endif %}
<div class="panel {% if PLAYLIST_ENABLED %}panel-active{% endif %}"> <div class="panel {% if PLAYLIST_ENABLED %}panel-active{% endif %}">
<div class="panel-body"> <div class="panel-body">
@ -82,26 +20,5 @@
{% endwith %} {% endwith %}
</div> </div>
</div> </div>
<div class="panel panel-inactive">
<div class="panel-body">
<h3>{{ l.slideshow_slide_panel_inactive }}</h3>
{% with tclass='inactive', slides=disabled_slides %}
{% include 'slideshow/slides/component/table.jinja.html' %}
{% endwith %}
</div>
</div>
<div class="modals hidden">
<div class="modals-outer">
<a href="javascript:void(0);" class="modal-close">
<i class="fa fa-close"></i>
</a>
<div class="modals-inner">
{% include 'slideshow/slides/modal/add.jinja.html' %}
{% include 'slideshow/slides/modal/edit.jinja.html' %}
{% include 'core/utrack.jinja.html' %}
</div>
</div>
</div>
{% endblock %} {% endblock %}

View File

@ -3,7 +3,7 @@
{{ l.slideshow_slide_form_add_title }} {{ l.slideshow_slide_form_add_title }}
</h2> </h2>
<form class="form" action="/slideshow/slide/add" method="POST" enctype="multipart/form-data"> <form class="form" action="{{ url_for('slideshow_slide_add') }}" method="POST" enctype="multipart/form-data">
<h3> <h3>
{{ l.slideshow_slide_form_section_content }} {{ l.slideshow_slide_form_section_content }}
@ -56,25 +56,27 @@
<div class="form-group object-input"> <div class="form-group object-input">
<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" disabled="disabled" /> <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" /> disabled="disabled"/>
<input type="file" name="object" id="slide-add-object-input-upload"
class="slide-add-object-input hidden" disabled="disabled"/>
</div> </div>
</div> </div>
</div> </div>
<h3> <h3 class="divide">
{{ l.slideshow_slide_form_section_scheduling }} {{ l.slideshow_slide_form_section_scheduling }}
</h3> </h3>
<div class="form-group slide-notification-group"> <div class="form-group slide-notification-group">
<label for="slide-add-is-notification">{{ l.slideshow_slide_form_label_is_notification }}</label> <label for="slide-add-is-notification">{{ l.slideshow_slide_form_label_is_notification }}</label>
<div class="widget"> <div class="widget">
<input type="checkbox" class="trigger slide-is-notification" name="is_notification" id="slide-add-is-notification" /> <input type="checkbox" class="trigger slide-is-notification" name="is_notification"
id="slide-add-is-notification"/>
</div> </div>
</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">
@ -83,8 +85,10 @@
<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> <option value="cron">{{ l.slideshow_slide_form_label_cron_scheduled_cron }}</option>
</select> </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" id="slide-add-cron-schedule-datetimepicker" class="datetimepicker" value=""
<input type="text" name="cron_schedule" id="slide-add-cron-schedule" class="target hidden" placeholder="{{ l.slideshow_slide_form_widget_cron_scheduled_placeholder }}" /> 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>
@ -96,8 +100,10 @@
<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> <option value="cron">{{ l.slideshow_slide_form_label_cron_scheduled_cron }}</option>
</select> </select>
<input type="text" id="slide-add-cron-schedule-end-datetimepicker" class="datetimepicker" value="" placeholder="{{ l.slideshow_slide_form_label_cron_scheduled_datetime_placeholder }}" /> <input type="text" id="slide-add-cron-schedule-end-datetimepicker" class="datetimepicker" value=""
<input type="text" name="cron_schedule_end" id="slide-add-cron-schedule-end" class="target hidden" placeholder="{{ l.slideshow_slide_form_widget_cron_scheduled_placeholder }}" /> placeholder="{{ l.slideshow_slide_form_label_cron_scheduled_datetime_placeholder }}"/>
<input type="text" name="cron_schedule_end" id="slide-add-cron-schedule-end" class="target hidden"
placeholder="{{ l.slideshow_slide_form_widget_cron_scheduled_placeholder }}"/>
</div> </div>
</div> </div>
@ -110,15 +116,16 @@
</div> </div>
<div class="actions"> <div class="actions">
<button type="button" class="btn-normal modal-close"> <button type="button" class="btn btn-naked modal-close">
{{ l.slideshow_slide_form_button_cancel }} {{ l.common_close }}
</button> </button>
<button type="submit" class="green"> <button type="submit" class="btn btn-info">
<i class="fa fa-plus icon-left"></i> {{ l.slideshow_slide_form_add_submit }} <i class="fa fa-save icon-left"></i>{{ l.common_save }}
</button> </button>
<button type="button" disabled="disabled" class="green hidden btn-loading"> <button type="button" disabled="disabled" class="btn btn-naked hidden btn-loading">
{{ l.common_loading }} {{ l.common_loading }}
</button> </button>
</div> </div>
</form> </form>
</div> </div>

View File

@ -4,7 +4,7 @@
</h2> </h2>
<form class="form" action="/slideshow/slide/edit" method="POST"> <form class="form" action="{{ url_for('slideshow_slide_edit') }}" method="POST">
<h3> <h3>
{{ l.slideshow_slide_form_section_content }} {{ l.slideshow_slide_form_section_content }}
@ -22,14 +22,15 @@
</div> </div>
<h3> <h3 class="divide">
{{ l.slideshow_slide_form_section_scheduling }} {{ l.slideshow_slide_form_section_scheduling }}
</h3> </h3>
<div class="form-group slide-notification-group"> <div class="form-group slide-notification-group">
<label for="slide-edit-is-notification">{{ l.slideshow_slide_form_label_is_notification }}</label> <label for="slide-edit-is-notification">{{ l.slideshow_slide_form_label_is_notification }}</label>
<div class="widget"> <div class="widget">
<input type="checkbox" class="trigger slide-is-notification" name="is_notification" id="slide-edit-is-notification" /> <input type="checkbox" class="trigger slide-is-notification" name="is_notification"
id="slide-edit-is-notification"/>
</div> </div>
</div> </div>
@ -41,8 +42,10 @@
<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> <option value="cron">{{ l.slideshow_slide_form_label_cron_scheduled_cron }}</option>
</select> </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" id="slide-edit-cron-schedule-datetimepicker" class="datetimepicker" value=""
<input type="text" name="cron_schedule" id="slide-edit-cron-schedule" class="target hidden" placeholder="{{ l.slideshow_slide_form_widget_cron_scheduled_placeholder }}" /> 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>
@ -53,8 +56,10 @@
<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>
</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=""
<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 }}" /> 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 }}"/>
</div> </div>
</div> </div>
@ -67,11 +72,14 @@
</div> </div>
<div class="actions"> <div class="actions">
<button type="button" class="btn-normal modal-close"> <button type="button" class="btn btn-naked modal-close">
{{ l.slideshow_slide_form_button_cancel }} {{ l.common_close }}
</button> </button>
<button type="submit" class="green"> <button type="submit" class="btn btn-info">
<i class="fa fa-save icon-left"></i>{{ l.slideshow_slide_form_edit_submit }} <i class="fa fa-save icon-left"></i>{{ l.common_save }}
</button>
<button type="button" disabled="disabled" class="btn btn-naked hidden btn-loading">
{{ l.common_loading }}
</button> </button>
</div> </div>
</form> </form>