- Updated various views to replace 'dashboard-card' with 'content-card' for consistency in styling. - Modified filter and table card classes across multiple pages including applications, campaigns, commands, datasets, dayparts, displays, display groups, display profiles, fonts, layouts, libraries, menu boards, modules, player software, playlists, resolutions, schedules, settings, sync groups, tags, tasks, templates, transitions, users, and user groups.
290 lines
14 KiB
Twig
290 lines
14 KiB
Twig
{#
|
|
/**
|
|
* Copyright (C) 2022 Xibo Signage Ltd
|
|
*
|
|
* Xibo - Digital Signage - http://www.xibo.org.uk
|
|
*
|
|
* This file is part of Xibo.
|
|
*
|
|
* Xibo is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* any later version.
|
|
*
|
|
* Xibo is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#}
|
|
{% extends "authed.twig" %}
|
|
{% import "inline.twig" as inline %}
|
|
|
|
{% block title %}{{ "Templates"|trans }} | {% endblock %}
|
|
|
|
{% block actionMenu %}{% endblock %}
|
|
|
|
{% block pageContent %}
|
|
<div class="ots-displays-page">
|
|
<div class="page-header ots-page-header">
|
|
<h1>{% trans "Templates" %}</h1>
|
|
<p class="text-muted">{% trans "Manage your reusable templates." %}</p>
|
|
</div>
|
|
|
|
<div class="widget content-card ots-displays-card">
|
|
<div class="widget-body ots-displays-body">
|
|
<div class="XiboGrid" id="{{ random() }}" data-grid-name="templateView">
|
|
<div class="XiboFilter card mb-3 bg-light content-card ots-filter-card">
|
|
<div class="ots-filter-header">
|
|
<h3 class="ots-filter-title">{% trans "Filter Templates" %}</h3>
|
|
<button type="button" class="ots-filter-toggle" id="ots-filter-collapse-btn" title="{% trans 'Toggle filter panel' %}">
|
|
<i class="fa fa-chevron-down"></i>
|
|
</button>
|
|
</div>
|
|
<div class="ots-filter-content collapsed" id="ots-filter-content">
|
|
<div class="FilterDiv card-body" id="Filter">
|
|
<form class="form-inline">
|
|
{% set title %}{% trans "Name" %}{% endset %}
|
|
{{ inline.inputNameGrid('template', title) }}
|
|
|
|
{% if currentUser.featureEnabled("tag.tagging") %}
|
|
{% set title %}{% trans "Tags" %}{% endset %}
|
|
{% set exactTagTitle %}{% trans "Exact match?" %}{% endset %}
|
|
{% set logicalOperatorTitle %}{% trans "When filtering by multiple Tags, which logical operator should be used?" %}{% endset %}
|
|
{% set helpText %}{% trans "A comma separated list of tags to filter by. Enter a tag|tag value to filter tags with values. Enter --no-tag to filter all items without tags. Enter - before a tag or tag value to exclude from results." %}{% endset %}
|
|
{{ inline.inputWithTags("tags", title, null, helpText, null, null, null, "exactTags", exactTagTitle, logicalOperatorTitle) }}
|
|
{% endif %}
|
|
|
|
{{ inline.hidden("folderId") }}
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<div class="grid-with-folders-container ots-grid-with-folders">
|
|
<div class="grid-folder-tree-container p-3 content-card ots-folder-tree" id="grid-folder-filter">
|
|
<input id="jstree-search" class="form-control" type="text" placeholder="{% trans "Search" %}">
|
|
<div class="form-check">
|
|
<input type="checkbox" class="form-check-input" id="folder-tree-clear-selection-button">
|
|
<label class="form-check-label" for="folder-tree-clear-selection-button" title="{% trans "Search in all folders" %}">{% trans "All Folders" %}</label>
|
|
</div>
|
|
<div class="folder-search-no-results d-none">
|
|
<p>{% trans 'No Folders matching the search term' %}</p>
|
|
</div>
|
|
<div id="container-folder-tree"></div>
|
|
</div>
|
|
<div id="datatable-container">
|
|
<div class="XiboData card py-3 content-card ots-table-card">
|
|
<div class="ots-table-toolbar">
|
|
<button type="button" id="folder-tree-select-folder-button" class="btn btn-sm btn-outline-secondary ots-toolbar-btn" title="{% trans "Open / Close Folder Search options" %}"><i class="fas fa-folder"></i></button>
|
|
<div id="breadcrumbs"></div>
|
|
{% if currentUser.featureEnabled("template.add") %}
|
|
<button class="btn btn-sm btn-success ots-toolbar-btn XiboFormButton" title="{% trans "Add a new Template and jump to the layout editor." %}" href="{{ url_for("template.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
|
{% endif %}
|
|
<button class="btn btn-sm btn-primary ots-toolbar-btn" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
|
</div>
|
|
<table id="templates" class="table table-striped" data-content-type="layout" data-content-id-name="layoutId" data-state-preference-name="templateGrid" style="width: 100%;">
|
|
<thead>
|
|
<tr>
|
|
<th>{% trans "Name" %}</th>
|
|
<th>{% trans "Status" %}</th>
|
|
<th>{% trans "Owner" %}</th>
|
|
<th>{% trans "Description" %}</th>
|
|
{% if currentUser.featureEnabled("tag.tagging") %}<th>{% trans "Tags" %}</th>{% endif %}
|
|
<th>{% trans "Orientation" %}</th>
|
|
<th>{% trans "Thumbnail" %}</th>
|
|
<th>{% trans "Sharing" %}</th>
|
|
<th class="rowMenu"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block javaScript %}
|
|
<script type="text/javascript" nonce="{{ cspNonce }}">
|
|
{% if not currentUser.featureEnabled("folder.view") %}
|
|
disableFolders();
|
|
{% endif %}
|
|
var table = $("#templates").DataTable({
|
|
"language": dataTablesLanguage,
|
|
dom: dataTablesTemplate,
|
|
serverSide: true,
|
|
stateSave: true,
|
|
stateDuration: 0,
|
|
responsive: true,
|
|
stateLoadCallback: dataTableStateLoadCallback,
|
|
stateSaveCallback: dataTableStateSaveCallback,
|
|
filter: false,
|
|
searchDelay: 3000,
|
|
"order": [[ 1, "asc"]],
|
|
ajax: {
|
|
"url": "{{ url_for("template.search") }}",
|
|
"data": function(d) {
|
|
$.extend(d, $("#templates").closest(".XiboGrid").find(".FilterDiv form").serializeObject());
|
|
}
|
|
},
|
|
"columns": [
|
|
{ "data": "layout", responsivePriority: 2},
|
|
{
|
|
"name": "publishedStatus",
|
|
responsivePriority: 2,
|
|
"data": function (data, type) {
|
|
if (data.publishedDate != null) {
|
|
var now = moment();
|
|
var published = moment(data.publishedDate);
|
|
var differenceMinutes = published.diff(now, 'minutes');
|
|
var momentDifference = moment(now).to(published);
|
|
|
|
if (differenceMinutes < -5) {
|
|
return data.publishedStatus.concat(" - ", translations.publishedStatusFailed);
|
|
} else {
|
|
return data.publishedStatus.concat(" - ", translations.publishedStatusFuture + " " + momentDifference);
|
|
}
|
|
} else {
|
|
return data.publishedStatus;
|
|
}
|
|
|
|
}
|
|
},
|
|
{ "data": "owner", responsivePriority: 3},
|
|
{
|
|
"name": "description",
|
|
"data": null,
|
|
responsivePriority: 3,
|
|
"render": {"_": "description", "display": "descriptionWithMarkup", "sort": "description"}
|
|
},
|
|
{% if currentUser.featureEnabled("tag.tagging") %}{
|
|
"sortable": false,
|
|
"visible": false,
|
|
"data": dataTableCreateTags,
|
|
responsivePriority: 3
|
|
},{% endif %}
|
|
{ data: 'orientation', responsivePriority: 10, visible: false},
|
|
{
|
|
responsivePriority: 3,
|
|
data: 'thumbnail',
|
|
render: function (data, type, row) {
|
|
if (type !== 'display') {
|
|
return row.layoutId;
|
|
}
|
|
if (data) {
|
|
return '<a class="img-replace" data-toggle="lightbox" data-type="image" href="' + data + '">' +
|
|
'<img class="img-fluid" src="' + data + '" alt="{{ "Thumbnail"|trans }}" />' +
|
|
'</a>';
|
|
} else {
|
|
var addUrl = '{{ url_for("layout.thumbnail.add", {id: ":id"}) }}'.replace(':id', row.layoutId);
|
|
return '<a class="img-replace generate-layout-thumbnail" href="' + addUrl + '">' +
|
|
'<img class="img-fluid" src="{{ theme.uri("img/thumbs/placeholder.png") }}" alt="{{ "Add Thumbnail"|trans }}" />' +
|
|
'</a>';
|
|
}
|
|
return '';
|
|
},
|
|
sortable: false
|
|
},
|
|
{
|
|
"data": "groupsWithPermissions",
|
|
responsivePriority: 4,
|
|
"render": dataTableCreatePermissions
|
|
},
|
|
{
|
|
"orderable": false,
|
|
responsivePriority: 1,
|
|
"data": dataTableButtonsColumn
|
|
}
|
|
]
|
|
});
|
|
|
|
table.on('draw', dataTableDraw);
|
|
table.on('draw', { form: $("#templates").closest(".XiboGrid").find(".FilterDiv form") } ,dataTableCreateTagEvents);
|
|
table.on('draw', function(e, settings) {
|
|
$('#' + e.target.id + ' .generate-layout-thumbnail').on('click', function(e) {
|
|
e.preventDefault();
|
|
var $anchor = $(this);
|
|
$.ajax({
|
|
url: $anchor.attr('href'),
|
|
method: 'POST',
|
|
success: function() {
|
|
$anchor.find('img').attr('src', $anchor.attr('href'));
|
|
$anchor.removeClass('generate-layout-thumbnail').attr('data-toggle', 'lightbox');
|
|
}
|
|
});
|
|
});
|
|
});
|
|
table.on('processing.dt', dataTableProcessing);
|
|
dataTableAddButtons(table, $('#templates_wrapper').find('.dataTables_buttons'));
|
|
|
|
$("#refreshGrid").click(function () {
|
|
table.ajax.reload();
|
|
});
|
|
|
|
function templateFormOpen() {
|
|
if ($('#folder-tree-form-modal').length === 0) {
|
|
// compile tree folder modal and append it to Form
|
|
var folderTreeModal = templates['folder-tree'];
|
|
var treeConfig = {"container": "container-folder-form-tree", "modal": "folder-tree-form-modal"};
|
|
treeConfig.trans = translations.folderTree;
|
|
$("body").append(folderTreeModal(treeConfig));
|
|
|
|
$("#folder-tree-form-modal").on('hidden.bs.modal', function () {
|
|
// Fix for 2nd/overlay modal
|
|
$('.modal:visible').length && $(document.body).addClass('modal-open');
|
|
|
|
$(this).data('bs.modal', null);
|
|
});
|
|
}
|
|
|
|
// select current working folder if one is selected in the grid
|
|
if ($('#container-folder-tree').jstree("get_selected", true)[0] !== undefined) {
|
|
$('#templateAddForm' + ' #folderId').val($('#container-folder-tree').jstree("get_selected", true)[0].id);
|
|
}
|
|
|
|
initJsTreeAjax($('#folder-tree-form-modal').find('#container-folder-form-tree'), 'templateAddForm', true, 600);
|
|
|
|
$("#templateAddForm").submit(function(e) {
|
|
e.preventDefault();
|
|
var form = $(this);
|
|
|
|
var url = $(this).data().redirect;
|
|
|
|
$.ajax({
|
|
type: $(this).attr("method"),
|
|
url: $(this).attr("action"),
|
|
data: $(this).serialize(),
|
|
cache: false,
|
|
dataType:"json",
|
|
success: function(xhr, textStatus, error) {
|
|
|
|
XiboSubmitResponse(xhr, form);
|
|
|
|
if (xhr.success) {
|
|
// Reload the designer
|
|
XiboRedirect(url.replace(":id", xhr.id));
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function layoutPublishFormOpen() {
|
|
// Nothing to do here, but we use the same form on the layout designer and have a callback registered there
|
|
}
|
|
|
|
function layoutEditFormSaved() {
|
|
// Nothing to do here.
|
|
}
|
|
</script>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %} |