Release v2.5.6
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h2>{{#if isEdit}}Edit announcement{{#if announcement.short_label}} - {{announcement.short_label}}{{/if}}{{else}}Add announcement{{/if}}</h2>
|
||||
<p>{{#if isEdit}}Update the message, screens, color, or expiry behavior.{{else}}Give the announcement a short description, then define the full message and target screens.{{/if}}</p>
|
||||
<p>{{#if isEdit}}Update the message, screen groups, color, or expiry behavior.{{else}}Give the announcement a short description, then define the full message and target screen groups.{{/if}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,41 +13,43 @@
|
||||
<h3 class="card-title">Content</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-4">
|
||||
<label for="announcement-short-label" class="form-label">{{#if isEdit}}Descriptor{{else}}Short Description{{/if}}</label>
|
||||
<input id="announcement-short-label" name="short_label" class="form-control" value="{{announcement.short_label}}" maxlength="255" placeholder="{{#if isEdit}}Power warning{{else}}Announcement Short Name{{/if}}" />
|
||||
<div class="mb-3">
|
||||
<label for="announcement-short-label" class="form-label">Short Description</label>
|
||||
<input id="announcement-short-label" name="short_label" class="form-control" value="{{announcement.short_label}}" maxlength="128" data-limit-text-length placeholder="{{#if isEdit}}Power warning{{else}}Announcement Short Name{{/if}}" />
|
||||
<div class="form-text">A short name for admin lists and page titles.</div>
|
||||
</div>
|
||||
<div class="mb-0">
|
||||
<div class="mb-3">
|
||||
<label for="announcement-message" class="form-label">Message</label>
|
||||
<textarea id="announcement-message" name="message" class="form-control" rows="6" required placeholder="{{#if isEdit}}{{else}}Maintenance starts in 10 minutes.{{/if}}">{{announcement.message}}</textarea>
|
||||
<div class="form-text">Use the full text that should appear on screens.</div>
|
||||
</div>
|
||||
<div class="row g-3 mb-0">
|
||||
<div class="col-12 col-md-6 col-xl-12">
|
||||
<label for="announcement-type" class="form-label">Type</label>
|
||||
<select id="announcement-type" name="announcement_type" class="form-select">
|
||||
{{#each announcementTypes}}
|
||||
<option value="{{value}}" {{#if (eq ../announcement.announcement_type value)}}selected{{/if}}>{{label}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 col-xl-12">
|
||||
<label for="announcement-color" class="form-label">Color</label>
|
||||
<div class="row g-2 align-items-stretch">
|
||||
<div class="col">
|
||||
<select id="announcement-color" name="color_key" class="form-select">
|
||||
{{#each announcementColors}}
|
||||
<option value="{{value}}" {{#if (eq ../announcement.color_key value)}}selected{{/if}}>{{label}}</option>
|
||||
<div class="col-12 col-lg-6">
|
||||
<label id="announcement-type-label" class="form-label">Type</label>
|
||||
<div class="announcement-type-picker" data-announcement-type-picker-shell>
|
||||
<input id="announcement-type" name="announcement_type" type="hidden" value="{{announcement.announcement_type}}" data-announcement-type-value />
|
||||
<button type="button" class="announcement-type-picker__toggle" data-announcement-type-picker-toggle aria-expanded="false" aria-controls="announcement-type-picker-menu" title="Select a type">
|
||||
<span class="announcement-type-picker__toggle-icon" data-announcement-type-picker-icon aria-hidden="true"></span>
|
||||
<span class="announcement-type-picker__toggle-content">
|
||||
<span class="announcement-type-picker__toggle-label" data-announcement-type-picker-label>Select a type</span>
|
||||
<span class="announcement-type-picker__toggle-hint">Click to choose</span>
|
||||
</span>
|
||||
<i class="bi bi-chevron-down announcement-type-picker__caret" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div id="announcement-type-picker-menu" class="announcement-type-picker__menu" data-announcement-type-picker hidden>
|
||||
<div class="announcement-type-picker__grid" role="radiogroup" aria-labelledby="announcement-type-label">
|
||||
{{#each announcementTypes}}
|
||||
<button type="button" class="announcement-type-picker__option {{#if (eq ../announcement.announcement_type value)}}is-selected{{/if}}" data-announcement-type-option data-type-key="{{value}}" data-type-label="{{label}}" data-type-icon="{{icon}}" aria-pressed="{{#if (eq ../announcement.announcement_type value)}}true{{else}}false{{/if}}" title="{{label}}">
|
||||
<span class="announcement-type-picker__option-icon" aria-hidden="true">
|
||||
<i class="bi bi-{{icon}}"></i>
|
||||
</span>
|
||||
<span class="announcement-type-picker__label">{{label}}</span>
|
||||
</button>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="announcement-color-preview" data-announcement-color-preview aria-hidden="true"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 col-xl-12">
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="form-label">Icon</div>
|
||||
<div class="announcement-icon-picker" data-announcement-icon-picker-shell>
|
||||
<select id="announcement-icon" name="icon_key" class="d-none" tabindex="-1" aria-hidden="true">
|
||||
@@ -55,9 +57,12 @@
|
||||
<option value="{{value}}" {{#if (eq ../announcement.icon_key value)}}selected{{/if}}>{{label}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
<button type="button" class="announcement-icon-picker__toggle" data-announcement-icon-picker-toggle aria-expanded="false" aria-controls="announcement-icon-picker-menu">
|
||||
<button type="button" class="announcement-icon-picker__toggle" data-announcement-icon-picker-toggle aria-expanded="false" aria-controls="announcement-icon-picker-menu" title="Select an icon">
|
||||
<span class="announcement-icon-picker__toggle-icon" data-announcement-icon-picker-icon aria-hidden="true"></span>
|
||||
<span class="announcement-icon-picker__toggle-label" data-announcement-icon-picker-label>Select an icon</span>
|
||||
<span class="announcement-icon-picker__toggle-content">
|
||||
<span class="announcement-icon-picker__toggle-label" data-announcement-icon-picker-label>Select an icon</span>
|
||||
<span class="announcement-icon-picker__toggle-hint">Click to choose</span>
|
||||
</span>
|
||||
<i class="bi bi-chevron-down announcement-icon-picker__caret" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div id="announcement-icon-picker-menu" class="announcement-icon-picker__menu" data-announcement-icon-picker hidden>
|
||||
@@ -76,6 +81,22 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label id="announcement-color-label" class="form-label">Color</label>
|
||||
<div class="announcement-color-picker" data-announcement-color-picker-shell>
|
||||
<input id="announcement-color" name="color_key" type="hidden" value="{{announcement.color_key}}" data-announcement-color-value />
|
||||
<div class="announcement-color-picker__grid" role="radiogroup" aria-labelledby="announcement-color-label">
|
||||
{{#each announcementColors}}
|
||||
<button type="button" class="announcement-color-picker__option {{#if (eq ../announcement.color_key value)}}is-selected{{/if}}" data-announcement-color-option data-color-key="{{value}}" data-color-label="{{label}}" aria-pressed="{{#if (eq ../announcement.color_key value)}}true{{else}}false{{/if}}" title="{{label}}">
|
||||
<span class="announcement-color-picker__swatch {{#if (eq value 'light')}}text-bg-light text-dark{{else}}text-bg-{{value}}{{/if}}" aria-hidden="true">
|
||||
<i class="bi bi-check-lg announcement-color-picker__check" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span class="announcement-color-picker__label">{{label}}</span>
|
||||
</button>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -84,16 +105,16 @@
|
||||
<div class="card card-outline card-primary admin-form-card mb-3">
|
||||
<div class="card-footer d-flex flex-wrap align-items-center">
|
||||
{{#if isEdit}}
|
||||
<button type="submit" class="btn {{announcement.actionClassName}}" form="{{actionFormId}}"><i class="bi {{announcement.actionIcon}} me-1" aria-hidden="true"></i>{{announcement.actionLabel}}</button>
|
||||
<button type="submit" class="btn {{actionClassName}}" form="{{actionFormId}}" {{#if actionDisabled}}disabled title="{{actionDisabledTitle}}"{{/if}}><i class="bi {{actionIcon}} me-1" aria-hidden="true"></i>{{actionLabel}}</button>
|
||||
{{/if}}
|
||||
<div class="ms-auto d-flex flex-wrap align-items-center gap-2">
|
||||
{{{saveActionButtons formId="announcement-form" saveUrl=formAction cancelUrl=cancelUrl deleteUrl=deleteUrl deleteDisabled=deleteDisabled deleteTitle=announcement.deleteBlockedMessage showDelete=showDeleteAction showSaveAndClose=showSaveSecondaryActions showSaveAndNew=showSaveSecondaryActions}}}
|
||||
{{{saveActionButtons formId="announcement-form" saveUrl=formAction cancelUrl=cancelUrl deleteUrl=deleteUrl deleteConfirmMessage=deleteConfirmMessage deleteDisabled=deleteDisabled deleteTitle=deleteTitle showDelete=showDeleteAction showSaveAndClose=showSaveSecondaryActions showSaveAndNew=showSaveSecondaryActions}}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card card-outline card-secondary admin-form-card mb-4">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Screens</h3>
|
||||
<h3 class="card-title">Screen Groups</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-3 mb-0">
|
||||
@@ -119,14 +140,27 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-4 col-xl-12">
|
||||
<label class="form-label" id="announcement-screens-label">Target screen groups</label>
|
||||
<div class="announcement-screen-actions">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary announcement-screen-action" data-announcement-screen-select-all>Select all</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary announcement-screen-action" data-announcement-screen-select-none>Select none</button>
|
||||
</div>
|
||||
<div class="announcement-screen-picker" role="group" aria-labelledby="announcement-screens-label">
|
||||
{{#each announcementScreens}}
|
||||
<div class="announcement-screen-option">
|
||||
<input class="btn-check" type="checkbox" id="announcement-screen-{{id}}" name="screen_ids[]" value="{{id}}" autocomplete="off" {{#if isSelected}}checked{{/if}} />
|
||||
<label class="announcement-screen-chip" for="announcement-screen-{{id}}" title="{{name}}">
|
||||
<span class="announcement-screen-chip__text">{{name}}</span>
|
||||
<span class="announcement-screen-chip__check" aria-hidden="true">
|
||||
<i class="bi bi-check-lg announcement-screen-chip__check-icon"></i>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<label for="announcement-screens" class="form-label">Target screens</label>
|
||||
<select id="announcement-screens" name="screen_ids[]" class="form-select" multiple size="8" required>
|
||||
{{#each announcementScreens}}
|
||||
<option value="{{id}}" {{#if isSelected}}selected{{/if}}>{{name}}{{#if slug}} ({{slug}}){{/if}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
<div class="form-text">Select one or more screens for this announcement.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -136,5 +170,4 @@
|
||||
|
||||
{{#if isEdit}}
|
||||
<form id="announcement-action-form" method="post" action="{{announcement.actionPath}}" class="d-none" data-confirm-message="{{announcement.actionConfirmMessage}}" data-async-command></form>
|
||||
<form id="delete-announcement-form" method="post" action="/announcements/{{announcement.id}}/delete" class="d-none" data-confirm-message="Delete this announcement?"></form>
|
||||
{{/if}}
|
||||
@@ -23,7 +23,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-table-sort-key="description">Description</th>
|
||||
<th>Screens</th>
|
||||
<th>Screen Groups</th>
|
||||
<th data-table-sort-key="type">Type</th>
|
||||
<th data-table-sort-key="color">Color</th>
|
||||
<th data-table-sort-key="status">Status</th>
|
||||
@@ -47,7 +47,7 @@
|
||||
</div>
|
||||
{{!-- <div class="text-secondary small text-break">{{#if message}}{{truncateText message 50}}{{else}}No message provided.{{/if}}</div> --}}
|
||||
</td>
|
||||
<td data-label="Screens" class="text-break">
|
||||
<td data-label="Screen Groups" class="text-break">
|
||||
<span class="small text-secondary">{{screenTargetsLabel}}</span>
|
||||
</td>
|
||||
<td data-label="Type">{{announcement_type}}</td>
|
||||
@@ -66,17 +66,20 @@
|
||||
{{/if}}
|
||||
</td>
|
||||
<td data-label="Actions">
|
||||
{{#if (anyPermission ../currentUser 'announcements.update' 'announcements.delete')}}
|
||||
{{#if (anyPermission ../currentUser 'announcements.create' 'announcements.update' 'announcements.delete')}}
|
||||
<div class="actions">
|
||||
{{#if (hasPermission ../currentUser 'announcements.update')}}
|
||||
<form class="inline-form" method="post" action="{{actionButtonPath}}" data-confirm-message="{{actionButtonConfirmMessage}}" data-async-command>
|
||||
<button class="btn btn-sm {{actionButtonClassName}}" type="submit"><i class="bi {{actionButtonIcon}} me-1" aria-hidden="true"></i>{{actionButtonLabel}}</button>
|
||||
<button class="btn btn-sm {{actionButtonClassName}}" type="submit" {{#if actionButtonDisabled}}disabled title="{{actionButtonDisabledTitle}}"{{/if}}><i class="bi {{actionButtonIcon}} me-1" aria-hidden="true"></i>{{actionButtonLabel}}</button>
|
||||
</form>
|
||||
<a class="btn btn-sm btn-primary" href="/announcements/{{id}}/edit">Edit</a>
|
||||
{{/if}}
|
||||
{{#if (hasPermission ../currentUser 'announcements.create')}}
|
||||
<a class="btn btn-sm btn-secondary" href="/announcements/{{id}}/duplicate">Dupe</a>
|
||||
{{/if}}
|
||||
{{#if (hasPermission ../currentUser 'announcements.delete')}}
|
||||
<form class="inline-form" method="post" action="/announcements/{{id}}/delete" data-confirm-message="Delete this announcement?">
|
||||
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
|
||||
<button class="btn btn-sm {{#if deleteDisabled}}btn-outline-danger{{else}}btn-danger{{/if}}" type="submit" {{#if deleteDisabled}}disabled title="{{deleteBlockedMessage}}"{{/if}}>Delete</button>
|
||||
</form>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
@@ -13,18 +13,21 @@
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label for="canvas-size-name" class="form-label">Name</label>
|
||||
<input id="canvas-size-name" name="name" class="form-control" value="{{canvasSize.name}}" required />
|
||||
<input id="canvas-size-name" name="name" type="text" maxlength="255" data-limit-text-length class="form-control" value="{{canvasSize.name}}" required />
|
||||
</div>
|
||||
<div class="row g-3">
|
||||
<div class="col-12 col-md-6">
|
||||
<label for="canvas-size-width" class="form-label">Width</label>
|
||||
<input id="canvas-size-width" name="width" type="number" min="1" class="form-control" value="{{canvasSize.width}}" required />
|
||||
<input id="canvas-size-width" name="width" type="number" min="1" max="{{maxDimension}}" data-limit-input-value class="form-control" value="{{canvasSize.width}}" {{#if dimensionsLocked}}disabled{{/if}} required />
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<label for="canvas-size-height" class="form-label">Height</label>
|
||||
<input id="canvas-size-height" name="height" type="number" min="1" class="form-control" value="{{canvasSize.height}}" required />
|
||||
<input id="canvas-size-height" name="height" type="number" min="1" max="{{maxDimension}}" data-limit-input-value class="form-control" value="{{canvasSize.height}}" {{#if dimensionsLocked}}disabled{{/if}} required />
|
||||
</div>
|
||||
</div>
|
||||
{{#if dimensionsLocked}}
|
||||
<div class="form-text mt-2">Dimensions are locked while this canvas size is used by templates.</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="card-footer d-flex justify-content-end">
|
||||
{{{saveActionButtons formId="canvas-size-form" saveUrl=formAction cancelUrl=cancelUrl deleteUrl=deleteUrl deleteConfirmMessage=deleteConfirmMessage deleteDisabled=deleteDisabled}}}
|
||||
|
||||
@@ -36,11 +36,14 @@
|
||||
<td data-label="Dimensions">{{width}}x{{height}}</td>
|
||||
<td data-label="Templates"><span class="chip">{{templateCount}} template{{#if (gt templateCount 1)}}s{{else}}{{/if}}{{#if (lt templateCount 1)}}s{{/if}}</span></td>
|
||||
<td data-label="Actions">
|
||||
{{#if (anyPermission ../currentUser 'canvas-sizes.update' 'canvas-sizes.delete')}}
|
||||
{{#if (anyPermission ../currentUser 'canvas-sizes.create' 'canvas-sizes.update' 'canvas-sizes.delete')}}
|
||||
<div class="actions">
|
||||
{{#if (hasPermission ../currentUser 'canvas-sizes.update')}}
|
||||
<a class="btn btn-sm btn-primary" href="/canvas-sizes/{{id}}/edit">Edit</a>
|
||||
{{/if}}
|
||||
{{#if (hasPermission ../currentUser 'canvas-sizes.create')}}
|
||||
<a class="btn btn-sm btn-secondary" href="/canvas-sizes/{{id}}/duplicate">Dupe</a>
|
||||
{{/if}}
|
||||
{{#if (hasPermission ../currentUser 'canvas-sizes.delete')}}
|
||||
<form class="inline-form" method="post" action="/canvas-sizes/{{id}}/delete" data-confirm-message="Delete this canvas size?">
|
||||
{{#if (gt templateCount 0)}}
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<div class="screen-command-panel">
|
||||
<div class="screen-command-panel-left">
|
||||
<div class="screen-command-selector-field">
|
||||
<label class="screen-command-selector-label" for="screen-command-select">Target screen</label>
|
||||
<label class="screen-command-selector-label" for="screen-command-select">Target screen group</label>
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text" aria-hidden="true"><i class="bi bi-broadcast"></i></span>
|
||||
<select
|
||||
id="screen-command-select"
|
||||
class="form-select"
|
||||
aria-label="Target screen"
|
||||
aria-label="Target screen group"
|
||||
data-screen-command-select
|
||||
>
|
||||
{{#each screens}}
|
||||
@@ -57,13 +57,13 @@
|
||||
<div class="screen-command-panel-right">
|
||||
<div class="screen-command-summary">
|
||||
<span class="screen-command-pill is-live" data-screen-command-pill>Choose a screen</span>
|
||||
<strong data-screen-command-name>Select a target screen</strong>
|
||||
<span class="screen-command-summary-meta" data-screen-command-meta>Commands update automatically for the selected screen.</span>
|
||||
<strong data-screen-command-name>Select a target screen group</strong>
|
||||
<span class="screen-command-summary-meta" data-screen-command-meta>Commands update automatically for the selected screen group.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="empty">No screens are available to target yet.</div>
|
||||
<div class="empty">No screen groups are available to target yet.</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
@@ -84,7 +84,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-table-sort-key="client">Client</th>
|
||||
<th data-table-sort-key="screen">Current Screen</th>
|
||||
<th data-table-sort-key="screen">Current Screen Group</th>
|
||||
<th data-table-sort-key="slide">Current Slide</th>
|
||||
<th data-table-sort-key="ip">IP</th>
|
||||
<th data-table-sort-key="viewport">Viewport</th>
|
||||
@@ -99,7 +99,7 @@
|
||||
<td data-label="Client" class="client-rename-cell" title="Double-click to rename">
|
||||
<div>{{#if client_name}}{{client_name}}{{else}}Unknown{{/if}}</div>
|
||||
</td>
|
||||
<td data-label="Current Screen">
|
||||
<td data-label="Current Screen Group">
|
||||
<div>{{#if screen_name}}{{screen_name}}{{else}}Unknown{{/if}}</div>
|
||||
</td>
|
||||
<td data-label="Current Slide">
|
||||
@@ -135,7 +135,7 @@
|
||||
<input type="hidden" name="connectionId" value="{{id}}" />
|
||||
<button type="submit" class="btn btn-sm btn-danger" aria-label="Reload client" title="Reload client"><i class="bi bi-arrow-repeat" aria-hidden="true"></i></button>
|
||||
</form>
|
||||
<button type="button" class="btn btn-sm btn-danger" data-action="move-screen" data-bs-toggle="modal" data-bs-target="#client-move-screen-modal" aria-label="Move client to another screen" title="Move client to another screen"><i class="bi bi-display" aria-hidden="true"></i></button>
|
||||
<button type="button" class="btn btn-sm btn-danger" data-action="move-screen" data-bs-toggle="modal" data-bs-target="#client-move-screen-modal" aria-label="Move client to another screen group" title="Move client to another screen group"><i class="bi bi-display" aria-hidden="true"></i></button>
|
||||
<form method="post" action="/clients/{{screen_slug}}/commands" class="inline-form" data-async-command>
|
||||
<input type="hidden" name="command" value="previous" />
|
||||
<input type="hidden" name="connectionId" value="{{id}}" />
|
||||
@@ -174,7 +174,7 @@
|
||||
<form id="client-move-screen-form" method="post" action="#" class="d-flex flex-column">
|
||||
<div class="modal-header">
|
||||
<div>
|
||||
<h2 class="modal-title fs-5" id="client-move-screen-modal-label">Move client to another screen</h2>
|
||||
<h2 class="modal-title fs-5" id="client-move-screen-modal-label">Move client to another screen group</h2>
|
||||
</div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
@@ -183,15 +183,15 @@
|
||||
<input type="hidden" name="connectionId" value="" data-client-move-connection-id />
|
||||
<input type="hidden" name="deviceId" value="" data-client-move-device-id />
|
||||
<input type="hidden" name="clientName" value="" data-client-move-client-name />
|
||||
<label class="form-label" for="client-move-screen-target">Target screen</label>
|
||||
<label class="form-label" for="client-move-screen-target">Target screen group</label>
|
||||
<select class="form-select" id="client-move-screen-target" name="targetScreenSlug" data-client-move-target required>
|
||||
{{#if screens.length}}
|
||||
<option value="">Select a screen</option>
|
||||
<option value="">Select a screen group</option>
|
||||
{{#each screens}}
|
||||
<option value="{{slug}}">{{name}}</option>
|
||||
{{/each}}
|
||||
{{else}}
|
||||
<option value="">No screens available</option>
|
||||
<option value="">No screen groups available</option>
|
||||
{{/if}}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h2>Dashboard</h2>
|
||||
<p>Monitor screens and quick actions from one place.</p>
|
||||
<p>Monitor screen groups and quick actions from one place.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
<div>
|
||||
<span class="dashboard-hero-kicker">Live overview</span>
|
||||
<h3 class="dashboard-hero-title">Keep the control surface focused on live state and actions.</h3>
|
||||
<p class="dashboard-hero-copy">Use the cards below for the current totals, then open the screen snapshot when you want a quick read on playlist assignment and live connections.</p>
|
||||
<p class="dashboard-hero-copy">Use the cards below for the current totals, then open the screen group snapshot when you want a quick read on playlist assignment and live connections.</p>
|
||||
{{#if primaryPlayerUrl}}
|
||||
<div class="alert alert-info mb-0 mt-3 py-2">
|
||||
<strong>Begin onboarding:</strong>
|
||||
open <a href="{{primaryPlayerUrl}}" target="_blank" rel="noreferrer">{{primaryPlayerUrl}}</a>
|
||||
on the public screen to get started.
|
||||
on the public display to get started.
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
@@ -29,7 +29,7 @@
|
||||
{{#if (hasPermission currentUser "screens.read")}}
|
||||
<div class="dashboard-hero-stat">
|
||||
<span class="dashboard-hero-stat-value">{{screens.length}}</span>
|
||||
<span class="dashboard-hero-stat-label">screens</span>
|
||||
<span class="dashboard-hero-stat-label">screen groups</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if (hasPermission currentUser "clients.read")}}
|
||||
@@ -117,11 +117,11 @@
|
||||
<div class="card card-outline card-secondary dashboard-screen-card-shell">
|
||||
<div class="card-header dashboard-screen-card-header">
|
||||
<div class="dashboard-card-heading">
|
||||
<h3 class="card-title">Screen snapshot</h3>
|
||||
<h3 class="card-title">Screen group snapshot</h3>
|
||||
<p class="dashboard-card-subtitle">Player URL, playlist assignment,
|
||||
and live connection count without the spreadsheet feel.</p>
|
||||
</div>
|
||||
<a class="btn btn-sm btn-outline-primary" href="/screens">Manage screens</a>
|
||||
<a class="btn btn-sm btn-primary" href="/screens">Manage screen groups</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="dashboard-screens-grid" class="dashboard-screen-grid">
|
||||
@@ -158,7 +158,7 @@
|
||||
</article>
|
||||
{{/each}}
|
||||
{{else}}
|
||||
<div class="empty dashboard-screen-empty">No screens yet.</div>
|
||||
<div class="empty dashboard-screen-empty">No screen groups yet.</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<div class="row g-3 align-items-end">
|
||||
<div class="col-12 col-lg-7">
|
||||
<label for="playlist-name" class="form-label">Name</label>
|
||||
<input id="playlist-name" name="name" class="form-control" value="{{playlist.name}}" placeholder="Lobby loop" required />
|
||||
<input id="playlist-name" name="name" class="form-control" value="{{playlist.name}}" maxlength="128" data-limit-text-length placeholder="Lobby loop" required />
|
||||
</div>
|
||||
<div class="col-12 col-lg-5">
|
||||
<label for="playlist-canvas-size" class="form-label">Canvas size</label>
|
||||
@@ -66,7 +66,7 @@
|
||||
</div>
|
||||
<div class="card-body p-0 playlist-items-table-responsive">
|
||||
<table class="table table-striped w-100 mb-0">
|
||||
<thead><tr><th>Order</th><th>Slide</th><th>Schedule</th><th>Config</th><th>Actions</th></tr></thead>
|
||||
<thead><tr><th>Order</th><th>Slide</th><th>Schedule</th><th>Playback</th><th>Actions</th></tr></thead>
|
||||
<tbody id="playlist-items-body" data-playlist-id="{{playlist.id}}" data-playlist-canvas-id="{{playlistCanvasId}}">
|
||||
{{#if playlistSlides.length}}
|
||||
{{#each playlistSlides}}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<tr>
|
||||
<th data-table-sort-key="name">Name</th>
|
||||
<th data-table-sort-key="slides">Slides</th>
|
||||
<th data-table-sort-key="screens">Screens</th>
|
||||
<th data-table-sort-key="screens">Screen Groups</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -34,13 +34,16 @@
|
||||
<tr data-table-search-row>
|
||||
<td data-label="Name">{{name}}</td>
|
||||
<td data-label="Slides"><span class="chip">{{slideCount}} slide{{#if (gt slideCount 1)}}s{{else}}{{/if}}{{#if (lt slideCount 1)}}s{{/if}}</span></td>
|
||||
<td data-label="Screens"><span class="chip">{{screenCount}} screen{{#if (gt screenCount 1)}}s{{else}}{{/if}}{{#if (lt screenCount 1)}}s{{/if}}</span></td>
|
||||
<td data-label="Screen Groups"><span class="chip">{{screenCount}} screen group{{#if (eq screenCount 1)}}{{else}}s{{/if}}</span></td>
|
||||
<td data-label="Actions">
|
||||
{{#if (anyPermission ../currentUser 'playlists.update' 'playlists.delete')}}
|
||||
{{#if (anyPermission ../currentUser 'playlists.create' 'playlists.update' 'playlists.delete')}}
|
||||
<div class="actions">
|
||||
{{#if (hasPermission ../currentUser 'playlists.update')}}
|
||||
<a class="btn btn-sm btn-primary" href="/playlists/{{id}}/edit">Edit</a>
|
||||
{{/if}}
|
||||
{{#if (hasPermission ../currentUser 'playlists.create')}}
|
||||
<a class="btn btn-sm btn-secondary" href="/playlists/{{id}}/duplicate">Dupe</a>
|
||||
{{/if}}
|
||||
{{#if (hasPermission ../currentUser 'playlists.delete')}}
|
||||
<form class="inline-form" method="post" action="/playlists/{{id}}/delete" data-confirm-message="Delete this playlist?">
|
||||
{{#if (gt screenCount 0)}}
|
||||
|
||||
@@ -6,9 +6,8 @@
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-4 ms-auto flex-shrink-0">
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" data-remove-schedule-rule data-confirm-message="Remove this rule?">Remove</button>
|
||||
<button type="button" class="btn btn-tool p-0 text-body-secondary" data-lte-toggle="card-collapse" aria-label="Collapse rule {{ruleIndex}}" title="Collapse rule {{ruleIndex}}">
|
||||
<i class="bi bi-dash-lg" aria-hidden="true" data-lte-icon="collapse"></i>
|
||||
<i class="bi bi-plus-lg" aria-hidden="true" data-lte-icon="expand"></i>
|
||||
<button type="button" class="btn btn-tool p-0 text-body-secondary" data-lte-toggle="card-collapse" aria-expanded="{{#if isCollapsed}}false{{else}}true{{/if}}" aria-label="Collapse rule {{ruleIndex}}" title="Collapse rule {{ruleIndex}}">
|
||||
<i class="bi {{#if isCollapsed}}bi-plus-lg{{else}}bi-dash-lg{{/if}}" aria-hidden="true" data-schedule-rule-collapse-icon></i>
|
||||
<span class="visually-hidden">Collapse rule {{ruleIndex}}</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -36,11 +35,14 @@
|
||||
<div class="col-12">
|
||||
<div class="btn-group btn-group-sm w-100 schedule-day-grid" role="group" aria-label="Schedule days">
|
||||
{{#each dayOptions}}
|
||||
<input class="btn-check" type="checkbox" id="{{inputId}}" value="{{value}}" autocomplete="off" {{#if checked}}checked{{/if}} data-schedule-rule-day />
|
||||
<label class="btn btn-outline-primary flex-fill schedule-day-button" for="{{inputId}}">{{label}}</label>
|
||||
<span class="schedule-day-option">
|
||||
<input class="btn-check" type="checkbox" id="{{inputId}}" value="{{value}}" autocomplete="off" {{#if checked}}checked{{/if}} data-schedule-rule-day />
|
||||
<label class="btn btn-outline-primary flex-fill schedule-day-button" for="{{inputId}}">{{label}}</label>
|
||||
</span>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="invalid-feedback d-block mt-2" data-schedule-rule-feedback aria-live="polite" hidden></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,15 +1,11 @@
|
||||
<tr data-playlist-slide-row data-row-key="{{slide.row_key}}" data-slide-id="{{slide.slide_id}}" data-canvas-signature="{{slide.canvasSignature}}" data-media-type="{{slide.media_type}}" data-media-path="{{slide.media_path}}" data-video-source-path="{{slide.videoSourcePath}}" data-video-duration-seconds="{{slide.videoDurationSeconds}}" data-use-video-duration-state="{{#if slide.useVideoDuration}}true{{else}}false{{/if}}" data-disable-audio-state="{{#if slide.disableAudio}}true{{else}}false{{/if}}">
|
||||
<td class="playlist-order-cell" data-label="Order">
|
||||
<div class="playlist-order-cell-inner">
|
||||
<button type="button" class="playlist-drag-handle btn btn-link p-0 text-body-secondary" data-playlist-drag-handle aria-label="Drag to reorder" title="Drag to reorder">
|
||||
<span class="playlist-drag-handle-icon" aria-hidden="true">
|
||||
<svg class="playlist-drag-handle-svg" viewBox="0 0 24 32" focusable="false" aria-hidden="true">
|
||||
<polygon points="12,2 20,9 4,9"></polygon>
|
||||
<rect x="4" y="14" width="16" height="4" rx="2"></rect>
|
||||
<polygon points="4,23 20,23 12,30"></polygon>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
<div class="playlist-order-stepper" aria-label="Reorder slide controls">
|
||||
<button type="button" class="playlist-order-move playlist-order-move-up btn btn-link p-0 text-body-secondary" data-playlist-row-move="up" aria-label="Move slide up" title="Move slide up"><i class="bi bi-caret-up-fill" aria-hidden="true" data-playlist-row-move-icon="up"></i></button>
|
||||
<button type="button" class="playlist-drag-handle btn btn-link p-0 text-body-secondary" data-playlist-drag-handle aria-label="Drag to reorder" title="Drag to reorder"><span class="playlist-drag-handle-icon" aria-hidden="true"><i class="bi bi-grip-horizontal"></i></span></button>
|
||||
<button type="button" class="playlist-order-move playlist-order-move-down btn btn-link p-0 text-body-secondary" data-playlist-row-move="down" aria-label="Move slide down" title="Move slide down"><i class="bi bi-caret-down-fill" aria-hidden="true" data-playlist-row-move-icon="down"></i></button>
|
||||
</div>
|
||||
<span class="playlist-order-number">{{slide.displayOrder}}</span>
|
||||
</div>
|
||||
</td>
|
||||
@@ -33,16 +29,19 @@
|
||||
<div class="playlist-schedule-summary"><span class="chip" data-schedule-summary-chip>{{slide.scheduleSummary}}</span></div>
|
||||
<div class="d-none" data-schedule-rules-container data-schedule-rules='{{json slide.scheduleRules}}'></div>
|
||||
</td>
|
||||
<td data-label="Config">
|
||||
<td data-label="Playback">
|
||||
<div class="playlist-duration-field">
|
||||
<input name="duration_seconds[]" type="text" inputmode="decimal" value="{{slide.durationSeconds}}" required form="playlist-edit-form" class="form-control form-control-sm playlist-duration-input text-end" {{#if slide.useVideoDuration}}disabled{{/if}} data-playlist-duration-input />
|
||||
<div class="input-group input-group-sm flex-nowrap playlist-duration-input-group">
|
||||
<span class="input-group-text playlist-duration-prefix text-body-secondary" aria-hidden="true"><i class="bi bi-clock"></i></span>
|
||||
<input name="duration_seconds[]" type="text" inputmode="decimal" min="1" value="{{slide.durationSeconds}}" required form="playlist-edit-form" class="form-control form-control-sm playlist-duration-input text-end" {{#if slide.useVideoDuration}}disabled{{/if}} data-limit-input-value data-playlist-duration-input />
|
||||
</div>
|
||||
<input type="hidden" name="use_video_duration[]" value="{{#if slide.useVideoDuration}}1{{else}}0{{/if}}" form="playlist-edit-form" data-playlist-use-video-duration-input />
|
||||
<input type="hidden" name="disable_audio[]" value="{{#if slide.disableAudio}}1{{else}}0{{/if}}" form="playlist-edit-form" data-playlist-disable-audio-input />
|
||||
{{#if slide.useVideoDuration}}
|
||||
<input type="hidden" name="duration_seconds[]" value="{{slide.durationSeconds}}" form="playlist-edit-form" data-video-duration-mirror data-playlist-duration-mirror />
|
||||
{{/if}}
|
||||
<button type="button" class="btn btn-outline-info btn-sm playlist-use-video-duration{{#if slide.useVideoDuration}} active{{/if}}" data-use-video-duration-button aria-pressed="{{#if slide.useVideoDuration}}true{{else}}false{{/if}}" aria-label="{{#if slide.useVideoDuration}}Disable use video duration{{else}}Use video duration{{/if}}" title="{{#if slide.useVideoDuration}}Disable use video duration{{else}}Use video duration{{/if}}" {{#unless slide.showVideoDurationButton}}hidden{{/unless}}><i class="bi {{#if slide.useVideoDuration}}bi-camera-video{{else}}bi-camera-video-off{{/if}}" aria-hidden="true" data-use-video-duration-icon></i><span class="visually-hidden">{{#if slide.useVideoDuration}}Disable use video duration{{else}}Use video duration{{/if}}</span></button>
|
||||
<button type="button" class="btn {{#if slide.disableAudio}}btn-outline-secondary{{else}}btn-secondary active{{/if}} btn-sm playlist-disable-audio" data-disable-audio-button aria-pressed="{{#if slide.disableAudio}}true{{else}}false{{/if}}" aria-label="{{#if slide.disableAudio}}Enable audio{{else}}Mute slide{{/if}}" title="{{#if slide.disableAudio}}Enable audio{{else}}Mute slide{{/if}}" {{#unless slide.showMuteButton}}hidden{{/unless}}><i class="bi {{#if slide.disableAudio}}bi-volume-mute{{else}}bi-volume-up{{/if}}" aria-hidden="true" data-disable-audio-icon></i><span class="visually-hidden">{{#if slide.disableAudio}}Enable audio{{else}}Mute slide{{/if}}</span></button>
|
||||
<button type="button" class="btn {{#if slide.useVideoDuration}}btn-info{{else}}btn-outline-info{{/if}} btn-sm playlist-use-video-duration" data-use-video-duration-button aria-pressed="{{#if slide.useVideoDuration}}true{{else}}false{{/if}}" aria-label="{{#if slide.useVideoDuration}}Disable use video duration{{else}}Use video duration{{/if}}" title="{{#if slide.useVideoDuration}}Disable use video duration{{else}}Use video duration{{/if}}" {{#unless slide.showVideoDurationButton}}hidden{{/unless}}><i class="bi {{#if slide.useVideoDuration}}bi-camera-video{{else}}bi-camera-video-off{{/if}}" aria-hidden="true" data-use-video-duration-icon></i><span class="visually-hidden">{{#if slide.useVideoDuration}}Disable use video duration{{else}}Use video duration{{/if}}</span></button>
|
||||
<button type="button" class="btn {{#if slide.disableAudio}}btn-outline-secondary{{else}}btn-secondary{{/if}} btn-sm playlist-disable-audio" data-disable-audio-button aria-pressed="{{#if slide.disableAudio}}true{{else}}false{{/if}}" aria-label="{{#if slide.disableAudio}}Enable audio{{else}}Mute slide{{/if}}" title="{{#if slide.disableAudio}}Enable audio{{else}}Mute slide{{/if}}" {{#unless slide.showMuteButton}}hidden{{/unless}}><i class="bi {{#if slide.disableAudio}}bi-volume-mute{{else}}bi-volume-up{{/if}}" aria-hidden="true" data-disable-audio-icon></i><span class="visually-hidden">{{#if slide.disableAudio}}Enable audio{{else}}Mute slide{{/if}}</span></button>
|
||||
</div>
|
||||
</td>
|
||||
<td data-label="Actions">
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h2>{{#if isEdit}}Edit screen - {{screen.name}}{{else}}Add screen{{/if}}</h2>
|
||||
<h2>{{#if isEdit}}Edit screen group - {{screen.name}}{{else}}Add screen group{{/if}}</h2>
|
||||
<p>{{#if isEdit}}Update the display name and assigned playlist.{{else}}Register a player endpoint and connect it to a playlist.{{/if}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-12{{#if isEdit}} col-xl-8{{/if}}">
|
||||
<div class="card card-outline card-primary admin-form-card{{#if isEdit}} h-100{{/if}}">
|
||||
<div class="col-12 col-xl-8">
|
||||
<div class="card card-outline card-primary admin-form-card h-100">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Screen details</h3>
|
||||
<h3 class="card-title">Screen group details</h3>
|
||||
</div>
|
||||
<form id="screen-form" method="post" action="{{formAction}}" {{{formAttrs}}}>
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-12 col-md-6">
|
||||
<label for="screen-name" class="form-label">Name</label>
|
||||
<input id="screen-name" name="name" class="form-control" value="{{screen.name}}" placeholder="Front desk TV" required />
|
||||
<input id="screen-name" name="name" class="form-control" value="{{screen.name}}" maxlength="128" data-limit-text-length placeholder="Front desk TV" required />
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<label for="screen-slug" class="form-label">Slug</label>
|
||||
<input id="screen-slug" name="slug" class="form-control" value="{{screen.slug}}" placeholder="front-desk-tv" />
|
||||
<input id="screen-slug" name="slug" class="form-control" value="{{screen.slug}}" maxlength="128" data-limit-text-length placeholder="front-desk-tv" />
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label for="screen-playlist" class="form-label">Playlist</label>
|
||||
@@ -34,24 +34,24 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer d-flex justify-content-end">
|
||||
{{{saveActionButtons formId="screen-form" saveUrl=formAction cancelUrl=cancelUrl deleteUrl=deleteUrl deleteDisabled=deleteDisabled showSaveAndClose=showSaveSecondaryActions showSaveAndNew=showSaveSecondaryActions}}}
|
||||
{{{saveActionButtons formId="screen-form" saveUrl=formAction cancelUrl=cancelUrl deleteUrl=deleteUrl deleteConfirmMessage=deleteConfirmMessage deleteDisabled=deleteDisabled showSaveAndClose=showSaveSecondaryActions showSaveAndNew=showSaveSecondaryActions}}}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-xl-4">
|
||||
<div class="card card-outline card-secondary admin-form-card h-100">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Player URL</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{#if screen.player_url}}
|
||||
<p class="mb-0"><a href="{{screen.player_url}}" target="_blank">{{screen.player_url}}</a></p>
|
||||
{{else}}
|
||||
<p class="mb-0 text-muted">No player URL is available yet.</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="col-12 col-xl-4">
|
||||
<div class="card card-outline card-secondary admin-form-card h-100">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Player URL</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{#if screen.player_url}}
|
||||
<p class="mb-0"><a href="{{screen.player_url}}" target="_blank">{{screen.player_url}}</a></p>
|
||||
{{else}}
|
||||
<p class="mb-0 text-muted">No player URL is available yet.</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,20 +1,20 @@
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h2>Screens</h2>
|
||||
<h2>Screen Groups</h2>
|
||||
<p>Register display endpoints and keep playlist assignments in sync.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card card-outline card-primary" data-table-search-container data-table-pagination-card>
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Existing screens</h3>
|
||||
<h3 class="card-title">Existing screen groups</h3>
|
||||
<div class="card-tools d-flex flex-nowrap align-items-center gap-2">
|
||||
<div class="input-group input-group-sm flex-shrink-1" style="width: min(14rem, 100%);">
|
||||
<span class="input-group-text" aria-hidden="true"><i class="bi bi-search"></i></span>
|
||||
<input type="search" class="form-control" placeholder="Search screens" aria-label="Search screens" data-table-search />
|
||||
<input type="search" class="form-control" placeholder="Search screen groups" aria-label="Search screen groups" data-table-search />
|
||||
</div>
|
||||
{{#if (hasPermission currentUser 'screens.create')}}
|
||||
<a class="btn btn-primary btn-sm" href="/screens/new">Add screen</a>
|
||||
<a class="btn btn-primary btn-sm" href="/screens/new">Add screen group</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
@@ -56,7 +56,7 @@
|
||||
<a class="btn btn-sm btn-primary" href="/screens/{{id}}/edit">Edit</a>
|
||||
{{/if}}
|
||||
{{#if (hasPermission ../currentUser 'screens.delete')}}
|
||||
<form class="inline-form" method="post" action="/screens/{{id}}/delete" data-confirm-message="Delete this screen?">
|
||||
<form class="inline-form" method="post" action="/screens/{{id}}/delete" data-confirm-message="Delete this screen group?">
|
||||
{{#if (gt player_connection_count 0)}}
|
||||
<button class="btn btn-sm btn-outline-danger" type="submit" disabled>Delete</button>
|
||||
{{else}}
|
||||
@@ -72,7 +72,7 @@
|
||||
</tr>
|
||||
{{/each}}
|
||||
{{else}}
|
||||
<tr data-table-search-empty-default><td colspan="5" class="empty">No screens yet.</td></tr>
|
||||
<tr data-table-search-empty-default><td colspan="5" class="empty">No screen groups yet.</td></tr>
|
||||
{{/if}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<div class="row g-3">
|
||||
<div class="col-6">
|
||||
<label for="slide-title" class="form-label">Title</label>
|
||||
<input id="slide-title" name="title" class="form-control" value="{{slide.title}}" required />
|
||||
<input id="slide-title" name="title" class="form-control" value="{{slide.title}}" maxlength="128" data-limit-text-length required />
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label for="template-select" class="form-label">Template</label>
|
||||
@@ -118,9 +118,10 @@
|
||||
{{/if}}
|
||||
|
||||
<textarea id="slide-editor-data" hidden>{{json slideEditorData}}</textarea>
|
||||
<script src="/assets/js/shared/qr-code-svg.js?v={{appVersion}}"></script>
|
||||
<script src="/assets/vendor/cropperjs/cropper.min.js"></script>
|
||||
<script src="/assets/vendor/tinymce/tinymce.min.js"></script>
|
||||
<script src="/assets/vendor/qrcode-generator/qrcode.js?v={{appVersion}}"></script>
|
||||
<script src="/assets/vendor/qr-code-styling/qr-code-styling-loader.js?v={{appVersion}}"></script>
|
||||
{{#each slideEditorScripts}}
|
||||
<script type="module" src="/assets/{{this}}?v={{../appVersion}}"></script>
|
||||
{{/each}}
|
||||
|
||||
@@ -46,11 +46,14 @@
|
||||
<td data-label="Template">{{template_name}}</td>
|
||||
<td data-label="Playlists"><span class="chip">{{playlist_count}} playlist{{#if (gt playlist_count 1)}}s{{else}}{{/if}}{{#if (lt playlist_count 1)}}s{{/if}}</span></td>
|
||||
<td data-label="Actions">
|
||||
{{#if (anyPermission ../currentUser 'slides.update' 'slides.delete')}}
|
||||
{{#if (anyPermission ../currentUser 'slides.create' 'slides.update' 'slides.delete')}}
|
||||
<div class="actions">
|
||||
{{#if (hasPermission ../currentUser 'slides.update')}}
|
||||
<a class="btn btn-sm btn-primary" href="/slides/{{id}}/edit">Edit</a>
|
||||
{{/if}}
|
||||
{{#if (hasPermission ../currentUser 'slides.create')}}
|
||||
<a class="btn btn-sm btn-secondary" href="/slides/{{id}}/duplicate">Dupe</a>
|
||||
{{/if}}
|
||||
{{#if (hasPermission ../currentUser 'slides.delete')}}
|
||||
<form class="inline-form" method="post" action="/slides/{{id}}/delete" data-confirm-message="Delete this slide?">
|
||||
{{#if (gt playlist_count 0)}}
|
||||
|
||||
@@ -35,6 +35,47 @@
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
function startPreviewVideoPlayback(root) {
|
||||
if (!root) {
|
||||
return;
|
||||
}
|
||||
|
||||
var videos = root.querySelectorAll('video.slide-preview-video');
|
||||
Array.prototype.forEach.call(videos, function (video) {
|
||||
if (!video) {
|
||||
return;
|
||||
}
|
||||
|
||||
video.autoplay = true;
|
||||
video.loop = true;
|
||||
video.muted = true;
|
||||
video.playsInline = true;
|
||||
|
||||
function tryPlay() {
|
||||
try {
|
||||
video.load();
|
||||
} catch (_error) {
|
||||
// Ignore load failures and try to play anyway.
|
||||
}
|
||||
|
||||
var playPromise = video.play && video.play();
|
||||
if (playPromise && typeof playPromise.catch === 'function') {
|
||||
playPromise.catch(function () {
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (video.readyState >= 2) {
|
||||
tryPlay();
|
||||
return;
|
||||
}
|
||||
|
||||
video.addEventListener('canplay', tryPlay, { once: true });
|
||||
video.addEventListener('loadedmetadata', tryPlay, { once: true });
|
||||
});
|
||||
}
|
||||
|
||||
function injectFontStylesheet(href) {
|
||||
var normalizedHref = String(href || '').trim();
|
||||
if (!normalizedHref) {
|
||||
@@ -96,9 +137,11 @@
|
||||
canvas.style.backgroundPosition = 'center';
|
||||
canvas.style.backgroundSize = '100% 100%';
|
||||
canvas.style.backgroundRepeat = 'no-repeat';
|
||||
canvas.style.transformOrigin = 'center center';
|
||||
canvas.style.width = canvasWidth + 'px';
|
||||
canvas.style.height = canvasHeight + 'px';
|
||||
canvas.innerHTML = String(payload.html || '');
|
||||
startPreviewVideoPlayback(canvas);
|
||||
|
||||
var stageRect = stage.getBoundingClientRect();
|
||||
if (!stageRect.width || !stageRect.height) {
|
||||
@@ -112,6 +155,7 @@
|
||||
|
||||
window.addEventListener('hashchange', applyPayload);
|
||||
window.addEventListener('resize', applyPayload);
|
||||
window.addEventListener('load', applyPayload);
|
||||
applyPayload();
|
||||
}());
|
||||
</script>
|
||||
@@ -24,8 +24,8 @@
|
||||
</select>
|
||||
</label>
|
||||
<label class="d-grid gap-1">
|
||||
Duration ms
|
||||
<input class="form-control" type="number" min="0" step="1" data-animation-modal-step="intro" data-animation-modal-field="duration_ms" placeholder="1000" />
|
||||
Duration %
|
||||
<input class="form-control" type="number" min="1" max="200" step="1" data-animation-modal-step="intro" data-animation-modal-field="duration_ms" placeholder="100" />
|
||||
</label>
|
||||
<label class="d-grid gap-1">
|
||||
Delay ms
|
||||
@@ -49,8 +49,8 @@
|
||||
</select>
|
||||
</label>
|
||||
<label class="d-grid gap-1">
|
||||
Duration ms
|
||||
<input class="form-control" type="number" min="0" step="1" data-animation-modal-step="outro" data-animation-modal-field="duration_ms" placeholder="1000" />
|
||||
Duration %
|
||||
<input class="form-control" type="number" min="1" max="200" step="1" data-animation-modal-step="outro" data-animation-modal-field="duration_ms" placeholder="100" />
|
||||
</label>
|
||||
<label class="d-grid gap-1">
|
||||
Delay ms
|
||||
@@ -74,8 +74,8 @@
|
||||
</select>
|
||||
</label>
|
||||
<label class="d-grid gap-1">
|
||||
Duration ms
|
||||
<input class="form-control" type="number" min="0" step="1" data-animation-modal-step="loop" data-animation-modal-field="duration_ms" placeholder="1000" />
|
||||
Duration %
|
||||
<input class="form-control" type="number" min="1" max="200" step="1" data-animation-modal-step="loop" data-animation-modal-field="duration_ms" placeholder="100" />
|
||||
</label>
|
||||
<label class="d-grid gap-1">
|
||||
Delay ms
|
||||
@@ -83,7 +83,7 @@
|
||||
</label>
|
||||
<label class="d-grid gap-1">
|
||||
Repeat
|
||||
<input class="form-control" type="number" min="1" step="1" data-animation-modal-step="loop" data-animation-modal-field="iterations" placeholder="1" />
|
||||
<input class="form-control" type="number" min="1" max="999" step="1" data-animation-modal-step="loop" data-animation-modal-field="iterations" placeholder="1" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<div class="template-designer-main">
|
||||
<div class="designer-stage" id="designer-stage" style="aspect-ratio: {{template.canvas_size_width}} / {{template.canvas_size_height}};">
|
||||
<img id="background-preview" src="{{template.background_image_path}}" alt="" style="{{#if template.background_image_path}}{{else}}display:none;{{/if}}" />
|
||||
<div class="empty-bg" id="background-empty" style="{{#if template.background_image_path}}display:none;{{/if}}">No background image selected</div>
|
||||
<div class="empty-bg" id="background-empty" style="opacity:0.4;{{#if template.background_image_path}}display:none;{{/if}}">No background image selected</div>
|
||||
<div class="designer-overlay" id="designer-overlay"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -33,26 +33,26 @@
|
||||
<div class="card card-outline card-primary admin-form-card">
|
||||
<div class="card-body d-grid gap-3 pb-0">
|
||||
<div class="btn-group" role="group" aria-label="Template actions">
|
||||
{{{saveActionButtons formId="template-form" saveUrl=formAction cancelUrl=cancelUrl deleteUrl=deleteUrl deleteDisabled=deleteDisabled showSaveAndClose=showSaveSecondaryActions showSaveAndNew=showSaveSecondaryActions}}}
|
||||
{{{saveActionButtons formId="template-form" saveUrl=formAction cancelUrl=cancelUrl cancelConfirmMessage=cancelConfirmMessage deleteUrl=deleteUrl deleteDisabled=deleteDisabled deleteConfirmMessage=deleteConfirmMessage showSaveAndClose=showSaveSecondaryActions showSaveAndNew=showSaveSecondaryActions}}}
|
||||
</div>
|
||||
<div class="d-grid gap-2 template-preview-card" id="template-preview-card" hidden>
|
||||
<div class="btn-group flex-wrap gap-0" role="group" aria-label="Template animation preview controls">
|
||||
<button type="button" class="btn btn-outline-secondary" id="template-preview-intro" data-preview-control data-preview-action="intro" aria-pressed="false">Intro</button>
|
||||
<button type="button" class="btn btn-outline-secondary" id="template-preview-out" data-preview-control data-preview-action="out" aria-pressed="false">Outro</button>
|
||||
<button type="button" class="btn btn-outline-secondary" id="template-preview-continuous" data-preview-control data-preview-action="continuous" aria-pressed="false">Attention</button>
|
||||
<button type="button" class="btn btn-outline-secondary" id="template-preview-intro" data-preview-control data-preview-action="intro" aria-pressed="false"><i class="bi bi-play-fill me-1" aria-hidden="true" data-preview-icon></i><span>Intro</span></button>
|
||||
<button type="button" class="btn btn-outline-secondary" id="template-preview-out" data-preview-control data-preview-action="out" aria-pressed="false"><i class="bi bi-play-fill me-1" aria-hidden="true" data-preview-icon></i><span>Outro</span></button>
|
||||
<button type="button" class="btn btn-outline-secondary" id="template-preview-continuous" data-preview-control data-preview-action="continuous" aria-pressed="false"><i class="bi bi-play-fill me-1" aria-hidden="true" data-preview-icon></i><span>Attention</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card card-outline card-secondary admin-form-card region-info-card mb-3">
|
||||
<div class="card card-outline card-info admin-form-card region-info-card mb-3">
|
||||
<div class="card-header">
|
||||
<div>
|
||||
<h3 class="card-title">Region information</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<button type="button" class="btn btn-outline-secondary w-100" id="add-region-button" data-bs-toggle="modal" data-bs-target="#region-add-modal">Add region</button>
|
||||
<div class="region-selector-block mt-4">
|
||||
<button type="button" class="btn btn-info w-100" id="add-region-button" data-bs-toggle="modal" data-bs-target="#region-add-modal">Add region</button>
|
||||
<div class="region-selector-block mt-3">
|
||||
<label for="region-select" class="form-label">Select region</label>
|
||||
<select id="region-select" class="form-select region-select"></select>
|
||||
</div>
|
||||
@@ -72,7 +72,7 @@
|
||||
<div class="row g-3 template-meta-row">
|
||||
<div class="col-12">
|
||||
<label for="template-name" class="form-label">Name</label>
|
||||
<input name="name" id="template-name" class="form-control" value="{{template.name}}" required />
|
||||
<input name="name" id="template-name" class="form-control" value="{{template.name}}" maxlength="128" data-limit-text-length required />
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label for="canvas-size-select" class="form-label">Canvas size</label>
|
||||
@@ -100,15 +100,11 @@
|
||||
<div class="row g-3">
|
||||
<div class="col-12 col-md-6">
|
||||
<label for="background-image" class="form-label">Background image</label>
|
||||
{{#if isEdit}}
|
||||
<div class="template-background-row d-flex gap-2">
|
||||
<input type="file" name="background_image" id="background-image" class="form-control" accept="image/*" />
|
||||
<button type="button" class="btn btn-outline-secondary" id="remove-background-image">Remove</button>
|
||||
</div>
|
||||
<input type="checkbox" name="remove_background_image" id="remove-background-image-flag" value="1" hidden />
|
||||
{{else}}
|
||||
<div class="template-background-row d-flex gap-2">
|
||||
<input type="file" name="background_image" id="background-image" class="form-control" accept="image/*" />
|
||||
{{/if}}
|
||||
<button type="button" class="btn btn-outline-secondary" id="remove-background-image">Remove</button>
|
||||
</div>
|
||||
<input type="checkbox" name="remove_background_image" id="remove-background-image-flag" value="1" hidden />
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<label for="background-color" class="form-label">Background colour</label>
|
||||
@@ -121,11 +117,8 @@
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{{#if isEdit}}
|
||||
<form id="delete-template-form" method="post" action="/templates/{{template.id}}/delete" data-confirm-message="Delete this template?"></form>
|
||||
{{/if}}
|
||||
|
||||
<script src="/assets/vendor/qrcode-generator/qrcode.js?v={{appVersion}}"></script>
|
||||
<script src="/assets/js/shared/qr-code-svg.js?v={{appVersion}}"></script>
|
||||
<script src="/assets/vendor/qr-code-styling/qr-code-styling-loader.js?v={{appVersion}}"></script>
|
||||
|
||||
{{#> modal-shell modalId="region-add-modal" modalLabelId="region-add-modal-label" modalDialogClass="modal-dialog-centered"}}
|
||||
<div class="modal-header">
|
||||
@@ -143,7 +136,7 @@
|
||||
<div class="card-header template-field-head d-flex align-items-center flex-nowrap gap-2"><strong class="flex-grow-1 text-truncate" data-region-title>Region</strong><span class="chip ms-auto" data-region-chip>Text</span></div>
|
||||
<div class="card-body p-3 d-grid gap-3 pb-0">
|
||||
<div class="region-field-grid region-field-grid--identity">
|
||||
<label>Region name<input class="form-control" name="region_name[]" value="" placeholder="region_1" required /></label>
|
||||
<label>Region name<input class="form-control" name="region_name[]" value="" maxlength="64" data-limit-text-length placeholder="type_1" required /></label>
|
||||
</div>
|
||||
<input type="hidden" name="region_type[]" value="text" />
|
||||
<input type="hidden" name="region_key[]" value="" />
|
||||
@@ -156,7 +149,7 @@
|
||||
<div class="region-field-grid region-field-grid--size">
|
||||
<label>Width<input class="form-control" type="number" name="region_width[]" value="300" required /></label>
|
||||
<label>Height<input class="form-control" type="number" name="region_height[]" value="120" required /></label>
|
||||
<label>Lock ratio<input class="form-control" type="text" name="region_lock_ratio[]" value="" placeholder="16:9 or 5:4" /></label>
|
||||
<label>Lock ratio<input class="form-control" type="text" name="region_lock_ratio[]" value="" maxlength="20" data-limit-text-length placeholder="Ratio, e.g. 16:9" /></label>
|
||||
<label>Remove<button type="button" class="btn btn-outline-danger w-100" data-region-remove-button{{#if isEdit}} disabled{{/if}}>Remove</button></label>
|
||||
</div>
|
||||
<div class="region-field-grid region-field-grid--animation">
|
||||
|
||||
@@ -38,11 +38,14 @@
|
||||
<td data-label="Regions">{{regionCount}}</td>
|
||||
<td data-label="Slides used by"><span class="chip">{{slideCount}} slide{{#if (gt slideCount 1)}}s{{else}}{{/if}}{{#if (lt slideCount 1)}}s{{/if}}</span></td>
|
||||
<td data-label="Actions">
|
||||
{{#if (anyPermission ../currentUser 'templates.update' 'templates.delete')}}
|
||||
{{#if (anyPermission ../currentUser 'templates.create' 'templates.update' 'templates.delete')}}
|
||||
<div class="actions">
|
||||
{{#if (hasPermission ../currentUser 'templates.update')}}
|
||||
<a class="btn btn-sm btn-primary" href="/templates/{{id}}/edit">Edit</a>
|
||||
{{/if}}
|
||||
{{#if (hasPermission ../currentUser 'templates.create')}}
|
||||
<a class="btn btn-sm btn-secondary" href="/templates/{{id}}/duplicate">Dupe</a>
|
||||
{{/if}}
|
||||
{{#if (hasPermission ../currentUser 'templates.delete')}}
|
||||
<form class="inline-form" method="post" action="/templates/{{id}}/delete" data-confirm-message="Delete this template?">
|
||||
{{#if (gt slideCount 0)}}
|
||||
|
||||
Reference in New Issue
Block a user