diff --git a/ui/src/layout-editor/main.js b/ui/src/layout-editor/main.js index 84a7a97a80..a36e4e0840 100644 --- a/ui/src/layout-editor/main.js +++ b/ui/src/layout-editor/main.js @@ -2870,8 +2870,19 @@ lD.checkLayoutStatus = function() { * New open playlist editor * @param {string} playlistId - Id of the playlist * @param {object} region - Region related to the playlist + * @param {boolean} regionSpecific + * @param {boolean} showPlaylistSwitch + * @param {boolean} switchStatus + * @param {string} auxPlaylistId - Id of the parent/child playlist */ -lD.openPlaylistEditor = function(playlistId, region) { +lD.openPlaylistEditor = function( + playlistId, + region, + regionSpecific = true, + showPlaylistSwitch = false, + switchStatus = false, + auxPlaylistId, +) { // Deselect previous selected object lD.selectObject(); @@ -2914,7 +2925,38 @@ lD.openPlaylistEditor = function(playlistId, region) { $playlistEditorPanel.removeClass('hidden'); // Load playlist editor - pE.loadEditor(true); + pE.loadEditor( + true, + regionSpecific, // Region specific? + showPlaylistSwitch, // Show playlist switch? + switchStatus, // Show switch as on? + { + on: function() { // Switch ON callback + $playlistEditorPanel.find('#playlist-editor') + .attr('playlist-id', auxPlaylistId); + lD.openPlaylistEditor( + auxPlaylistId, + region, + false, + true, + true, + playlistId, + ); + }, + off: function() { // Switch OFF callback + $playlistEditorPanel.find('#playlist-editor') + .attr('playlist-id', auxPlaylistId); + lD.openPlaylistEditor( + auxPlaylistId, + region, + true, + true, + false, + playlistId, + ); + }, + }, + ); // Mark as opened lD.playlistEditorOpened = true; diff --git a/ui/src/layout-editor/viewer.js b/ui/src/layout-editor/viewer.js index 5e0462846a..bc370cf7f0 100644 --- a/ui/src/layout-editor/viewer.js +++ b/ui/src/layout-editor/viewer.js @@ -601,7 +601,24 @@ Viewer.prototype.handleInteractions = function() { // Open playlist editor lD.openPlaylistEditor( regionObject.playlists.playlistId, - regionObject); + regionObject, + ); + }; + + const playlistInlineEditorBtnClick = function( + childPlaylistId, + regionId, + ) { + const regionObject = + lD.getObjectByTypeAndId('region', regionId); + lD.openPlaylistEditor( + childPlaylistId, + regionObject, + false, + true, + true, + regionObject.playlists.playlistId, + ); }; const playlistPreviewBtnClick = function(playlistId, direction) { @@ -697,9 +714,19 @@ Viewer.prototype.handleInteractions = function() { } else if ( $(e.target).hasClass('playlist-edit-btn') ) { - // Edit region if it's a playlist - playlistEditorBtnClick($(e.target) - .parents('.designer-region-playlist').attr('id')); + // Edit subplaylist inside playlist + if ($(e.target).hasClass('subplaylist-inline-edit-btn')) { + // Edit subplaylist inside playlist + playlistInlineEditorBtnClick( + $(e.target).data('childPlaylistId'), + $(e.target).parents('.designer-region-playlist').attr('id'), + ); + } else { + // Edit region if it's a playlist + playlistEditorBtnClick( + $(e.target).parents('.designer-region-playlist').attr('id'), + ); + } } else if ( $(e.target).hasClass('playlist-preview-paging-prev') ) { @@ -1178,13 +1205,62 @@ Viewer.prototype.renderRegion = function( region.playlistCountOfWidgets = res.extra && res.extra.countOfWidgets ? res.extra.countOfWidgets : 1; - $container.append(viewerPlaylistControlsTemplate({ + const appendOptions = { titleEdit: viewerTrans.editPlaylist, seq: region.playlistSeq, countOfWidgets: region.playlistCountOfWidgets, isEmpty: res.extra && res.extra.empty, trans: viewerTrans, - })); + canEditPlaylist: false, + }; + + // Append playlist controls using appendOptions + const appendPlaylistControls = function() { + $container.append(viewerPlaylistControlsTemplate(appendOptions)); + }; + + // If it's playslist with a single subplaylist widget + if ( + Object.keys(region.widgets).length === 1 && + Object.values(region.widgets)[0].subType === 'subplaylist' + ) { + // Get assigned subplaylists + const subplaylists = + JSON.parse( + Object.values(region.widgets)[0].getOptions().subPlaylists, + ); + + // If there's only one playlist, get permissions + if (subplaylists.length === 1) { + const subPlaylistId = subplaylists[0].playlistId; + $.ajax({ + method: 'GET', + url: urlsForApi.playlist.get.url + + '?playlistId=' + subplaylists[0].playlistId, + success: function(_res) { + // User has permissions + if (_res.data && _res.data.length > 0) { + appendOptions.canEditPlaylist = true; + appendOptions.canEditPlaylistId = subPlaylistId; + } + + // Append playlist controls + appendPlaylistControls(); + }, + error: function(_res) { + console.error(_res); + // Still append playlist controls + appendPlaylistControls(); + }, + }); + } else { + // Append playlist controls + appendPlaylistControls(); + } + } else { + // Append playlist controls + appendPlaylistControls(); + } } // If widget is selected, update moveable for the region diff --git a/ui/src/playlist-editor/main.js b/ui/src/playlist-editor/main.js index 4d07dcacd6..e7cee5b825 100644 --- a/ui/src/playlist-editor/main.js +++ b/ui/src/playlist-editor/main.js @@ -32,6 +32,10 @@ const playlistEditorExternalContainerTemplate = const messageTemplate = require('../templates/message.hbs'); const loadingTemplate = require('../templates/loading.hbs'); const contextMenuTemplate = require('../templates/context-menu.hbs'); +const topbarTemplatePlaylistEditor = + require('../templates/topbar-playlist-editor.hbs'); +const switchPlaylistsTemplate = + require('../templates/topbar-playlist-switch.hbs'); // Include modules const Playlist = require('../playlist-editor/playlist.js'); @@ -40,8 +44,6 @@ const Toolbar = require('../editor-core/toolbar.js'); const PropertiesPanel = require('../editor-core/properties-panel.js'); const HistoryManager = require('../editor-core/history-manager.js'); const TemplateManager = require('../layout-editor/template-manager.js'); -const topbarTemplatePlaylistEditor = - require('../templates/topbar-playlist-editor.hbs'); // Include CSS if (typeof lD == 'undefined') { @@ -101,8 +103,19 @@ window.pE = { /** * Load Playlist and build app structure * @param {string} inline - Is this an inline playlist editor? + * @param {boolean} regionSpecific - Is this region specific? + * @param {boolean} showPlaylistSwitch - Are we editing a child playlist? + * @param {boolean} playlistSwitchStatus - true: child, false: parent + * @param {object} switchContainerCallbacks - on and off callbacks + * - aux parent playlist id if we're editing child playlist */ -pE.loadEditor = function(inline = false) { +pE.loadEditor = function( + inline = false, + regionSpecific = true, + showPlaylistSwitch = false, + playlistSwitchStatus = false, + switchContainerCallbacks, +) { // Add class to body so we can use CSS specifically on it (!inline) && $('body').addClass('editor-opened'); @@ -120,7 +133,7 @@ pE.loadEditor = function(inline = false) { pE.regionSpecificQuery = ''; if (inline) { - pE.regionSpecificQuery = '®ionSpecific=1'; + (regionSpecific) && (pE.regionSpecificQuery = '®ionSpecific=1'); pE.mainRegion = pE.editorContainer.parents('#editor-container').data('regionObj'); pE.inline = true; @@ -238,6 +251,13 @@ pE.loadEditor = function(inline = false) { .on('click', function() { pE.close(); }); + + if (showPlaylistSwitch) { + pE.createPlaylistSwitch( + playlistSwitchStatus, + switchContainerCallbacks, + ); + } } else { // Login Form needed? if (res.login) { @@ -577,6 +597,15 @@ pE.refreshEditor = function( this.selectedObject.selected = true; } + // If we have more than one widget in the playlist + // and the playlist switch is off, remove switch + if ( + this.playlistSwitchOn === false && + Object.values(this.playlist.widgets).length != 1 + ) { + this.removePlaylistSwitch(); + } + // Remove temporary data (reloadPropertiesPanel) && this.clearTemporaryData(); @@ -1233,3 +1262,72 @@ pE.handleKeyInputs = function() { }); }; +/** + * Playlist switch between parent and child playlist + * @param {boolean} status - Is switch starting as on? + * @param {object} callbacks - on and off switch callbacks + */ +pE.createPlaylistSwitch = function( + status, + callbacks, +) { + const $topContainer = + pE.editorContainer.parents('#editor-container'); + + // Get switch container + let $switchContainer = + $topContainer.find('.playlist-switch-container'); + + // If container doesn't exist, create it + if ($switchContainer.length === 0) { + $switchContainer = $(switchPlaylistsTemplate(playlistEditorTrans)); + $switchContainer.prependTo($topContainer); + } + + // Add class to playlist editor container + // to adjust its height + $topContainer.find('#playlist-editor').addClass('playlist-switch-on'); + + // Create switch and handle callbacks + $switchContainer.find('[name="playlist-switch-input"]').bootstrapSwitch({ + state: status, + onText: playlistEditorTrans.playlistSwitch.on, + offText: playlistEditorTrans.playlistSwitch.off, + onSwitchChange: function(_e, state) { + setTimeout(() => { + if (state) { + callbacks.on(); + // Set flag as on + this.playlistSwitchOn = true; + } else { + callbacks.off(); + // Set flag as off + this.playlistSwitchOn = false; + } + }, 600); + }, + }); + + // Set flag as off + this.playlistSwitchOn = status; +}; + +/** + * Remove playlist switch + */ +pE.removePlaylistSwitch = function() { + // Get switch container + const $switchContainer = + pE.editorContainer.parents('#editor-container') + .find('.playlist-switch-container'); + + // Destroy switch + $switchContainer.find('[name="playlist-switch-input"]') + .bootstrapSwitch('destroy'); + + // Remove container + $switchContainer.remove(); + + // Set flag as off + this.playlistSwitchOn = false; +}; diff --git a/ui/src/style/layout-editor.scss b/ui/src/style/layout-editor.scss index c3b836b216..f613c7cae2 100644 --- a/ui/src/style/layout-editor.scss +++ b/ui/src/style/layout-editor.scss @@ -463,7 +463,11 @@ body.editor-opened { } &:hover > .playlist-edit-btn { - opacity: 0.8; + opacity: 0.7; + + &:hover { + opacity: 0.9; + } } &:hover > .playlist-preview-paging { diff --git a/ui/src/style/playlist-editor.scss b/ui/src/style/playlist-editor.scss index a8e34001d8..76fc18889b 100644 --- a/ui/src/style/playlist-editor.scss +++ b/ui/src/style/playlist-editor.scss @@ -759,4 +759,25 @@ $timeline-step-height: 22px; } } } + + &.playlist-switch-on { + height: calc(100% - 40px); + } +} + +.playlist-switch-container { + background-color: $xibo-color-primary-l10; + height: 40px; + padding: 6px; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + line-height: 14px; + align-items: center; + gap: 8px; + border-radius: 4px 4px 0 0; + + .bootstrap-switch-container > * { + line-height: 16px; + } } \ No newline at end of file diff --git a/ui/src/templates/topbar-playlist-switch.hbs b/ui/src/templates/topbar-playlist-switch.hbs new file mode 100644 index 0000000000..17cf84bff6 --- /dev/null +++ b/ui/src/templates/topbar-playlist-switch.hbs @@ -0,0 +1,7 @@ +
+
+ +
+ + {{playlistSwitch.helpText}} +
\ No newline at end of file diff --git a/ui/src/templates/viewer-playlist-controls.hbs b/ui/src/templates/viewer-playlist-controls.hbs index ecc06e4c40..aeed9a0d38 100644 --- a/ui/src/templates/viewer-playlist-controls.hbs +++ b/ui/src/templates/viewer-playlist-controls.hbs @@ -1,5 +1,6 @@ -
+
{{#if isEmpty}} @@ -9,19 +10,19 @@

{{ trans.empty }}

{{else}} -
- +
+ - {{seq}}/{{countOfWidgets}} + {{seq}}/{{countOfWidgets}} - -
+ +
{{/if}} \ No newline at end of file diff --git a/views/common.twig b/views/common.twig index c60cbc000a..dc2110eee6 100644 --- a/views/common.twig +++ b/views/common.twig @@ -1048,12 +1048,19 @@ duration: toolbarTrans.duration, editPlaylistTitle: "{{ "Edit Playlist - %playlistName% - "|trans }}", widgetsCount: "{{ "Widgets count"|trans }}", + editingSourcePlaylist: "{{ "Editing source playlist %playlistName% "|trans }}", zoomControls: { in: "{{ "Zoom In"|trans }}", out: "{{ "Zoom Out"|trans }}", default: "{{ "Default zoom"|trans }}", scaleMode: "{{ "Change scale mode"|trans }}", }, + playlistSwitch: { + on: "{{ "On"|trans }}", + off: "{{ "Off"|trans }}", + title: "{{ "Edit Playlist"|trans }}", + helpText: "{{ "This widget only has one playlist, switch Edit Playlist mode to ON and edit that playlist directly. Warning: your changes will apply anywhere this Playlist is used."|trans }}", + } }; var deleteModalTrans = { diff --git a/views/layout-designer-page.twig b/views/layout-designer-page.twig index 2cb4273322..b82992dd43 100644 --- a/views/layout-designer-page.twig +++ b/views/layout-designer-page.twig @@ -92,6 +92,7 @@ next: '{{ "Next Widget"|trans }}', empty: '{{ "Empty Playlist"|trans }}', invalidRegion: '{{ "Invalid Region"|trans }}', + editPlaylistTitle: '{{ "Edit Playlist"|trans }}', }; var timelineTrans = {