diff --git a/lib/Connector/XiboExchangeConnector.php b/lib/Connector/XiboExchangeConnector.php index d7d405bf65..38270145b9 100644 --- a/lib/Connector/XiboExchangeConnector.php +++ b/lib/Connector/XiboExchangeConnector.php @@ -30,6 +30,7 @@ use Xibo\Entity\SearchResult; use Xibo\Event\TemplateProviderEvent; use Xibo\Event\TemplateProviderImportEvent; +use Xibo\Event\TemplateProviderListEvent; use Xibo\Support\Sanitizer\SanitizerInterface; /** @@ -50,6 +51,7 @@ public function registerWithDispatcher(EventDispatcherInterface $dispatcher): Co { $dispatcher->addListener('connector.provider.template', [$this, 'onTemplateProvider']); $dispatcher->addListener('connector.provider.template.import', [$this, 'onTemplateProviderImport']); + $dispatcher->addListener('connector.provider.template.list', [$this, 'onTemplateList']); return $this; } @@ -239,4 +241,25 @@ private function createSearchResult($template) : SearchResult $searchResult->download = $template->downloadUrl; return $searchResult; } + + /** + * Add this connector to the list of providers. + * @param \Xibo\Event\TemplateProviderListEvent $event + * @return void + */ + public function onTemplateList(TemplateProviderListEvent $event): void + { + $this->getLogger()->debug('onTemplateList:event'); + + $providerDetails = new ProviderDetails(); + $providerDetails->id = $this->getSourceName(); + $providerDetails->link = 'https://xibosignage.com'; + $providerDetails->logoUrl = $this->getThumbnail(); + $providerDetails->iconUrl = 'exchange-alt'; + $providerDetails->message = $this->getTitle(); + $providerDetails->backgroundColor = ''; + $providerDetails->mediaTypes = ['xlf']; + + $event->addProvider($providerDetails); + } } diff --git a/lib/Controller/Template.php b/lib/Controller/Template.php index db8e7b9d63..ff1ffc0808 100644 --- a/lib/Controller/Template.php +++ b/lib/Controller/Template.php @@ -22,11 +22,13 @@ namespace Xibo\Controller; use Parsedown; +use Psr\Http\Message\ResponseInterface; use Slim\Http\Response as Response; use Slim\Http\ServerRequest as Request; use Xibo\Entity\SearchResult; use Xibo\Entity\SearchResults; use Xibo\Event\TemplateProviderEvent; +use Xibo\Event\TemplateProviderListEvent; use Xibo\Factory\LayoutFactory; use Xibo\Factory\TagFactory; use Xibo\Support\Exception\AccessDeniedException; @@ -767,4 +769,21 @@ public function editForm(Request $request, Response $response, $id) return $this->render($request, $response); } + + /** + * Get list of Template providers with their details. + * + * @param Request $request + * @param Response $response + * @return Response|ResponseInterface + */ + public function providersList(Request $request, Response $response): Response|\Psr\Http\Message\ResponseInterface + { + $event = new TemplateProviderListEvent(); + $this->getDispatcher()->dispatch($event, $event->getName()); + + $providers = $event->getProviders(); + + return $response->withJson($providers); + } } diff --git a/lib/Event/TemplateProviderListEvent.php b/lib/Event/TemplateProviderListEvent.php new file mode 100644 index 0000000000..4dfd71ec7d --- /dev/null +++ b/lib/Event/TemplateProviderListEvent.php @@ -0,0 +1,60 @@ +. + */ + +namespace Xibo\Event; + +use Xibo\Connector\ProviderDetails; + +/** + * Get a list of template providers + */ +class TemplateProviderListEvent extends Event +{ + protected static $NAME = 'connector.provider.template.list'; + /** + * @var array + */ + private mixed $providers; + + public function __construct($providers = []) + { + $this->providers = $providers; + } + + /** + * @param ProviderDetails $provider + * @return TemplateProviderListEvent + */ + public function addProvider(ProviderDetails $provider): TemplateProviderListEvent + { + $this->providers[] = $provider; + return $this; + } + + /** + * @return ProviderDetails[] + */ + public function getProviders(): array + { + return $this->providers; + } +} diff --git a/lib/routes-web.php b/lib/routes-web.php index 1fa0372ccc..54cbb4bbae 100644 --- a/lib/routes-web.php +++ b/lib/routes-web.php @@ -378,6 +378,8 @@ // // template // +$app->get('/template/connector/list', ['\Xibo\Controller\Template','providersList']) + ->setName('template.search.providers'); $app->get('/template/search', ['\Xibo\Controller\Template', 'search'])->setName('template.search.all'); $app->get('/template/view', ['\Xibo\Controller\Template','displayPage']) ->addMiddleware(new FeatureAuth($app->getContainer(), ['template.view'])) diff --git a/ui/src/editor-core/toolbar.js b/ui/src/editor-core/toolbar.js index e7b9ada2f1..1b5fa1be25 100644 --- a/ui/src/editor-core/toolbar.js +++ b/ui/src/editor-core/toolbar.js @@ -612,29 +612,6 @@ Toolbar.prototype.init = function({isPlaylist = false} = {}) { state: '', itemCount: 0, }, - { - name: 'layout_exchange', - disabled: isPlaylist, - itemName: toolbarTrans.menuItems.layoutExchangeName, - itemIcon: 'exchange-alt', // TODO: Change icon! - itemTitle: toolbarTrans.menuItems.layoutExchangeTitle, - contentType: 'layout_exchange', - filters: { - name: { - value: '', - key: 'template', - }, - provider: { - value: 'remote', - locked: true, - }, - orientation: { - value: '', - }, - }, - state: '', - itemCount: 0, - }, ]; // Menu items @@ -666,7 +643,71 @@ Toolbar.prototype.init = function({isPlaylist = false} = {}) { this.moduleListOtherTypes = moduleListOtherTypes; this.moduleGroups = moduleGroups; - // Get providers + // Get template providers + $.ajax(urlsForApi.template.getProviders).done(function(res) { + // Stop if not available + if (Object.keys(self).length === 0) { + return; + } + + if ( + Array.isArray(res) + ) { + res.forEach((provider) => { + // Add provider to menu items + self.menuItems.push( + { + name: provider.id, + disabled: isPlaylist, + itemName: provider.message, + // link: provider.link, + itemTitle: toolbarTrans.menuItems + .layoutExchangeTitle.replace('%obj%', provider.message), + itemIcon: provider.iconUrl, + contentType: 'template_exchange', + filters: { + name: { + value: '', + key: 'template', + }, + provider: { + value: 'remote', + locked: true, + }, + orientation: { + value: '', + }, + }, + state: '', + itemCount: 0, + }); + }); + } else { + // Login Form needed? + if (res.login) { + window.location.href = window.location.href; + location.reload(); + } else { + // Just an error we dont know about + if (res.message == undefined) { + console.error(res); + } else { + console.error(res.message); + } + } + } + + // Render + self.render(); + }).catch(function(jqXHR, textStatus, errorThrown) { + console.error(jqXHR, textStatus, errorThrown); + console.error(errorMessagesTrans.getProvidersFailed); + + // Render + self.render(); + }); + + // Get media providers $.ajax(urlsForApi.media.getProviders).done(function(res) { // Stop if not available if (Object.keys(self).length === 0) { @@ -1326,7 +1367,7 @@ Toolbar.prototype.createContent = function( this.elementsContentCreateWindow(menu, savePrefs); } else if ( content.contentType === 'layout_templates' || - content.contentType === 'layout_exchange' + content.contentType === 'template_exchange' ) { this.layoutTemplatesContentCreateWindow(menu); } else if (content.contentType === 'playlists') { @@ -2413,7 +2454,7 @@ Toolbar.prototype.elementsContentCreateWindow = function( Toolbar.prototype.layoutTemplatesContentCreateWindow = function(menu) { const self = this; const app = this.parent; - const tabContentName = this.menuItems[menu].name; + const tabContentType = this.menuItems[menu].contentType; // Deselect previous selections self.deselectCardsAndDropZones(); @@ -2424,18 +2465,19 @@ Toolbar.prototype.layoutTemplatesContentCreateWindow = function(menu) { filters: this.menuItems[menu].filters, trans: toolbarTrans, formClass: 'layout_tempates-search-form', - headerMessage: (tabContentName == 'layout_exchange') ? - toolbarTrans.layoutExchangeTemplatesMessage : + headerMessage: (tabContentType == 'template_exchange') ? + toolbarTrans.layoutExchangeTemplatesMessage + .replace('%obj%', this.menuItems[menu].itemName) : toolbarTrans.layoutTemplatesMessage, }); // Clear temp data app.common.clearContainer( - self.DOMObject.find('#' + tabContentName + '-container-' + menu), + self.DOMObject.find('#' + tabContentType + '-container-' + menu), ); // Append template to the search main div - self.DOMObject.find('#' + tabContentName + '-container-' + menu).html(html); + self.DOMObject.find('#' + tabContentType + '-container-' + menu).html(html); // Load content this.layoutTemplatesContentPopulate(menu); @@ -2473,9 +2515,9 @@ Toolbar.prototype.playlistsContentCreateWindow = function(menu) { Toolbar.prototype.layoutTemplatesContentPopulate = function(menu) { const app = this.parent; const self = this; - const tabContentName = this.menuItems[menu].name; + const tabContentType = this.menuItems[menu].contentType; const $container = self.DOMObject.find( - '#' + tabContentName + '-container-' + menu, + '#' + tabContentType + '-container-' + menu, ); const $content = self.DOMObject.find('#media-content-' + menu); const $searchForm = $container.parent().find('.toolbar-search-form'); diff --git a/ui/src/style/toolbar.scss b/ui/src/style/toolbar.scss index 2e0a166107..303a80d124 100644 --- a/ui/src/style/toolbar.scss +++ b/ui/src/style/toolbar.scss @@ -629,7 +629,7 @@ } } - &.toolbar-layout_templates-pane, &.toolbar-layout_exchange-pane { + &.toolbar-layout_templates-pane, &.toolbar-template_exchange-pane { .toolbar-card:not(.has-thumb):hover .media-title { overflow: visible; white-space: normal; diff --git a/ui/src/templates/toolbar-content.hbs b/ui/src/templates/toolbar-content.hbs index 51bc623b3e..546603021b 100644 --- a/ui/src/templates/toolbar-content.hbs +++ b/ui/src/templates/toolbar-content.hbs @@ -1,5 +1,5 @@
diff --git a/views/common.twig b/views/common.twig index 2fe86d6faf..8e058ec5bf 100644 --- a/views/common.twig +++ b/views/common.twig @@ -142,6 +142,10 @@ list: { url: "{{ url_for("template.view") }}" }, + getProviders: { + url: "{{ url_for("template.search.providers") }}", + type: 'GET' + }, }, region: { transform: { @@ -765,8 +769,7 @@ actionsTitle: "{{ "Interactive actions"|trans }}", layoutTemplateName: "{{ "Layout Templates" |trans }}", layoutTemplateTitle: "{{ "Search for Layout Templates"|trans }}", - layoutExchangeName: "{{ "Xibo Layout Exchange" |trans }}", - layoutExchangeTitle: "{{ "Search for templates available from the Xibo Exchange."|trans }}", + layoutExchangeTitle: "{{ "Search for templates available from the %obj%."|trans }}", playlistsName: "{{ "Playlists" |trans }}", playlistsTitle: "{{ "Add Playlists"|trans }}", providerTitle: "{{ "Provider: %obj%"|trans }}", @@ -816,7 +819,7 @@ layout: "{{ "Layout"|trans }}", region: "{{ "Zone"|trans }}", layoutTemplatesMessage: "{{ "Replace your Layout with a template?"|trans }}", - layoutExchangeTemplatesMessage: "{{ "Replace your Layout with a Xibo Exchange template?"|trans }}", + layoutExchangeTemplatesMessage: "{{ "Replace your Layout with a %obj% template?"|trans }}", isRequired: "{{ "Required"|trans }}", libraryTypes: { image: "{{ "Image" |trans }}",