From 95ff44c95518c43b9f2d87740a1f56b1172e30a7 Mon Sep 17 00:00:00 2001 From: maurofmferrao Date: Tue, 26 Sep 2023 17:04:19 +0100 Subject: [PATCH] Show error message on Elements with bad configured widget relates to xibosignageltd/xibo-private#499 --- lib/Controller/Layout.php | 4 +- lib/Widget/IcsProvider.php | 2 +- lib/Widget/RssProvider.php | 2 +- ui/src/editor-core/properties-panel.js | 17 ++------ ui/src/editor-core/widget.js | 43 +++++++++++++++++++++ ui/src/layout-editor/viewer.js | 35 +++++++++++++++++ ui/src/style/layout-editor.scss | 15 +++++++ ui/src/templates/viewer-element-content.hbs | 2 +- views/common.twig | 2 +- views/layout-designer-page.twig | 3 +- 10 files changed, 105 insertions(+), 20 deletions(-) diff --git a/lib/Controller/Layout.php b/lib/Controller/Layout.php index 74ca66966a..78e1144678 100644 --- a/lib/Controller/Layout.php +++ b/lib/Controller/Layout.php @@ -1473,7 +1473,7 @@ public function grid(Request $request, Response $response) $module = $this->moduleFactory->getByType($widget->type); } catch (NotFoundException $notFoundException) { // This module isn't available, mark it as invalid. - $widget->isValid = 0; + $widget->isValid = false; $widget->setUnmatchedProperty('moduleName', __('Invalid Module')); $widget->setUnmatchedProperty('name', __('Invalid Module')); $widget->setUnmatchedProperty('tags', []); @@ -1504,7 +1504,7 @@ public function grid(Request $request, Response $response) if (in_array('widget_validity', $embed)) { $status = 0; $layout->assessWidgetStatus($module, $widget, $status); - $widget->isValid = $status; + $widget->isValid = $status === 1; } // apply default transitions to a dynamic parameters on widget object. diff --git a/lib/Widget/IcsProvider.php b/lib/Widget/IcsProvider.php index d7f94a9795..b9ddd6203f 100644 --- a/lib/Widget/IcsProvider.php +++ b/lib/Widget/IcsProvider.php @@ -51,7 +51,7 @@ public function fetchData(DataProviderInterface $dataProvider): WidgetProviderIn // Do we have a feed configured? $uri = $dataProvider->getProperty('uri'); if (empty($uri)) { - throw new InvalidArgumentException('Please enter a the URI to a valid ICS feed.', 'uri'); + throw new InvalidArgumentException('Please enter the URI to a valid ICS feed.', 'uri'); } // Create an ICal helper and pass it the contents of the file. diff --git a/lib/Widget/RssProvider.php b/lib/Widget/RssProvider.php index 760c879103..28d5ced00d 100644 --- a/lib/Widget/RssProvider.php +++ b/lib/Widget/RssProvider.php @@ -51,7 +51,7 @@ public function fetchData(DataProviderInterface $dataProvider): WidgetProviderIn { $uri = $dataProvider->getProperty('uri'); if (empty($uri)) { - throw new InvalidArgumentException(__('Please enter a the URI to a valid RSS feed.'), 'uri'); + throw new InvalidArgumentException(__('Please enter the URI to a valid RSS feed.'), 'uri'); } $picoFeedLoggingEnabled = Environment::isDevMode(); diff --git a/ui/src/editor-core/properties-panel.js b/ui/src/editor-core/properties-panel.js index 4147ed1720..b09766ff23 100644 --- a/ui/src/editor-core/properties-panel.js +++ b/ui/src/editor-core/properties-panel.js @@ -528,21 +528,12 @@ PropertiesPanel.prototype.render = function( // Check if we can use is repeat data dataToRender.repeatDataActive = hasData; - // Check if we need to show the required elements error message - if (target.requiredElements && target.requiredElements.valid == false) { - const dataType = lD.common.getModuleByType(target.subType).dataType; - - // Get element names for the missing elements - const requiredMissingElements = - target.requiredElements.missing.map((el) => { - const elTitle = lD.templateManager.templates[dataType][el].title; - return (elTitle != undefined) ? elTitle : el; - }); + // Check required elements + const errorMessage = target.checkRequiredElements(); + if (errorMessage != '') { dataToRender.showErrorMessage = true; - dataToRender.errorMessage = - propertiesPanelTrans.requiredElementsMessage - .replace('%elements%', requiredMissingElements.join(', ')); + dataToRender.errorMessage = errorMessage; } } diff --git a/ui/src/editor-core/widget.js b/ui/src/editor-core/widget.js index 8097f92e84..83ce445ea0 100644 --- a/ui/src/editor-core/widget.js +++ b/ui/src/editor-core/widget.js @@ -151,6 +151,8 @@ const Widget = function(id, data, regionId = null, layoutObject = null) { this.cachedData = {}; this.forceRecalculateData = false; + this.validateData = {}; + this.validateRequiredElements = function() { const moduleType = this.subType; // Check if element is required @@ -1135,10 +1137,24 @@ Widget.prototype.getData = function() { data: sampleData, meta: data?.meta || {}, }; + + // Save error to widget + self.validateData = { + sampleDataMessage: layoutEditorTrans.showingSampleData, + }; + + // If we have an error, add it to the validate data + if (data.success === false) { + self.validateData.errorMessage = data.message; + } + resolve(self.cachedData); } } } else { + // Valid, so reset messages + self.validateData = {}; + // Run onDataLoad/onParseData Object.keys(modulesList).forEach(function(item) { if (modulesList[item].type === self.subType @@ -1194,6 +1210,33 @@ Widget.prototype.getData = function() { return self.cachedDataPromise; }; +/** + * Update element map for this widget + * @return {string} error message + */ +Widget.prototype.checkRequiredElements = function() { + let errorMessage = ''; + const self = this; + + // Check if we need to show the required elements error message + if (self.requiredElements && self.requiredElements.valid == false) { + const dataType = lD.common.getModuleByType(self.subType).dataType; + + // Get element names for the missing elements + const requiredMissingElements = + self.requiredElements.missing.map((el) => { + const elTitle = lD.templateManager.templates[dataType][el].title; + return (elTitle != undefined) ? elTitle : el; + }); + + errorMessage = + propertiesPanelTrans.requiredElementsMessage + .replace('%elements%', requiredMissingElements.join(', ')); + } + + return errorMessage; +}; + /** * Update element map for this widget * @param {object} [element] diff --git a/ui/src/layout-editor/viewer.js b/ui/src/layout-editor/viewer.js index ee08af1591..8973a2f47b 100644 --- a/ui/src/layout-editor/viewer.js +++ b/ui/src/layout-editor/viewer.js @@ -1600,6 +1600,41 @@ Viewer.prototype.renderElementContent = function( const elData = elementData?.data; const meta = elementData?.meta; + // If parent widget isn't valid, replace error message + if (!$.isEmptyObject(parentWidget.validateData)) { + const $messageContainer = $elementContainer.find('.invalid-parent'); + const errorArray = [$messageContainer.prop('title')]; + + // Required elements message + const requiredElementsErrorMessage = + parentWidget.checkRequiredElements(); + + (requiredElementsErrorMessage) && + errorArray.push( + '

' + + requiredElementsErrorMessage + + '

'); + + // Default error message + (parentWidget.validateData.errorMessage) && + errorArray.push( + '

' + + parentWidget.validateData.errorMessage + + '

'); + + (parentWidget.validateData.sampleDataMessage) && + errorArray.push( + '

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

'); + + // Set title/tooltip + $messageContainer.tooltip('dispose') + .prop('title', '
' + + errorArray.join('') + '
'); + $messageContainer.tooltip(); + } + // Check all data elements and make replacements for (const key in elData) { if (elData.hasOwnProperty(key)) { diff --git a/ui/src/style/layout-editor.scss b/ui/src/style/layout-editor.scss index bacbe4053a..cacb1048b8 100644 --- a/ui/src/style/layout-editor.scss +++ b/ui/src/style/layout-editor.scss @@ -1903,3 +1903,18 @@ body[layout-editor-fs] .moveable-control-box { color: $xibo-color-accent !important; font-size: 1rem !important; } + +/* Custom tooltip */ +.custom-tooltip { + text-align: left; + color: $xibo-color-neutral-0; + line-height: 1.2; + + p { + margin: 0.3rem 0; + } + + .sample-data { + color: $xibo-color-tertiary; + } +} \ No newline at end of file diff --git a/ui/src/templates/viewer-element-content.hbs b/ui/src/templates/viewer-element-content.hbs index 0c51363304..d8d07f85a1 100644 --- a/ui/src/templates/viewer-element-content.hbs +++ b/ui/src/templates/viewer-element-content.hbs @@ -16,7 +16,7 @@ {{/neq}} {{#if invalidParent}} -
+
{{/if}} diff --git a/views/common.twig b/views/common.twig index 2356ad1172..6430cd2b54 100644 --- a/views/common.twig +++ b/views/common.twig @@ -490,7 +490,7 @@ left: "{{ "Left" |trans }}", scale: "{{ "Scale"|trans }}", layer: "{{ "Layer"|trans }}", - invalidWidget: "{{ "This widget needs to be configured before it will be shown"|trans }}", + invalidWidget: "{{ "This widget needs to be configured before it will be shown."|trans }}", requiredElementsMessage: "{{ "This widget needs to have at least one of the following elements: %elements%." |trans }}", dataSlot: "{{ "Data Slot"|trans }}", dataSlotHelpText: "{{ "When there are more than one of the same element for a widget you can set the slot for each element. For example with two of the same element you'd have data slot 1 and data slot 2. If 10 items were returned slot 1 would receive items 1,3,5,7,9 and slot 2 would receive items 2,4,6,8,19."|trans }}", diff --git a/views/layout-designer-page.twig b/views/layout-designer-page.twig index 679a2945dd..77f380864a 100644 --- a/views/layout-designer-page.twig +++ b/views/layout-designer-page.twig @@ -76,7 +76,8 @@ unlockMessage: "{% trans "The current layout will be unlocked to other users. You will also be redirected to the Layouts page" %}", viewModeTitle: "{% trans "View" %}", actions: "{% trans "Actions" %}", - welcomeModalMessage: "{% trans "This is published and cannot be edited. You can checkout for editing below, or continue to view it in a read only mode." %}" + welcomeModalMessage: "{% trans "This is published and cannot be edited. You can checkout for editing below, or continue to view it in a read only mode." %}", + showingSampleData: "{% trans "Showing sample data" %}", }; var viewerTrans = {