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}}