diff --git a/lib/Connector/AlphaVantageConnector.php b/lib/Connector/AlphaVantageConnector.php index 3e30eb0602..8c36343109 100644 --- a/lib/Connector/AlphaVantageConnector.php +++ b/lib/Connector/AlphaVantageConnector.php @@ -1,6 +1,6 @@ getDataProvider()->setCacheTtl($this->getSetting('cachePeriod', 3600)); } catch (\Exception $exception) { $this->getLogger()->error('onDataRequest: Failed to get results. e = ' . $exception->getMessage()); - $dataProvider->addError(__('Unable to contact the AlphaVantage API')); + if ($exception instanceof InvalidArgumentException) { + $dataProvider->addError($exception->getMessage()); + } else { + $dataProvider->addError(__('Unable to contact the AlphaVantage API')); + } } } } @@ -231,7 +235,7 @@ private function getStockResults(DataProviderInterface $dataProvider): void if ($items == '') { $this->getLogger()->error('Missing Items for Stocks Module with WidgetId ' . $dataProvider->getWidgetId()); - throw new InvalidArgumentException(__('Missing Items for Stocks Module'), 'items'); + throw new InvalidArgumentException(__('Add some stock symbols'), 'items'); } // Parse items out into an array diff --git a/lib/Connector/XiboExchangeConnector.php b/lib/Connector/XiboExchangeConnector.php index 71c77540db..6b576ea285 100644 --- a/lib/Connector/XiboExchangeConnector.php +++ b/lib/Connector/XiboExchangeConnector.php @@ -193,7 +193,7 @@ private function createSearchResult($template) : SearchResult $searchResult->title = $template->title; $searchResult->description = empty($template->description) ? null - : Parsedown::instance()->line($template->description); + : Parsedown::instance()->setSafeMode(true)->line($template->description); // Optional data if (property_exists($template, 'tags') && count($template->tags) > 0) { diff --git a/lib/Controller/DataSetRss.php b/lib/Controller/DataSetRss.php index 0729018eaf..f293a2304f 100644 --- a/lib/Controller/DataSetRss.php +++ b/lib/Controller/DataSetRss.php @@ -1,8 +1,8 @@ dataSetFactory->getById($feed->dataSetId); // What is the edit date of this data set - $dataSetEditDate = ($dataSet->lastDataEdit == 0) ? Carbon::now()->subMonths(2) : Carbon::createFromTimestamp($dataSet->lastDataEdit); + $dataSetEditDate = ($dataSet->lastDataEdit == 0) + ? Carbon::now()->subMonths(2) + : Carbon::createFromTimestamp($dataSet->lastDataEdit); // Do we have this feed in the cache? $cache = $this->pool->getItem('/dataset/rss/' . $feed->id); @@ -692,7 +692,10 @@ public function feed(Request $request, Response $response, $psk) if ($cache->isMiss() || $cache->getCreation() < $dataSetEditDate) { // We need to recache - $this->getLog()->debug('Generating RSS feed and saving to cache. Created on ' . (($cache->getCreation() !== false) ? $cache->getCreation()->format(DateFormatHelper::getSystemFormat()) : 'never')); + $this->getLog()->debug('Generating RSS feed and saving to cache. Created on ' + . ($cache->getCreation() + ? $cache->getCreation()->format(DateFormatHelper::getSystemFormat()) + : 'never')); $output = $this->generateFeed($feed, $dataSetEditDate, $dataSet); @@ -705,10 +708,10 @@ public function feed(Request $request, Response $response, $psk) $response->withHeader('Content-Type', 'application/rss+xml'); echo $output; - - } catch (NotFoundException $notFoundException) { + } catch (NotFoundException) { $this->getState()->httpStatus = 404; } + return $response; } /** @@ -718,12 +721,14 @@ public function feed(Request $request, Response $response, $psk) * @return string * @throws \Xibo\Support\Exception\NotFoundException */ - private function generateFeed($feed, $dataSetEditDate, $dataSet) + private function generateFeed($feed, $dataSetEditDate, $dataSet): string { // Create the start of our feed, its description, etc. $builder = Rss20FeedBuilder::create() ->withTitle($feed->title) ->withAuthor($feed->author) + ->withFeedUrl('') + ->withSiteUrl('') ->withDate($dataSetEditDate); $sort = $feed->getSort(); @@ -860,6 +865,7 @@ private function generateFeed($feed, $dataSetEditDate, $dataSet) foreach ($dataSetResults as $row) { $item = Rss20ItemBuilder::create($builder); + $item->withUrl(''); $hasContent = false; $hasDate = false; @@ -880,7 +886,7 @@ private function generateFeed($feed, $dataSetEditDate, $dataSet) } else if ($mappings[$key]['dataSetColumnId'] === $feed->publishedDateColumnId) { try { $date = Carbon::createFromTimestamp($value); - } catch (InvalidDateException $dateException) { + } catch (InvalidDateException) { $date = $dataSetEditDate; } @@ -892,11 +898,13 @@ private function generateFeed($feed, $dataSetEditDate, $dataSet) } } - if (!$hasDate) + if (!$hasDate) { $item->withPublishedDate($dataSetEditDate); + } - if ($hasContent) + if ($hasContent) { $builder->withItem($item); + } } // Found, do things diff --git a/lib/Controller/Layout.php b/lib/Controller/Layout.php index bd74409493..91dcae7dd2 100644 --- a/lib/Controller/Layout.php +++ b/lib/Controller/Layout.php @@ -415,11 +415,12 @@ public function add(Request $request, Response $response) // Empty template so we create a blank layout with the provided resolution if (empty($resolutionId)) { - // Pick landscape - $resolution = $this->resolutionFactory->getByDimensions(1920, 1080); - $resolutionId = $resolution->resolutionId; + // Get the nearest landscape resolution we can + $resolution = $this->resolutionFactory->getClosestMatchingResolution(1920, 1080); - $this->getLog()->debug('add: no resolution resolved: ' . $resolutionId); + // Get the ID + $resolutionId = $resolution->resolutionId; + $this->getLog()->debug('add: resolution resolved: ' . $resolutionId); } $layout = $this->layoutFactory->createFromResolution( @@ -1600,7 +1601,7 @@ public function grid(Request $request, Response $response) // Parse down for description $layout->setUnmatchedProperty( 'descriptionFormatted', - Parsedown::instance()->text($layout->description) + Parsedown::instance()->setSafeMode(true)->text($layout->description) ); } else if ($showDescriptionId == 2) { $layout->setUnmatchedProperty('descriptionFormatted', strtok($layout->description, "\n")); @@ -3363,7 +3364,7 @@ public function createFullScreenLayout(Request $request, Response $response): Re $media->height )->resolutionId; } else if ($type === 'playlist') { - $resolutionId = $this->resolutionFactory->getByDimensions( + $resolutionId = $this->resolutionFactory->getClosestMatchingResolution( 1920, 1080 )->resolutionId; diff --git a/lib/Controller/Library.php b/lib/Controller/Library.php index 7c212f37ad..3bc46cc0bc 100644 --- a/lib/Controller/Library.php +++ b/lib/Controller/Library.php @@ -2489,7 +2489,7 @@ public function uploadFromUrl(Request $request, Response $response) } // if we were provided with optional Media name set it here, otherwise get it from download info - $name = empty($optionalName) ? $downloadInfo['filename'] : $optionalName; + $name = empty($optionalName) ? htmlspecialchars($downloadInfo['filename']) : $optionalName; // double check that provided Module Type and Extension are valid if (!Str::contains($module->getSetting('validExtensions'), $ext)) { diff --git a/lib/Controller/Login.php b/lib/Controller/Login.php index 3e81ffbe74..234a52afb9 100644 --- a/lib/Controller/Login.php +++ b/lib/Controller/Login.php @@ -1,8 +1,8 @@ send()) { throw new ConfigurationException('Unable to send password reminder to ' . $user->email); } else { - $this->getFlash()->addMessage('login_message', __('Reminder email has been sent to your email address')); + $this->getFlash()->addMessage( + 'login_message', + __('A reminder email will been sent to this user if they exist'), + ); } // Audit Log $this->getLog()->audit('User', $user->userId, 'Password Reset Link Granted', [ 'UserAgent' => $request->getHeader('User-Agent') ]); - } catch (GeneralException $xiboException) { - $this->getLog()->debug($xiboException->getMessage()); - $this->getFlash()->addMessage('login_message', __('User not found')); + } catch (GeneralException) { + $this->getFlash()->addMessage( + 'login_message', + __('A reminder email will been sent to this user if they exist'), + ); } $this->setNoOutput(true); diff --git a/lib/Controller/Playlist.php b/lib/Controller/Playlist.php index 6d62b2fb3d..3b04978812 100644 --- a/lib/Controller/Playlist.php +++ b/lib/Controller/Playlist.php @@ -1,6 +1,6 @@ getIntArray('media'); - if (count($media) <= 0) + if (empty($media)) { throw new InvalidArgumentException(__('Please provide Media to Assign'), 'media'); + } // Optional Duration $duration = ($sanitizedParams->getInt('duration')); diff --git a/lib/Controller/Template.php b/lib/Controller/Template.php index 6ac0357797..b905e1f27c 100644 --- a/lib/Controller/Template.php +++ b/lib/Controller/Template.php @@ -1,6 +1,6 @@ setUnmatchedProperty('descriptionWithMarkup', Parsedown::instance()->text($template->description)); + $template->setUnmatchedProperty( + 'descriptionWithMarkup', + Parsedown::instance()->setSafeMode(true)->text($template->description), + ); if ($this->getUser()->featureEnabled('template.modify') && $this->getUser()->checkEditable($template) @@ -347,7 +350,7 @@ public function search(Request $request, Response $response) // Handle the description $searchResult->description = ''; if (!empty($template->description)) { - $searchResult->description = Parsedown::instance()->line($template->description); + $searchResult->description = Parsedown::instance()->setSafeMode(true)->line($template->description); } $searchResult->orientation = $template->orientation; $searchResult->width = $template->width; diff --git a/lib/Entity/DataSet.php b/lib/Entity/DataSet.php index 26239e9bd7..f68086b420 100644 --- a/lib/Entity/DataSet.php +++ b/lib/Entity/DataSet.php @@ -1098,7 +1098,7 @@ private function createTable() CREATE TABLE `dataset_' . $this->dataSetId . '` ( `id` int(11) NOT NULL AUTO_INCREMENT, PRIMARY KEY (`id`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ', []); } diff --git a/lib/Entity/Layout.php b/lib/Entity/Layout.php index a17ca54872..6214eecd15 100644 --- a/lib/Entity/Layout.php +++ b/lib/Entity/Layout.php @@ -1812,32 +1812,6 @@ public function assessWidgetStatus(Module $module, Widget $widget, int &$status) $template ->decorateProperties($widget) ->validateProperties('status'); - } else if ($templateId === 'elements' && $module->hasRequiredElements()) { - // Make sure our required element is present. - $providedElements = []; - - $widgetElements = $widget->getOptionValue('elements', null); - if (!empty($widgetElements)) { - // Elements will be JSON - $widgetElements = json_decode($widgetElements, true); - - // go through the arrays to get properties array inside of elements - // find fontFamily property, add it to fonts array if we do not already have it there - foreach (($widgetElements ?? []) as $widgetElement) { - foreach (($widgetElement['elements'] ?? []) as $element) { - $providedElements[] = $element['id']; - } - } - } - - $missingElements = array_diff($module->requiredElements, $providedElements); - - if (count($missingElements) > 0) { - throw new NotFoundException( - __('Missing one or more required elements'), - implode(',', $missingElements), - ); - } } // If we have validator interfaces, then use it now diff --git a/lib/Entity/Media.php b/lib/Entity/Media.php index c5d5a951ff..47c22d3af4 100644 --- a/lib/Entity/Media.php +++ b/lib/Entity/Media.php @@ -1,6 +1,6 @@ mediaId = $this->getStore()->insert(' INSERT INTO `media` (`name`, `type`, duration, originalFilename, userID, retired, moduleSystemFile, released, apiRef, valid, `createdDt`, `modifiedDt`, `enableStat`, `folderId`, `permissionsFolderId`, `orientation`, `width`, `height`) VALUES (:name, :type, :duration, :originalFileName, :userId, :retired, :moduleSystemFile, :released, :apiRef, :valid, :createdDt, :modifiedDt, :enableStat, :folderId, :permissionsFolderId, :orientation, :width, :height) diff --git a/lib/Factory/BaseFactory.php b/lib/Factory/BaseFactory.php index 082939a82a..42ba87340f 100644 --- a/lib/Factory/BaseFactory.php +++ b/lib/Factory/BaseFactory.php @@ -362,8 +362,15 @@ protected function parseComparisonOperator($variable) * @param array $params Array of parameters passed by reference * @param bool $useRegex flag to match against a regex pattern */ - public function nameFilter($tableName, $tableColumn, $terms, &$body, &$params, $useRegex = false, $logicalOperator = 'OR') - { + public function nameFilter( + $tableName, + $tableColumn, + $terms, + &$body, + &$params, + $useRegex = false, + $logicalOperator = 'OR' + ) { $i = 0; $tableAndColumn = $tableName . '.' . $tableColumn; @@ -377,7 +384,7 @@ public function nameFilter($tableName, $tableColumn, $terms, &$body, &$params, $ $searchName = trim($searchName); // Discard any incompatible - if ($searchName === '-' || empty($searchName)) { + if (empty(ltrim($searchName, '-')) || empty($searchName)) { continue; } diff --git a/lib/Factory/ResolutionFactory.php b/lib/Factory/ResolutionFactory.php index d34aa187ff..bf42887d85 100644 --- a/lib/Factory/ResolutionFactory.php +++ b/lib/Factory/ResolutionFactory.php @@ -1,6 +1,6 @@ $height ? '`intended_width` DESC' : '`intended_height` DESC'; + $resolutions = $this->query( - ['intended_width'], + $sort, [ 'disableUserCheck' => 1, - 'widthGe' => $width, - 'heightGe' => $height, + 'enabled' => 1, 'start' => 0, 'length' => 1 ] @@ -203,21 +206,11 @@ public function query($sortOrder = null, $filterBy = []) $params['width'] = $parsedFilter->getDouble('width'); } - if ($parsedFilter->getDouble('widthGe') !== null) { - $body .= ' AND intended_width >= :widthGe '; - $params['widthGe'] = $parsedFilter->getDouble('widthGe'); - } - if ($parsedFilter->getDouble('height') !== null) { $body .= ' AND intended_height = :height '; $params['height'] = $parsedFilter->getDouble('height'); } - if ($parsedFilter->getDouble('heightGe') !== null) { - $body .= ' AND intended_height >= :heightGe '; - $params['heightGe'] = $parsedFilter->getDouble('heightGe'); - } - if ($parsedFilter->getDouble('designerWidth') !== null) { $body .= ' AND width = :designerWidth '; $params['designerWidth'] = $parsedFilter->getDouble('designerWidth'); diff --git a/lib/Helper/Environment.php b/lib/Helper/Environment.php index 694419c795..a837fd8fbf 100644 --- a/lib/Helper/Environment.php +++ b/lib/Helper/Environment.php @@ -30,7 +30,7 @@ */ class Environment { - public static $WEBSITE_VERSION_NAME = '4.0.9'; + public static $WEBSITE_VERSION_NAME = '4.0.10'; public static $XMDS_VERSION = '7'; public static $XLF_VERSION = 4; public static $VERSION_REQUIRED = '8.1.0'; diff --git a/lib/Helper/LayoutUploadHandler.php b/lib/Helper/LayoutUploadHandler.php index d4421ed3ca..b86ffc2b33 100644 --- a/lib/Helper/LayoutUploadHandler.php +++ b/lib/Helper/LayoutUploadHandler.php @@ -1,6 +1,6 @@ getSanitizer($_REQUEST); // Parse parameters - $name = $params->getArray('name')[$index]; + $name = htmlspecialchars($params->getArray('name')[$index]); $tags = $controller->getUser()->featureEnabled('tag.tagging') - ? $params->getArray('tags')[$index] + ? htmlspecialchars($params->getArray('tags')[$index]) : ''; $template = $params->getCheckbox('template', ['default' => 0]); $replaceExisting = $params->getCheckbox('replaceExisting', ['default' => 0]); diff --git a/lib/Helper/XiboUploadHandler.php b/lib/Helper/XiboUploadHandler.php index bae67dd2c8..f5e666445d 100644 --- a/lib/Helper/XiboUploadHandler.php +++ b/lib/Helper/XiboUploadHandler.php @@ -1,6 +1,6 @@ getUser()->isQuotaFullByUser(true); // Get some parameters - $name = $this->getParam($index, 'name', $fileName); + $name = htmlspecialchars($this->getParam($index, 'name', $fileName)); $tags = $controller->getUser()->featureEnabled('tag.tagging') - ? $this->getParam($index, 'tags', '') + ? htmlspecialchars($this->getParam($index, 'tags', '')) : ''; // Guess the type diff --git a/lib/Middleware/Actions.php b/lib/Middleware/Actions.php index cf321b8cc0..b54dd8d73d 100644 --- a/lib/Middleware/Actions.php +++ b/lib/Middleware/Actions.php @@ -1,6 +1,6 @@ userTypeId == 1 && file_exists(PROJECT_ROOT . '/web/install/index.php')) { $container->get('logger')->notice('Install.php exists and shouldn\'t'); - $notifications[] = $factory->create(__('There is a problem with this installation. "install.php" should be deleted.')); + $notifications[] = $factory->create( + __('There is a problem with this installation, the web/install folder should be deleted.') + ); $extraNotifications++; // Test for web in the URL. diff --git a/lib/Service/HelpService.php b/lib/Service/HelpService.php index e4bdc5a1bb..5c720281d4 100644 --- a/lib/Service/HelpService.php +++ b/lib/Service/HelpService.php @@ -1,6 +1,6 @@ url = $this->helpBase . $helpLink->url; } if (!empty($helpLink->summary)) { - $helpLink->summary = \Parsedown::instance()->line($helpLink->summary); + $helpLink->summary = \Parsedown::instance()->setSafeMode(true)->line($helpLink->summary); } $this->links[$pageName][] = $helpLink; diff --git a/lib/Service/LogService.php b/lib/Service/LogService.php index fa35672a8d..eda228990b 100644 --- a/lib/Service/LogService.php +++ b/lib/Service/LogService.php @@ -1,6 +1,6 @@ mode) == 'test') { + if (strtolower($this->mode) == 'test' || $logAsError) { $paramSql = ''; foreach ($params as $key => $param) { $paramSql .= 'SET @' . $key . '=\'' . $param . '\';' . PHP_EOL; } - $this->log->debug($paramSql . str_replace(':', '@', $sql)); + + ($logAsError) + ? $this->log->error($paramSql . str_replace(':', '@', $sql)) + : $this->log->debug($paramSql . str_replace(':', '@', $sql)); } } diff --git a/lib/Service/LogServiceInterface.php b/lib/Service/LogServiceInterface.php index 6ffa11ced4..57674625bf 100644 --- a/lib/Service/LogServiceInterface.php +++ b/lib/Service/LogServiceInterface.php @@ -1,8 +1,8 @@ errorInfo[1] ?? $PDOException->getCode(); + + // syntax error, log the sql and params in error level. + if ($errorCode == 1064 && $this->log != null) { + $this->log->sql($sql, $params, true); + } + // Throw if we're not expected to reconnect. if (!$reconnect) { throw $PDOException; } - $errorCode = $PDOException->errorInfo[1] ?? $PDOException->getCode(); - if ($errorCode != 2006) { throw $PDOException; } else { diff --git a/lib/Widget/CurrenciesAndStocksProvider.php b/lib/Widget/CurrenciesAndStocksProvider.php index b20e5f5257..706458e8d4 100644 --- a/lib/Widget/CurrenciesAndStocksProvider.php +++ b/lib/Widget/CurrenciesAndStocksProvider.php @@ -74,7 +74,7 @@ public function fetchDuration(DurationProviderInterface $durationProvider): Widg if ($numItems > 1) { // If we have paging involved then work out the page count. - $itemsPerPage = $durationProvider->getWidget()->getOptionValue('itemsPerPage', 0); + $itemsPerPage = $durationProvider->getWidget()->getOptionValue('maxItemsPerPage', 0); if ($itemsPerPage > 0) { $numItems = ceil($numItems / $itemsPerPage); } diff --git a/lib/Widget/DataSetProvider.php b/lib/Widget/DataSetProvider.php index 2584300785..60070843de 100644 --- a/lib/Widget/DataSetProvider.php +++ b/lib/Widget/DataSetProvider.php @@ -1,6 +1,6 @@ getLog()->debug('fetchDuration: duration is per item'); // Count of rows - $lowerLimit = $durationProvider->getWidget()->getOptionValue('lowerLimit', 0); - $upperLimit = $durationProvider->getWidget()->getOptionValue('upperLimit', 15); - $numItems = $upperLimit - $lowerLimit; + $numItems = $durationProvider->getWidget()->getOptionValue('numItems', 0); // Workaround: dataset static (from v3 dataset view) has rowsPerPage instead. $rowsPerPage = $durationProvider->getWidget()->getOptionValue('rowsPerPage', 0); diff --git a/lib/routes.php b/lib/routes.php index cf62ad724a..997bbed15e 100644 --- a/lib/routes.php +++ b/lib/routes.php @@ -458,6 +458,7 @@ $app->post('/dataset', ['\Xibo\Controller\DataSet','add']) ->addMiddleware(new \Xibo\Middleware\FeatureAuth($app->getContainer(), ['dataset.add'])) ->setName('dataSet.add'); +$app->get('/rss/{psk}', ['\Xibo\Controller\DataSetRss','feed'])->setName('dataSet.rss.feed'); $app->group('', function (RouteCollectorProxy $group) { $group->put('/dataset/{id}', ['\Xibo\Controller\DataSet','edit'])->setName('dataSet.edit'); @@ -478,10 +479,10 @@ // RSS $group->get('/dataset/{id}/rss', ['\Xibo\Controller\DataSetRss','grid'])->setName('dataSet.rss.search'); $group->post('/dataset/{id}/rss', ['\Xibo\Controller\DataSetRss','add'])->setName('dataSet.rss.add'); - $group->put('/dataset/{id}/rss/{rssId}', ['\Xibo\Controller\DataSetRss','edit'])->setName('dataSet.rss.edit'); - $group->delete('/dataset/{id}/rss/{rssId}', ['\Xibo\Controller\DataSetRss','delete'])->setName('dataSet.rss.delete'); - $group->get('/rss/{psk}', ['\Xibo\Controller\DataSetRss','feed'])->setName('dataSet.rss.feed'); - + $group->put('/dataset/{id}/rss/{rssId}', ['\Xibo\Controller\DataSetRss','edit']) + ->setName('dataSet.rss.edit'); + $group->delete('/dataset/{id}/rss/{rssId}', ['\Xibo\Controller\DataSetRss','delete']) + ->setName('dataSet.rss.delete'); })->addMiddleware(new \Xibo\Middleware\FeatureAuth($app->getContainer(), ['dataset.modify'])); // Data diff --git a/locale/af.mo b/locale/af.mo index f8ab033bde..745d6ce571 100755 Binary files a/locale/af.mo and b/locale/af.mo differ diff --git a/locale/ar.mo b/locale/ar.mo index 745659b9f6..a4affb63ec 100755 Binary files a/locale/ar.mo and b/locale/ar.mo differ diff --git a/locale/bg.mo b/locale/bg.mo index 3a4107f93e..9f55a78ec6 100755 Binary files a/locale/bg.mo and b/locale/bg.mo differ diff --git a/locale/ca.mo b/locale/ca.mo index b9f66bf81e..eaee9ddda9 100755 Binary files a/locale/ca.mo and b/locale/ca.mo differ diff --git a/locale/cs.mo b/locale/cs.mo index e1a155d7b1..bf1eaa4d82 100755 Binary files a/locale/cs.mo and b/locale/cs.mo differ diff --git a/locale/da.mo b/locale/da.mo index 7e95abd90e..d477599f9b 100755 Binary files a/locale/da.mo and b/locale/da.mo differ diff --git a/locale/de.mo b/locale/de.mo index fd633e76f9..fedc7501d3 100755 Binary files a/locale/de.mo and b/locale/de.mo differ diff --git a/locale/default.pot b/locale/default.pot index 5175495fa0..fe647061d6 100755 --- a/locale/default.pot +++ b/locale/default.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-03-08 09:24+0000\n" +"POT-Creation-Date: 2024-04-05 13:49+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -11454,7 +11454,7 @@ msgstr "" #: cache/9f/9f9a142004ca4a1c914a4395fe586017.php:125 #: cache/a4/a4b5f52b1992bb349e8a30b19ff24645.php:80 -#: lib/Controller/Playlist.php:1702 lib/Controller/Library.php:2114 +#: lib/Controller/Playlist.php:1703 lib/Controller/Library.php:2114 #: lib/Controller/Layout.php:1656 msgid "Design" msgstr "" @@ -12461,7 +12461,7 @@ msgid "Report an application fault" msgstr "" #: cache/04/0499f0242d314e52ef2aed46b4a0b1e9.php:113 -#: lib/Middleware/Actions.php:102 +#: lib/Middleware/Actions.php:104 msgid "" "CMS configuration warning, it is very unlikely that /web/ should be in the " "URL. This usually means that the DocumentRoot of the web server is wrong and " @@ -17918,7 +17918,7 @@ msgstr "" #: cache/71/71725d40a3089432630feea639ab8751.php:112 #: cache/71/71725d40a3089432630feea639ab8751.php:116 -#: lib/Controller/Sessions.php:114 +#: lib/Controller/Sessions.php:119 msgid "Logout" msgstr "" @@ -18167,8 +18167,8 @@ msgstr "" #: cache/2c/2c268905994cc74a86fb2ebd71265416.php:137 msgid "" -"Assign all Media items based on their Library duration, and make it sticky " -"so that changes in the library are not pulled into Layouts." +"Assign all Media items to Playlists based on their Library duration, and " +"make it sticky so that changes in the library are not pulled into Layouts." msgstr "" #: cache/2c/2c268905994cc74a86fb2ebd71265416.php:147 @@ -20809,7 +20809,7 @@ msgstr "" #: lib/Controller/Widget.php:988 lib/Controller/Widget.php:1077 #: lib/Controller/Widget.php:1444 lib/Controller/Widget.php:1553 #: lib/Controller/Widget.php:1635 lib/Controller/Playlist.php:1311 -#: lib/Controller/Playlist.php:1475 lib/Controller/Layout.php:740 +#: lib/Controller/Playlist.php:1476 lib/Controller/Layout.php:740 #: lib/Controller/Layout.php:843 lib/Controller/Region.php:229 #: lib/Controller/Region.php:376 lib/Controller/Region.php:462 #: lib/Controller/Region.php:527 lib/Controller/Region.php:748 @@ -21488,7 +21488,7 @@ msgstr "" msgid "Please provide an assetId" msgstr "" -#: lib/Controller/Fault.php:109 lib/Entity/Layout.php:1936 +#: lib/Controller/Fault.php:109 lib/Entity/Layout.php:1910 msgid "Can't create ZIP. Error Code: " msgstr "" @@ -21623,42 +21623,42 @@ msgstr "" msgid "Please provide Media to Assign" msgstr "" -#: lib/Controller/Playlist.php:1335 +#: lib/Controller/Playlist.php:1336 msgid "You do not have permissions to use this media" msgstr "" -#: lib/Controller/Playlist.php:1340 +#: lib/Controller/Playlist.php:1341 #, php-format msgid "You cannot assign file type %s to a playlist" msgstr "" -#: lib/Controller/Playlist.php:1400 +#: lib/Controller/Playlist.php:1401 msgid "Media Assigned" msgstr "" -#: lib/Controller/Playlist.php:1485 +#: lib/Controller/Playlist.php:1486 msgid "Cannot Save empty region playlist. Please add widgets" msgstr "" -#: lib/Controller/Playlist.php:1504 +#: lib/Controller/Playlist.php:1505 msgid "Order Changed" msgstr "" -#: lib/Controller/Playlist.php:1642 lib/Controller/Playlist.php:1719 +#: lib/Controller/Playlist.php:1643 lib/Controller/Playlist.php:1720 msgid "Specified Playlist item is not in use." msgstr "" -#: lib/Controller/Playlist.php:1712 lib/Controller/Library.php:2124 +#: lib/Controller/Playlist.php:1713 lib/Controller/Library.php:2124 #: lib/Controller/Layout.php:1702 msgid "Preview Layout" msgstr "" -#: lib/Controller/Playlist.php:1787 +#: lib/Controller/Playlist.php:1788 #, php-format msgid "For Playlist %s Enable Stats Collection is set to %s" msgstr "" -#: lib/Controller/Playlist.php:1920 +#: lib/Controller/Playlist.php:1921 #, php-format msgid "Playlist %s moved to Folder %s" msgstr "" @@ -22001,7 +22001,7 @@ msgid "" "This function is available only to User who originally locked this Layout." msgstr "" -#: lib/Controller/Layout.php:3111 lib/Entity/Layout.php:2275 +#: lib/Controller/Layout.php:3111 lib/Entity/Layout.php:2249 msgid "Empty Region" msgstr "" @@ -22587,7 +22587,7 @@ msgstr "" msgid "File available only for SSSP displays" msgstr "" -#: lib/Controller/Sessions.php:183 +#: lib/Controller/Sessions.php:177 msgid "User Logged Out." msgstr "" @@ -22745,9 +22745,10 @@ msgstr "" msgid "Layout ID %d is locked by another User! Lock expires on: %s" msgstr "" -#: lib/Middleware/Actions.php:93 +#: lib/Middleware/Actions.php:94 msgid "" -"There is a problem with this installation. \"install.php\" should be deleted." +"There is a problem with this installation, the web/install folder should be " +"deleted." msgstr "" #: lib/Middleware/SAMLAuthentication.php:102 @@ -23190,40 +23191,36 @@ msgid "" "edge. Please check the allowed Resize Limit in Administration -> Settings" msgstr "" -#: lib/Entity/Layout.php:1837 -msgid "Missing one or more required elements" -msgstr "" - -#: lib/Entity/Layout.php:1853 +#: lib/Entity/Layout.php:1827 #, php-format msgid "%s is pending conversion" msgstr "" -#: lib/Entity/Layout.php:1858 +#: lib/Entity/Layout.php:1832 #, php-format msgid "" "%s is too large. Please ensure that none of the images in your layout are " "larger than your Resize Limit on their longest edge." msgstr "" -#: lib/Entity/Layout.php:1870 +#: lib/Entity/Layout.php:1844 msgid "Misconfigured Playlist" msgstr "" -#: lib/Entity/Layout.php:2264 +#: lib/Entity/Layout.php:2238 #, php-format msgid "There is an error with this Layout: %s" msgstr "" -#: lib/Entity/Layout.php:2343 lib/Entity/Layout.php:2472 +#: lib/Entity/Layout.php:2317 lib/Entity/Layout.php:2446 msgid "Not a Draft" msgstr "" -#: lib/Entity/Layout.php:2558 +#: lib/Entity/Layout.php:2532 msgid "Draft Layouts must have a parent" msgstr "" -#: lib/Entity/Layout.php:2693 lib/Listener/WidgetListener.php:199 +#: lib/Entity/Layout.php:2667 lib/Listener/WidgetListener.php:199 msgid "Cannot add the same SubPlaylist twice." msgstr "" @@ -25116,33 +25113,33 @@ msgstr "" msgid "There was a problem processing your request, please try again" msgstr "" -#: lib/Connector/AlphaVantageConnector.php:120 +#: lib/Connector/AlphaVantageConnector.php:123 msgid "Unable to contact the AlphaVantage API" msgstr "" -#: lib/Connector/AlphaVantageConnector.php:234 -msgid "Missing Items for Stocks Module" +#: lib/Connector/AlphaVantageConnector.php:238 +msgid "Add some stock symbols" msgstr "" -#: lib/Connector/AlphaVantageConnector.php:282 +#: lib/Connector/AlphaVantageConnector.php:286 msgid "Invalid symbol " msgstr "" -#: lib/Connector/AlphaVantageConnector.php:319 +#: lib/Connector/AlphaVantageConnector.php:323 msgid "Stocks data invalid" msgstr "" -#: lib/Connector/AlphaVantageConnector.php:395 +#: lib/Connector/AlphaVantageConnector.php:399 msgid "" "Missing Items for Currencies Module. Please provide items in order to " "proceed." msgstr "" -#: lib/Connector/AlphaVantageConnector.php:537 -#: lib/Connector/AlphaVantageConnector.php:544 -#: lib/Connector/AlphaVantageConnector.php:549 -#: lib/Connector/AlphaVantageConnector.php:614 -#: lib/Connector/AlphaVantageConnector.php:619 +#: lib/Connector/AlphaVantageConnector.php:541 +#: lib/Connector/AlphaVantageConnector.php:548 +#: lib/Connector/AlphaVantageConnector.php:553 +#: lib/Connector/AlphaVantageConnector.php:618 +#: lib/Connector/AlphaVantageConnector.php:623 msgid "Currency data invalid" msgstr "" @@ -25473,7 +25470,7 @@ msgid "" "please consult your administrator" msgstr "" -#: lib/Storage/PdoStorageService.php:412 +#: lib/Storage/PdoStorageService.php:417 #, php-format msgid "Failed to write to database after %d retries. Please try again later." msgstr "" diff --git a/locale/el.mo b/locale/el.mo index 83b35dffa7..b479ac6842 100755 Binary files a/locale/el.mo and b/locale/el.mo differ diff --git a/locale/en_GB.mo b/locale/en_GB.mo index 1ed12c1e78..f82fd3492b 100755 Binary files a/locale/en_GB.mo and b/locale/en_GB.mo differ diff --git a/locale/es.mo b/locale/es.mo index a936b68cf7..09b7c76df7 100755 Binary files a/locale/es.mo and b/locale/es.mo differ diff --git a/locale/et.mo b/locale/et.mo index 97843592a9..a9dc090379 100755 Binary files a/locale/et.mo and b/locale/et.mo differ diff --git a/locale/eu.mo b/locale/eu.mo index 140b1274a9..0e1d1945ac 100755 Binary files a/locale/eu.mo and b/locale/eu.mo differ diff --git a/locale/fa.mo b/locale/fa.mo index c5eeb676ce..c856bbef26 100755 Binary files a/locale/fa.mo and b/locale/fa.mo differ diff --git a/locale/fi.mo b/locale/fi.mo index 8d6dbeb80f..1d64cd6cb0 100755 Binary files a/locale/fi.mo and b/locale/fi.mo differ diff --git a/locale/fr.mo b/locale/fr.mo index 769a6451e9..3212178056 100755 Binary files a/locale/fr.mo and b/locale/fr.mo differ diff --git a/locale/fr_CA.mo b/locale/fr_CA.mo index d6435c8f2a..fd449c61f7 100644 Binary files a/locale/fr_CA.mo and b/locale/fr_CA.mo differ diff --git a/locale/he.mo b/locale/he.mo index 6afe3af6a2..67bda4b59c 100755 Binary files a/locale/he.mo and b/locale/he.mo differ diff --git a/locale/hi.mo b/locale/hi.mo index 49263c81b8..ca9313c422 100755 Binary files a/locale/hi.mo and b/locale/hi.mo differ diff --git a/locale/hr.mo b/locale/hr.mo index 87342bd12e..512a7fde6a 100755 Binary files a/locale/hr.mo and b/locale/hr.mo differ diff --git a/locale/hu.mo b/locale/hu.mo index 033560f0dd..996e2f0131 100755 Binary files a/locale/hu.mo and b/locale/hu.mo differ diff --git a/locale/id.mo b/locale/id.mo index 81a4f17854..338cc8e169 100755 Binary files a/locale/id.mo and b/locale/id.mo differ diff --git a/locale/it.mo b/locale/it.mo index 34b120d01d..f6e1e1d559 100755 Binary files a/locale/it.mo and b/locale/it.mo differ diff --git a/locale/ja.mo b/locale/ja.mo index 0264b9413a..cdcbb0412b 100755 Binary files a/locale/ja.mo and b/locale/ja.mo differ diff --git a/locale/ko.mo b/locale/ko.mo index 31a5ff01fb..4774ea706e 100755 Binary files a/locale/ko.mo and b/locale/ko.mo differ diff --git a/locale/ku.mo b/locale/ku.mo index 503e71fa37..c6e867e407 100755 Binary files a/locale/ku.mo and b/locale/ku.mo differ diff --git a/locale/lb.mo b/locale/lb.mo index 16c03dbe5e..20a84cf764 100755 Binary files a/locale/lb.mo and b/locale/lb.mo differ diff --git a/locale/lo.mo b/locale/lo.mo index f8a1cd4ca1..47cbaefbf2 100755 Binary files a/locale/lo.mo and b/locale/lo.mo differ diff --git a/locale/lt.mo b/locale/lt.mo index 16bd093124..3f85c16ee2 100755 Binary files a/locale/lt.mo and b/locale/lt.mo differ diff --git a/locale/nb.mo b/locale/nb.mo index 84f3eafd54..45ecb74941 100755 Binary files a/locale/nb.mo and b/locale/nb.mo differ diff --git a/locale/nl.mo b/locale/nl.mo index 979cfe8ba0..ad08dc6a14 100755 Binary files a/locale/nl.mo and b/locale/nl.mo differ diff --git a/locale/nl_NL.mo b/locale/nl_NL.mo index 8981af6725..4a04d45af4 100755 Binary files a/locale/nl_NL.mo and b/locale/nl_NL.mo differ diff --git a/locale/pl.mo b/locale/pl.mo index 91fdbcebca..e7d43953e2 100755 Binary files a/locale/pl.mo and b/locale/pl.mo differ diff --git a/locale/pt.mo b/locale/pt.mo index e79087e34e..cceb58932b 100755 Binary files a/locale/pt.mo and b/locale/pt.mo differ diff --git a/locale/pt_BR.mo b/locale/pt_BR.mo index 8e4fbb909f..b8e886277a 100755 Binary files a/locale/pt_BR.mo and b/locale/pt_BR.mo differ diff --git a/locale/ro.mo b/locale/ro.mo index e0ab67f160..2252a3f0d7 100755 Binary files a/locale/ro.mo and b/locale/ro.mo differ diff --git a/locale/ru.mo b/locale/ru.mo index 66394e4bed..e1ded504c5 100755 Binary files a/locale/ru.mo and b/locale/ru.mo differ diff --git a/locale/sk.mo b/locale/sk.mo index f43ee18595..f57e138da1 100755 Binary files a/locale/sk.mo and b/locale/sk.mo differ diff --git a/locale/sl.mo b/locale/sl.mo index da67dce445..8cff7af181 100755 Binary files a/locale/sl.mo and b/locale/sl.mo differ diff --git a/locale/sr@latin.mo b/locale/sr@latin.mo index 6f5bca5a8d..d6d447f2d1 100755 Binary files a/locale/sr@latin.mo and b/locale/sr@latin.mo differ diff --git a/locale/sv.mo b/locale/sv.mo index 591002d2a5..cf05c4a40e 100755 Binary files a/locale/sv.mo and b/locale/sv.mo differ diff --git a/locale/th.mo b/locale/th.mo index a83c66a7e7..41ca1dc8ed 100755 Binary files a/locale/th.mo and b/locale/th.mo differ diff --git a/locale/tr.mo b/locale/tr.mo index d31de6eabc..bf36d78269 100755 Binary files a/locale/tr.mo and b/locale/tr.mo differ diff --git a/locale/vi.mo b/locale/vi.mo index a9aad1584c..e94e8b1c58 100755 Binary files a/locale/vi.mo and b/locale/vi.mo differ diff --git a/locale/zh_CN.mo b/locale/zh_CN.mo index 6659092563..8590982549 100755 Binary files a/locale/zh_CN.mo and b/locale/zh_CN.mo differ diff --git a/locale/zh_TW.mo b/locale/zh_TW.mo index cc938ce517..cb5a3cf38b 100755 Binary files a/locale/zh_TW.mo and b/locale/zh_TW.mo differ diff --git a/modules/dataset.xml b/modules/dataset.xml index c015028480..48d8a321f4 100755 --- a/modules/dataset.xml +++ b/modules/dataset.xml @@ -81,10 +81,6 @@ 0 - - 1 - 0 - @@ -106,6 +102,12 @@ + + + 1 + 0 + + Duration is per item @@ -257,6 +259,13 @@ // meta: Metadata // properties: The properties for the widget +// Do we have a freshnessTimeout? +if (properties.freshnessTimeout > 0 + && moment(meta.cacheDt).add(properties.freshnessTimeout, 'minutes').isBefore(moment()) + ) { + return {dataItems: []}; +} + // Filter the items array we have been given if (parseInt(properties.randomiseItems) === 1) { // Sort the items in a random order (considering the entire list) @@ -317,13 +326,14 @@ return {dataItems: items}; 0) { - // Get the now moment time - var now = moment(); - // Set up an interval to check whether or not we have exceeded our freshness - var timer = setInterval(function() { + // Set up an interval to check whether we have exceeded our freshness + if (window.freshnessTimer) { + clearInterval(window.freshnessTimer); + } + window.freshnessTimer = setInterval(function() { if (moment(meta.cacheDt).add(properties.freshnessTimeout, 'minutes').isBefore(moment())) { - $("#content").empty().append(properties.noDataMessage); - clearInterval(timer); + // Reload the widget data. + XiboPlayer.playerWidgets[id].render(); } }, 10000); } diff --git a/modules/src/xibo-dataset-render.js b/modules/src/xibo-dataset-render.js index eb90f8aeeb..141e70c357 100644 --- a/modules/src/xibo-dataset-render.js +++ b/modules/src/xibo-dataset-render.js @@ -1,7 +1,7 @@ /* - * Copyright (C) 2023 Xibo Signage Ltd + * Copyright (C) 2024 Xibo Signage Ltd * - * Xibo - Digital Signage - http://www.xibo.org.uk + * Xibo - Digital Signage - https://xibosignage.com * * This file is part of Xibo. * @@ -40,7 +40,11 @@ jQuery.fn.extend({ if (options.rowsPerPage > 0) { // Cycle handles this for us - $(el).cycle({ + if ($(el).prop('isCycle')) { + $(el).cycle('destroy'); + } + + $(el).prop('isCycle', true).cycle({ fx: options.transition, timeout: duration * 1000, slides: '> table', diff --git a/modules/src/xibo-player.js b/modules/src/xibo-player.js index df2519cb87..1b31e21da1 100644 --- a/modules/src/xibo-player.js +++ b/modules/src/xibo-player.js @@ -335,6 +335,7 @@ const XiboPlayer = function() { if (data.length > 0) { let lastSlotFilled = null; + const filledPinnedSlot = []; dataLoop: for (const [dataItemKey] of Object.entries(data)) { let hasSlotFilled = false; @@ -356,6 +357,11 @@ const XiboPlayer = function() { const slotItems = itemObj.items; const pinnedItems = itemObj.pinnedItems; const currentSlot = itemObj.slot; + let nextSlot = currentSlot + 1; + + if (nextSlot > maxSlot) { + nextSlot = currentSlot; + } // Skip if currentKey is less than the currentSlot // This occurs when a data slot has been skipped @@ -411,6 +417,13 @@ const XiboPlayer = function() { lastSlotFilled = currentSlot; } + if (pinnedSlots.includes(currentSlot) && + lastSlotFilled === currentSlot && + !filledPinnedSlot.includes(currentSlot) + ) { + filledPinnedSlot.push(currentSlot); + } + itemObj.dataKeys = [ ...itemObj.dataKeys, currentKey, @@ -420,6 +433,18 @@ const XiboPlayer = function() { hasSlotFilled = false; if (lastSlotFilled % maxSlot === 0) { lastSlotFilled = null; + } else if (currentKey > maxSlot && + nextSlot !== currentSlot && + pinnedSlots.includes(nextSlot) && + filledPinnedSlot.includes(nextSlot) + ) { + // Next slot is a pinned slot and has been filled + // So, current item must be passed to next non-pinned slot + if (nextSlot === maxSlot) { + lastSlotFilled = null; + } else { + lastSlotFilled = nextSlot; + } } break; @@ -1686,7 +1711,8 @@ XiboPlayer.prototype.renderOptions = function(currentWidget, globalOptions) { globalOptions, { duration: currentWidget.duration, - pauseEffectOnStart: globalOptions.pauseEffectOnStart ?? false, + pauseEffectOnStart: + globalOptions.pauseEffectOnStart ?? true, isPreview: currentWidget.isPreview, isEditor: currentWidget.isEditor, }, diff --git a/modules/src/xibo-text-render.js b/modules/src/xibo-text-render.js index 5990baf6aa..d302348298 100644 --- a/modules/src/xibo-text-render.js +++ b/modules/src/xibo-text-render.js @@ -369,7 +369,6 @@ jQuery.fn.extend({ .css({'white-space': 'normal', float: 'none'}); } - if (!options.pauseEffectOnStart) { // Set some options on the extra DIV and make it a marquee if (isUseNewMarquee) { diff --git a/modules/templates/article-static.xml b/modules/templates/article-static.xml index d545b9c869..d39e4dd156 100644 --- a/modules/templates/article-static.xml +++ b/modules/templates/article-static.xml @@ -102,7 +102,7 @@ {% if css or itemsSideBySide or backgroundColor or textDirection == "rtl" %} {% if itemsSideBySide %}.text-render-item, .page { float: left; }{% endif %} {% if textDirection == "rtl" %}#content { direction: rtl; }{% endif %} - {% if backgroundColor %}body { background-color: {{backgroundColor}}; }{% endif %} + {% if backgroundColor %}body { background-color: {{backgroundColor}} !important; }{% endif %} {{css|raw}} {% endif %} ]]> @@ -207,7 +207,7 @@ $(target).xiboLayoutAnimate(properties); } {% if backgroundColor %} body { - background-color: {{backgroundColor}}; + background-color: {{backgroundColor}} !important; } {% endif %} {% if copyright %} @@ -382,7 +382,7 @@ html { {% if backgroundColor %} body { - background-color: {{backgroundColor}}; + background-color: {{backgroundColor}} !important; } {% endif %} @@ -591,7 +591,7 @@ html { {% if backgroundColor %} body { - background-color: {{backgroundColor}}; + background-color: {{backgroundColor}} !important; } {% endif %} @@ -827,7 +827,7 @@ html { {% if backgroundColor %} body { - background-color: {{backgroundColor}}; + background-color: {{backgroundColor}} !important; } {% endif %} @@ -1043,7 +1043,7 @@ html { {% if backgroundColor %} body { - background-color: {{backgroundColor}}; + background-color: {{backgroundColor}} !important; } {% endif %} @@ -1296,7 +1296,7 @@ $(target).xiboLayoutAnimate(properties); ]]> @@ -3305,7 +3305,7 @@ $(target).xiboLayoutAnimate(properties); ]]> diff --git a/modules/templates/forecast-static.xml b/modules/templates/forecast-static.xml index b391aa7a00..95c6d7c54b 100644 --- a/modules/templates/forecast-static.xml +++ b/modules/templates/forecast-static.xml @@ -5560,7 +5560,6 @@ h1 { } body { - background-color: #000; {% if fontFamily %} font-family: {{fontFamily}}; {% else %} @@ -5569,6 +5568,10 @@ body { line-height: 1; } +#content > div { + background-color: #000; +} + .container { width: 960px !important; height: 180px !important; @@ -5909,7 +5912,6 @@ h1 { } body { - background-color: #000; {% if fontFamily %} font-family: {{fontFamily}}; {% else %} @@ -5918,6 +5920,10 @@ body { line-height: 1; } +#content > div { + background-color: #000; +} + .container { width: 189px !important; height: 900px !important; diff --git a/modules/templates/global-elements.xml b/modules/templates/global-elements.xml index 11c9d8792c..a7b690aa94 100644 --- a/modules/templates/global-elements.xml +++ b/modules/templates/global-elements.xml @@ -375,7 +375,6 @@ $(target).find('.date').each(function(_idx, dateEl){ Image URL Enter the URL of the image you want to use. - https://xibosignage.com/dist/img/header-logo.svg Opacity @@ -447,15 +446,13 @@ $(target).find('.date').each(function(_idx, dateEl){ - {{#if url}} - - {{/if}} + ]]> diff --git a/modules/templates/stock-static.xml b/modules/templates/stock-static.xml index 8467f5046c..65a98558fc 100644 --- a/modules/templates/stock-static.xml +++ b/modules/templates/stock-static.xml @@ -216,7 +216,7 @@ body { height: 420px !important; } -{% if backgroundColor %}body { background-color: {{backgroundColor}}; }{% endif %} +{% if backgroundColor %}body { background-color: {{backgroundColor}} !important; }{% endif %} .text-right { text-align: right; @@ -495,9 +495,7 @@ $(target).xiboLayoutAnimate(properties); The format to apply to all dates returned by the Widget. #DATE_FORMAT# - - Items per Page - This is the intended number of items on each page. + 1 @@ -541,7 +539,7 @@ body { line-height: 1; } -{% if backgroundColor %}body { background-color: {{backgroundColor}}; }{% endif %} +{% if backgroundColor %}body { background-color: {{backgroundColor}} !important; }{% endif %} .text-right { text-align: right; diff --git a/modules/text.xml b/modules/text.xml index c64f37d954..ed64dc488b 100755 --- a/modules/text.xml +++ b/modules/text.xml @@ -103,7 +103,7 @@ ]]> diff --git a/modules/worldclock-analogue.xml b/modules/worldclock-analogue.xml index 38a44a5152..182c7dc0e1 100644 --- a/modules/worldclock-analogue.xml +++ b/modules/worldclock-analogue.xml @@ -316,7 +316,7 @@ =1.12.0" } }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true - }, "tough-cookie": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", @@ -11667,12 +11174,6 @@ "integrity": "sha512-WZGXGstmCWgeevgTL54hrCuw1dyMQIzWy7ZfqRJfSmJZBwklI15egmQytFP6bPidmw3M8d5yEowl1niq4vmqZw==", "dev": true }, - "tryer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", - "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==", - "dev": true - }, "tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -11708,16 +11209,6 @@ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -11979,12 +11470,6 @@ "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", "dev": true }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true - }, "untildify": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", @@ -12080,12 +11565,6 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "dev": true - }, "uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -12098,12 +11577,6 @@ "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true - }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", @@ -12246,41 +11719,6 @@ } } }, - "webpack-bundle-analyzer": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.9.0.tgz", - "integrity": "sha512-Ob8amZfCm3rMB1ScjQVlbYYUEJyEjdEtQ92jqiFUYt5VkEeO2v5UMbv49P/gnmCZm3A6yaFQzCBvpZqN4MUsdA==", - "dev": true, - "requires": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1", - "bfj": "^6.1.1", - "chalk": "^2.4.1", - "commander": "^2.18.0", - "ejs": "^2.6.1", - "express": "^4.16.3", - "filesize": "^3.6.1", - "gzip-size": "^5.0.0", - "lodash": "^4.17.19", - "mkdirp": "^0.5.1", - "opener": "^1.5.1", - "ws": "^6.0.0" - }, - "dependencies": { - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - } - } - }, "webpack-cli": { "version": "4.10.0", "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", @@ -12495,15 +11933,6 @@ "mkdirp": "^0.5.1" } }, - "ws": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", - "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0" - } - }, "xibo-interactive-control": { "version": "git+https://github.com/xibosignage/xibo-interactive-control.git#44ad744a5a2a0af9e7fcdb828dbdbeff45cc40f0", "from": "git+https://github.com/xibosignage/xibo-interactive-control.git#44ad744a5a2a0af9e7fcdb828dbdbeff45cc40f0" diff --git a/package.json b/package.json index d82dacf204..25d4350925 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,6 @@ "uglifyjs-webpack-plugin": "^2.2.0", "url-loader": "^1.1.2", "webpack": "^5.90.3", - "webpack-bundle-analyzer": "^3.6.1", "webpack-cli": "^4.5.0", "webpack-merge": "^4.2.2" }, diff --git a/ui/src/editor-core/bottombar.js b/ui/src/editor-core/bottombar.js index c3ff239edc..8f393827e4 100644 --- a/ui/src/editor-core/bottombar.js +++ b/ui/src/editor-core/bottombar.js @@ -71,30 +71,14 @@ Bottombar.prototype.render = function(object) { }, )); - // Preview request path - let requestPath = urlsForApi.layout.preview.url; - requestPath = requestPath.replace(':id', lD.layout.layoutId); - // Handle play button ( play or pause ) this.DOMObject.find('#play-btn').click(function() { if (lD.viewer.previewPlaying) { - this.DOMObject.find('#play-btn i') - .removeClass('fa-stop-circle') - .addClass('fa-play-circle') - .attr('title', bottombarTrans.playPreviewLayout); - app.viewer.render(true); + app.viewer.stopPreview(); } else { - lD.viewer.playPreview( - requestPath, - lD.viewer.containerObjectDimensions, - ); - this.DOMObject.find('#play-btn i') - .removeClass('fa-play-circle') - .addClass('fa-stop-circle') - .attr('title', bottombarTrans.stopPreviewLayout); - lD.viewer.previewPlaying = true; + app.viewer.playPreview(); } - }.bind(this)); + }); } else if (object.type == 'region') { // Render region toolbar this.DOMObject.html(bottomBarViewerTemplate( diff --git a/ui/src/editor-core/history-manager.js b/ui/src/editor-core/history-manager.js index 35c4e39615..30a4a7b7b2 100644 --- a/ui/src/editor-core/history-manager.js +++ b/ui/src/editor-core/history-manager.js @@ -464,10 +464,13 @@ HistoryManager.prototype.removeLastChange = function() { /** * Render Manager + * @param {boolean} reloadToolbar - force render toolbar? */ -HistoryManager.prototype.render = function() { +HistoryManager.prototype.render = function( + reloadToolbar = true, +) { // Upload bottom bar if exists - if (this.parent.bottombar) { + if (this.parent.bottombar && reloadToolbar) { this.parent.bottombar.render(); } diff --git a/ui/src/editor-core/properties-panel.js b/ui/src/editor-core/properties-panel.js index 86d22681bc..b17345d766 100644 --- a/ui/src/editor-core/properties-panel.js +++ b/ui/src/editor-core/properties-panel.js @@ -575,11 +575,11 @@ PropertiesPanel.prototype.render = function( dataToRender.repeatDataActive = hasData; // Check required elements - const errorMessage = target.checkRequiredElements(); + const warningMessage = target.checkRequiredElements(); - if (errorMessage != '') { - dataToRender.showErrorMessage = true; - dataToRender.errorMessage = errorMessage; + if (warningMessage != '') { + dataToRender.showWarningMessage = true; + dataToRender.warningMessage = warningMessage; } } diff --git a/ui/src/helpers/form-helpers.js b/ui/src/helpers/form-helpers.js index 19bb8df893..ea0582f4ae 100644 --- a/ui/src/helpers/form-helpers.js +++ b/ui/src/helpers/form-helpers.js @@ -539,14 +539,24 @@ const formHelpers = function() { // COLORS // Background color for the editor - const backgroundColor = - ( - this.mainObject != undefined && - typeof this.mainObject.backgroundColor != 'undefined' && - this.mainObject.backgroundColor != null - ) ? - this.mainObject.backgroundColor : - this.defaultBackgroundColor; + let backgroundColor = this.defaultBackgroundColor; + + // From layout editor + if ( + this.mainObject != undefined && + typeof this.mainObject.backgroundColor != 'undefined' && + this.mainObject.backgroundColor != null + ) { + backgroundColor = this.mainObject.backgroundColor; + } + + // From inline playlist editor + if ( + this.namespace.inline && + lD && lD.layout && lD.layout.backgroundColor + ) { + backgroundColor = lD.layout.backgroundColor; + } // Choose a complementary color const color = $c.complement(backgroundColor); @@ -1936,14 +1946,25 @@ const formHelpers = function() { }); // Add same background colour to editor container as the layout's - const backgroundColor = - ( + let backgroundColor = self.defaultBackgroundColor; + + // From layout editor + if ( self.mainObject != undefined && typeof self.mainObject.backgroundColor != 'undefined' && self.mainObject.backgroundColor != null - ) ? - self.mainObject.backgroundColor : - self.defaultBackgroundColor; + ) { + backgroundColor = self.mainObject.backgroundColor; + } + + // From inline playlist editor + if ( + self.namespace.inline && + lD && lD.layout && lD.layout.backgroundColor + ) { + backgroundColor = lD.layout.backgroundColor; + } + $editorContainer.parent().css('background-color', backgroundColor); // Get button container diff --git a/ui/src/layout-editor/main.js b/ui/src/layout-editor/main.js index 31118362bd..9de479db69 100644 --- a/ui/src/layout-editor/main.js +++ b/ui/src/layout-editor/main.js @@ -674,7 +674,7 @@ lD.refreshEditor = function( this.bottombar.render(this.selectedObject); // Manager ( hidden ) - this.historyManager.render(); + this.historyManager.render(false); // Properties panel and viewer (reloadPropertiesPanel) && this.propertiesPanel.render(this.selectedObject); @@ -3003,10 +3003,13 @@ lD.openContextMenu = function(obj, position = {x: 0, y: 0}) { layoutObject.type === 'element-group' ); - // If target is a frame, send single widget info + // If target is a frame or zone, send single widget info const singleWidget = ( layoutObject.type === 'region' && - layoutObject.subType === 'frame' + ( + layoutObject.subType === 'frame' || + layoutObject.subType === 'zone' + ) ) ? Object.values(layoutObject.widgets)[0] : {}; // If target is group or element group, send parent widget @@ -3397,12 +3400,15 @@ lD.openContextMenu = function(obj, position = {x: 0, y: 0}) { const property = target.data('property'); const propertyType = target.data('propertyType'); - // If we're editing permissions and it's a frame + // If we're editing permissions and it's a frame ( or zone ) // edit the widget's permissions instead if ( property === 'PermissionsWidget' && layoutObject.type === 'region' && - layoutObject.subType === 'frame' + ( + layoutObject.subType === 'frame' || + layoutObject.subType === 'zone' + ) ) { // Call edit for widget instead const regionWidget = Object.values(layoutObject.widgets)[0]; diff --git a/ui/src/layout-editor/viewer.js b/ui/src/layout-editor/viewer.js index 6717e56829..45b62eb950 100644 --- a/ui/src/layout-editor/viewer.js +++ b/ui/src/layout-editor/viewer.js @@ -288,18 +288,13 @@ Viewer.prototype.render = function(forceReload = false, target = {}) { // Render full layout const $viewerContainer = this.DOMObject; - // If preview is playing, refresh the bottombar - if (this.previewPlaying && this.parent.selectedObject.type == 'layout') { - this.parent.bottombar.render(this.parent.selectedObject); - } - // Clear temp data lD.common.clearContainer($viewerContainer); // Show loading template $viewerContainer.html(loadingTemplate()); - // Set preview play as false + // When rendering, preview is always set to false this.previewPlaying = false; // Reset container properties @@ -1235,12 +1230,6 @@ Viewer.prototype.renderRegion = function( }, }); - // Update bottom bar - lD.bottombar.render( - (widgetToLoad) ? widgetToLoad : lD.selectedObject, - res, - ); - // If inline editor is on, show the controls for it // ( fixing asyc load problem ) if (lD.propertiesPanel.inlineEditor) { @@ -1661,7 +1650,7 @@ Viewer.prototype.renderElementContent = function( template.stencil : template.parent.stencil; let hbsTemplate = Handlebars.compile( (stencil?.hbs) ? - stencil.hbs: + stencil.hbs : '', ); @@ -1672,7 +1661,7 @@ Viewer.prototype.renderElementContent = function( ) { const styleTemplate = Handlebars.compile( (stencil?.style) ? - stencil.style: + stencil.style : '', ); @@ -1864,7 +1853,7 @@ Viewer.prototype.renderElementContent = function( } if (element.elementType === 'dataset' && elData) { - if (extendOverrideKey !==null) { + if (extendOverrideKey !== null) { convertedProperties[extendOverrideKey] = elData.hasOwnProperty(convertedProperties.datasetField) ? elData[convertedProperties.datasetField] : ''; @@ -1976,18 +1965,28 @@ Viewer.prototype.validateElement = function( 'canvas', ); - // Get error message ( from element or group ) + // Get error message container let $messageContainer = (hasGroup) ? - $groupContainer.find('> .invalid-parent') : - $elementContainer.find('> .invalid-parent'); + $groupContainer.find('> .message-container') : + $elementContainer.find('> .message-container'); + + // If there's no message container, add it + if ($messageContainer.length === 0) { + $messageContainer = $('
'); + + if (hasGroup) { + $messageContainer.appendTo($groupContainer); + } else { + $messageContainer.appendTo($elementContainer); + } + } + + // Get error message + let $errorMessage = $messageContainer.find('.error-message'); // Is widget not valid? const isNotValid = ( - !$.isEmptyObject(parentWidget.validateData) || - ( - parentWidget.requiredElements && - parentWidget.requiredElements.valid === false - ) + !$.isEmptyObject(parentWidget.validateData) ); // If parent widget isn't valid, show error message @@ -1995,19 +1994,75 @@ Viewer.prototype.validateElement = function( const errorArray = []; // Create message container if it doesn't exist - if ($messageContainer.length === 0) { - $messageContainer = $( - `
+ if ($errorMessage.length === 0) { + $errorMessage = $( + `
+ +
`); + + if (hasGroup) { + // Remove message from element if we're going to create the group one + $elementContainer.find('.error-message').remove(); + } + + $errorMessage.appendTo($messageContainer); + } + + // Request message + (parentWidget.validateData.errorMessage) && + errorArray.push( + '

' + + parentWidget.validateData.errorMessage + + '

'); + + // Sample data message + (parentWidget.validateData.sampleDataMessage) && + errorArray.push( + '

( ' + + parentWidget.validateData.sampleDataMessage + + ' )

'); + + // Set title/tooltip + $errorMessage.tooltip('dispose') + .prop('title', '
' + + errorArray.join('') + '
'); + $errorMessage.tooltip(); + + // Show tooltip + $errorMessage.removeClass('d-none'); + } else { + // Remove error message + $errorMessage.remove(); + } + + // Get warning message ( from element or group ) + let $warningMessage = (hasGroup) ? + $groupContainer.find('.warning-message') : + $elementContainer.find('.warning-message'); + + // Needs warning message? + const needsWarningMessage = ( + parentWidget.requiredElements && + parentWidget.requiredElements.valid === false + ); + + // Warning message needed + if (needsWarningMessage) { + const errorArray = []; + + // Create message container if it doesn't exist + if ($warningMessage.length === 0) { + $warningMessage = $( + `
`); if (hasGroup) { // Remove message from element if we're going to create the group one - $elementContainer.find('> .invalid-parent').remove(); - $messageContainer.appendTo($groupContainer); - } else { - $messageContainer.appendTo($elementContainer); + $elementContainer.find('.warning-message').remove(); } + + $warningMessage.appendTo($messageContainer); } // Check required elements @@ -2029,31 +2084,17 @@ Viewer.prototype.validateElement = function( requiredElementsErrorMessage + '

'); - // Request message - (parentWidget.validateData.errorMessage) && - errorArray.push( - '

' + - parentWidget.validateData.errorMessage + - '

'); - - // Sample data message - (parentWidget.validateData.sampleDataMessage) && - errorArray.push( - '

( ' + - parentWidget.validateData.sampleDataMessage + - ' )

'); - // Set title/tooltip - $messageContainer.tooltip('dispose') + $warningMessage.tooltip('dispose') .prop('title', '
' + - errorArray.join('') + '
'); - $messageContainer.tooltip(); + errorArray.join('') + '
'); + $warningMessage.tooltip(); // Show tooltip - $messageContainer.removeClass('d-none'); + $warningMessage.removeClass('d-none'); } else { // Remove error message - $messageContainer.remove(); + $warningMessage.remove(); } }; @@ -2072,10 +2113,26 @@ Viewer.prototype.validateElementData = function( $elementContainer.parents('.designer-element-group'); const hasGroup = Boolean(element.groupId); - // Get error message ( from element or group ) + // Get error message container let $messageContainer = (hasGroup) ? - $groupContainer.find('> .empty-element-data') : - $elementContainer.find('> .empty-element-data'); + $groupContainer.find('> .message-container') : + $elementContainer.find('> .message-container'); + + // If there's no message container, add it + if ($messageContainer.length === 0) { + $messageContainer = $('
'); + + if (hasGroup) { + $messageContainer.appendTo($groupContainer); + } else { + $messageContainer.appendTo($elementContainer); + } + } + + // Get error message ( from element or group ) + let $message = (hasGroup) ? + $groupContainer.find('.empty-element-data') : + $elementContainer.find('.empty-element-data'); const isNotValid = !widgetData || typeof widgetData === 'undefined' || widgetData === ''; @@ -2085,19 +2142,18 @@ Viewer.prototype.validateElementData = function( const elementType = element.elementType; // Create message if doesn't exist - if ($messageContainer.length === 0) { - $messageContainer = $( + if ($message.length === 0) { + $message = $( `
- +
`); if (hasGroup) { // Remove message from element if we're going to create the group one - $elementContainer.find('> .empty-element-data').remove(); - $messageContainer.appendTo($groupContainer); - } else { - $messageContainer.appendTo($elementContainer); + $elementContainer.find('.empty-element-data').remove(); } + + $message.appendTo($messageContainer); } errorArray.push( @@ -2113,53 +2169,94 @@ Viewer.prototype.validateElementData = function( '

'); // Set title/tooltip - $messageContainer.tooltip('dispose') + $message.tooltip('dispose') .prop('title', '
' + errorArray.join('') + '
'); - $messageContainer.tooltip(); + $message.tooltip(); // Show tooltip - $messageContainer.removeClass('d-none'); + $message.removeClass('d-none'); } else { // Remove message - $messageContainer.remove(); + $message.remove(); } }; /** * Play preview - * @param {string} url - Preview url - * @param {object} dimensions - Preview dimensions + * @param {object=} dimensions - Preview dimensions */ -Viewer.prototype.playPreview = function(url, dimensions) { +Viewer.prototype.playPreview = function(dimensions) { + const app = this.parent; + + // Preview request path + const requestPath = urlsForApi.layout.preview.url + .replace(':id', app.layout.layoutId); + + // If dimensions aren't set, use main container + if (!dimensions) { + dimensions = this.containerObjectDimensions; + } + // Compile layout template with data const html = viewerLayoutPreview({ - url: url, + url: requestPath, width: dimensions.width, height: dimensions.height, }); // Clear temp data - lD.common.clearContainer(this.DOMObject.find('.layout-player')); + app.common.clearContainer(this.DOMObject.find('.layout-player')); // Append layout html to the main div this.DOMObject.find('.layout-player').html(html); + + // Update playing button on bottombar + app.bottombar.DOMObject.find('#play-btn i') + .removeClass('fa-play-circle') + .addClass('fa-stop-circle') + .attr('title', bottombarTrans.stopPreviewLayout); + + // Mark as playing + this.previewPlaying = true; +}; + +/** + * Stop preview + */ +Viewer.prototype.stopPreview = function() { + const app = this.parent; + + // Reload bottombar to original state + app.bottombar.render(app.selectedObject); + + // Reload viewer ( which stops preview ) + this.render(true); }; /** * Toggle fullscreen */ Viewer.prototype.toggleFullscreen = function() { + const app = this.parent; + // If inline editor is opened, needs to be saved/closed if (this.inlineEditorState == 2) { // Close editor content this.closeInlineEditorContent(); } + // Was preview playing? + const previewWasPlaying = this.previewPlaying; + + // Is preview playing? Stop preview + if (previewWasPlaying) { + this.stopPreview(); + } this.DOMObject.parents('#layout-viewer-container').toggleClass('fullscreen'); - this.parent.editorContainer.toggleClass('fullscreen-mode'); + app.editorContainer.toggleClass('fullscreen-mode'); - this.fullscreenMode = this.parent.editorContainer.hasClass('fullscreen-mode'); + this.fullscreenMode = app.editorContainer.hasClass('fullscreen-mode'); // Add attribute to body for editor fullscreen to be used by the moveable if (this.fullscreenMode) { @@ -2169,6 +2266,11 @@ Viewer.prototype.toggleFullscreen = function() { } this.update(); + + // Is preview playing? Restart + if (previewWasPlaying) { + this.playPreview(); + } }; /** diff --git a/ui/src/playlist-editor/main.js b/ui/src/playlist-editor/main.js index 4d07dcacd6..da5ae8aee5 100644 --- a/ui/src/playlist-editor/main.js +++ b/ui/src/playlist-editor/main.js @@ -1,7 +1,7 @@ /* - * Copyright (C) 2023 Xibo Signage Ltd + * Copyright (C) 2024 Xibo Signage Ltd * - * Xibo - Digital Signage - http://www.xibo.org.uk + * Xibo - Digital Signage - https://xibosignage.com * * This file is part of Xibo. * @@ -289,8 +289,13 @@ pE.selectObject = function({ // simulate to add those items to a object if (target.data('type') == 'region') { pE.importFromProvider(this.toolbar.selectedQueue).then((res) => { - // Add media queue to playlist - this.playlist.addMedia(res, positionToAdd); + // If res is empty, it means that the import failed + if (res.length === 0) { + console.error(errorMessagesTrans.failedToImportMedia); + } else { + // Add media queue to playlist + this.playlist.addMedia(res, positionToAdd); + } }).catch(function() { toastr.error(errorMessagesTrans.importingMediaFailed); }); @@ -582,7 +587,7 @@ pE.refreshEditor = function( // Render containers (reloadToolbar) && this.toolbar.render(); - this.historyManager.render(); + this.historyManager.render(false); // Render timeline this.timeline.render(); diff --git a/ui/src/playlist-editor/playlist.js b/ui/src/playlist-editor/playlist.js index 9cae4198e9..52036a7d95 100644 --- a/ui/src/playlist-editor/playlist.js +++ b/ui/src/playlist-editor/playlist.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Xibo Signage Ltd + * Copyright (C) 2024 Xibo Signage Ltd * * Xibo - Digital Signage - https://xibosignage.com * @@ -167,7 +167,12 @@ Playlist.prototype.addObject = function( if (draggableType == 'media') { // Adding media from search tab to a region if ($(draggable).hasClass('from-provider')) { pE.importFromProvider([$(draggable).data('providerData')]).then((res) => { - this.addMedia(res, addToPosition); + // If res is empty, it means that the import failed + if (res.length === 0) { + console.error(errorMessagesTrans.failedToImportMedia); + } else { + this.addMedia(res, addToPosition); + } }).catch(function() { toastr.error(errorMessagesTrans.importingMediaFailed); }); diff --git a/ui/src/style/layout-editor.scss b/ui/src/style/layout-editor.scss index 9fa7474467..af455c32d1 100644 --- a/ui/src/style/layout-editor.scss +++ b/ui/src/style/layout-editor.scss @@ -1243,7 +1243,7 @@ body.editor-opened { opacity: 0.3; } - & > .invalid-parent, & > .empty-element-data { + & > .message-container { z-index: $viewer-object-group-bts-z-index !important; } @@ -1271,24 +1271,43 @@ body.editor-opened { } .designer-element, .designer-element-group { - .invalid-parent, .empty-element-data { + .message-container { + display: flex; + flex-direction: column-reverse; + gap: 4px; position: absolute; - padding: 2px 5px; - border-radius: 4px; - font-weight: bold; - z-index: 100; left: auto; top: auto; right: 2px; bottom: 2px; + z-index: 100; + } + + .error-message, .empty-element-data, .warning-message { + padding: 2px 5px; + border-radius: 4px; + font-weight: bold; background-color: $xibo-color-semantic-error; color: $xibo-color-neutral-0; opacity: 0.6; } + .warning-message { + color: $xibo-color-neutral-1000; + background-color: $xibo-color-semantic-warning; + } + + .empty-element-data { + background-color: $xibo-color-semantic-info; + } + &:hover { - .invalid-parent, .empty-element-data { + .error-message, .empty-element-data, .warning-message { opacity: 0.8; + + &:hover { + opacity: 1; + } } } } @@ -1992,7 +2011,7 @@ div#bg_media_name { } } - .error-message { + .error-message, .warning-message { display: none; } } diff --git a/ui/src/style/toolbar.scss b/ui/src/style/toolbar.scss index 63558cc579..15456e9858 100644 --- a/ui/src/style/toolbar.scss +++ b/ui/src/style/toolbar.scss @@ -493,20 +493,25 @@ } .provider { - background-color: $xibo-color-neutral-0; position: absolute; opacity: 0.6; bottom: 0; right: 0; - width: 60%; + width: 60px; + height: 40px; + display: flex; + justify-content: flex-end; + align-items: flex-end; &:hover { opacity: 0.8; } img { - width: 100%; + max-width: 100%; + max-height: 100%; padding: 2px 6px; + background-color: $xibo-color-neutral-0; } } diff --git a/ui/src/templates/context-menu.hbs b/ui/src/templates/context-menu.hbs index c13b848a59..ee2ef59151 100644 --- a/ui/src/templates/context-menu.hbs +++ b/ui/src/templates/context-menu.hbs @@ -67,7 +67,7 @@ {{/if}} - {{#if isFrame}} + {{#if isFrameOrZone}} {{#if widget.isPermissionsModifiable}}
{{trans.editPermissions}} diff --git a/ui/src/templates/forms/widget.hbs b/ui/src/templates/forms/widget.hbs index 546c56d56a..ed980ae868 100644 --- a/ui/src/templates/forms/widget.hbs +++ b/ui/src/templates/forms/widget.hbs @@ -11,6 +11,10 @@ {{> inputs/message variant="danger" customClass="mt-2 mb-2" title=errorMessage}} {{/if}} + {{#if showWarningMessage}} + {{> inputs/message variant="warning" customClass="mt-2 mb-2" title=warningMessage}} + {{/if}} +
diff --git a/ui/src/templates/toolbar-card-layout-template.hbs b/ui/src/templates/toolbar-card-layout-template.hbs index c68be5dd58..482f22f95c 100644 --- a/ui/src/templates/toolbar-card-layout-template.hbs +++ b/ui/src/templates/toolbar-card-layout-template.hbs @@ -11,9 +11,15 @@ {{title}} {{#if provider}} - - {{provider.message}} - + {{#neq provider.link "#"}} + + + + {{else}} +
+ +
+ {{/neq}} {{/if}} {{#if duration}} diff --git a/ui/src/templates/toolbar-card-media.hbs b/ui/src/templates/toolbar-card-media.hbs index 8e1699b76c..2d1e483f38 100644 --- a/ui/src/templates/toolbar-card-media.hbs +++ b/ui/src/templates/toolbar-card-media.hbs @@ -32,9 +32,15 @@ {{/eq}} {{#if provider}} - - - + {{#neq provider.link "#"}} + + + + {{else}} +
+ +
+ {{/neq}} {{/if}} {{#if mediaDuration}} diff --git a/views/user-form-preferences.twig b/views/user-form-preferences.twig index da9bc5162e..defbb864da 100644 --- a/views/user-form-preferences.twig +++ b/views/user-form-preferences.twig @@ -52,7 +52,7 @@ {% endif %} {% set title %}{% trans "Force current Library duration?" %}{% endset %} - {% set helpText %}{% trans "Assign all Media items based on their Library duration, and make it sticky so that changes in the library are not pulled into Layouts." %}{% endset %} + {% set helpText %}{% trans "Assign all Media items to Playlists based on their Library duration, and make it sticky so that changes in the library are not pulled into Layouts." %}{% endset %} {{ forms.checkbox("useLibraryDuration", title, currentUser.getOptionValue("useLibraryDuration", 0), helpText) }} {% set title %}{% trans "Auto show thumbnail column?" %}{% endset %}