Commit 3e798d13aa45a6fd65786bd6c7e20507f1754409

Authored by Igor Kulikov
1 parent 37a71a57

Improve qrcode widget. Add helps for qrcode and markdown widgets

... ... @@ -162,10 +162,10 @@
162 162 "resources": [],
163 163 "templateHtml": "<tb-qrcode-widget \n [ctx]=\"ctx\">\n</tb-qrcode-widget>",
164 164 "templateCss": "#container {\n overflow: auto;\n}\n\n.tbDatasource-container {\n margin: 5px;\n padding: 8px;\n}\n\n.tbDatasource-title {\n font-size: 1.200rem;\n font-weight: 500;\n padding-bottom: 10px;\n}\n\n.tbDatasource-table {\n width: 100%;\n box-shadow: 0 0 10px #ccc;\n border-collapse: collapse;\n white-space: nowrap;\n font-size: 1.000rem;\n color: #757575;\n}\n\n.tbDatasource-table td {\n position: relative;\n border-top: 1px solid rgba(0, 0, 0, 0.12);\n border-bottom: 1px solid rgba(0, 0, 0, 0.12);\n padding: 0px 18px;\n box-sizing: border-box;\n}",
165   - "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.qrCodeWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n dataKeysOptional: true,\n singleEntity: true\n };\n}\n\nself.onDestroy = function() {\n}\n\n",
166   - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"QR Code\",\n \"properties\": {\n \"qrCodeTextPattern\": {\n \"title\": \"QR code text pattern (for ex. '${entityName} | ${keyName} - some text.')\",\n \"type\": \"string\",\n \"default\": \"${entityName}\"\n },\n \"useQrCodeTextFunction\": {\n \"title\": \"Use QR code text function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"qrCodeTextFunction\": {\n \"title\": \"QR code text function: f(data)\",\n \"type\": \"string\",\n \"default\": \"return data['entityName'];\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"qrCodeTextPattern\",\n \"useQrCodeTextFunction\",\n {\n \"key\": \"qrCodeTextFunction\",\n \"type\": \"javascript\"\n }\n ]\n}\n",
  165 + "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.qrCodeWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n dataKeysOptional: true,\n datasourcesOptional: true\n };\n}\n\nself.onDestroy = function() {\n}\n\n",
  166 + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"QR Code\",\n \"properties\": {\n \"qrCodeTextPattern\": {\n \"title\": \"QR code text pattern (for ex. '${entityName} | ${keyName} - some text.')\",\n \"type\": \"string\",\n \"default\": \"${entityName}\"\n },\n \"useQrCodeTextFunction\": {\n \"title\": \"Use QR code text function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"qrCodeTextFunction\": {\n \"title\": \"QR code text function: f(data)\",\n \"type\": \"string\",\n \"default\": \"return data[0]['entityName'];\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"qrCodeTextPattern\",\n \"condition\": \"model.useQrCodeTextFunction !== true\"\n },\n \"useQrCodeTextFunction\",\n {\n \"key\": \"qrCodeTextFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/qrcode/qrcode_text_fn\",\n \"condition\": \"model.useQrCodeTextFunction === true\"\n }\n ]\n}\n",
167 167 "dataKeySettingsSchema": "{}\n",
168   - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"entityAliasId\":null,\"filterId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7036904308224163,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"qrCodeTextPattern\":\"${entityName}\",\"useQrCodeTextFunction\":false,\"qrCodeTextFunction\":\"return data['entityName'];\"},\"title\":\"QR Code\"}"
  168 + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"entityAliasId\":null,\"filterId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7036904308224163,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"qrCodeTextPattern\":\"${entityName}\",\"useQrCodeTextFunction\":false,\"qrCodeTextFunction\":\"return data[0] ? data[0]['entityName'] : '';\"},\"title\":\"QR Code\"}"
169 169 }
170 170 },
171 171 {
... ... @@ -181,9 +181,9 @@
181 181 "templateHtml": "<tb-markdown-widget \n [ctx]=\"ctx\">\n</tb-markdown-widget>",
182 182 "templateCss": "#container tb-markdown-widget {\n height: 100%;\n display: block;\n}\n\n#container tb-markdown-widget .tb-markdown-view {\n height: 100%;\n overflow: auto;\n}\n",
183 183 "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.markdownWidget.onDataUpdated();\n}\n\nself.actionSources = function() {\n return {\n 'elementClick': {\n name: 'widget-action.element-click',\n multiple: true\n }\n };\n}\n\nself.typeParameters = function() {\n return {\n dataKeysOptional: true,\n datasourcesOptional: true\n };\n}\n\nself.onDestroy = function() {\n}\n\n",
184   - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Markdown/HTML card\",\n \"properties\": {\n \"markdownTextPattern\": {\n \"title\": \"Markdown/HTML pattern (markdown or HTML with variables, for ex. '${entityName} or ${keyName} - some text.')\",\n \"type\": \"string\",\n \"default\": \"# Markdown/HTML card \\n - **Current entity**: **${entityName}**. \\n - **Current value**: **${Random}**.\"\n },\n \"markdownCss\": {\n \"title\": \"Markdown/HTML CSS\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useMarkdownTextFunction\": {\n \"title\": \"Use markdown/HTML value function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"markdownTextFunction\": {\n \"title\": \"Markdown/HTML value function: f(data)\",\n \"type\": \"string\",\n \"default\": \"return '# Some title\\\\n - Entity name: ' + data[0]['entityName'];\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"markdownTextPattern\",\n \"type\": \"markdown\"\n },\n {\n \"key\": \"markdownCss\",\n \"type\": \"css\"\n },\n \"useMarkdownTextFunction\",\n {\n \"key\": \"markdownTextFunction\",\n \"type\": \"javascript\"\n }\n ]\n}\n",
  184 + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Markdown/HTML card\",\n \"properties\": {\n \"markdownTextPattern\": {\n \"title\": \"Markdown/HTML pattern (markdown or HTML with variables, for ex. '${entityName} or ${keyName} - some text.')\",\n \"type\": \"string\",\n \"default\": \"# Markdown/HTML card \\n - **Current entity**: **${entityName}**. \\n - **Current value**: **${Random}**.\"\n },\n \"markdownCss\": {\n \"title\": \"Markdown/HTML CSS\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useMarkdownTextFunction\": {\n \"title\": \"Use markdown/HTML value function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"markdownTextFunction\": {\n \"title\": \"Markdown/HTML value function: f(data)\",\n \"type\": \"string\",\n \"default\": \"return '# Some title\\\\n - Entity name: ' + data[0]['entityName'];\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"markdownTextPattern\",\n \"type\": \"markdown\",\n \"condition\": \"model.useMarkdownTextFunction !== true\"\n },\n {\n \"key\": \"markdownCss\",\n \"type\": \"css\"\n },\n \"useMarkdownTextFunction\",\n {\n \"key\": \"markdownTextFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/markdown/markdown_text_fn\",\n \"condition\": \"model.useMarkdownTextFunction === true\"\n }\n ]\n}\n",
185 185 "dataKeySettingsSchema": "{}\n",
186   - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"markdownTextPattern\":\"### Markdown/HTML card\\n - **Current entity**: ${entityName}.\\n - **Current value**: ${Random}.\",\"markdownTextFunction\":\"return '# Some title\\\\n - Entity name: ' + data[0]['entityName'];\"},\"title\":\"Markdown/HTML Card\",\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"showLegend\":false}"
  186 + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"markdownTextPattern\":\"### Markdown/HTML card\\n - **Current entity**: ${entityName}.\\n - **Current value**: ${Random}.\",\"markdownTextFunction\":\"return '# Some title\\\\n - Entity name: ' + data[0]['entityName'];\",\"useMarkdownTextFunction\":false},\"title\":\"Markdown/HTML Card\",\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"showLegend\":false}"
187 187 }
188 188 }
189 189 ]
... ...
... ... @@ -99,15 +99,12 @@ export class MarkdownWidgetComponent extends PageComponent implements OnInit {
99 99 } else {
100 100 initialData = [];
101 101 }
102   - let markdownText: string;
103   - if (initialData) {
104   - const data = parseData(initialData);
105   - markdownText = this.settings.useMarkdownTextFunction ?
106   - safeExecute(this.markdownTextFunction, [data]) : this.settings.markdownTextPattern;
107   - const allData = flatData(data);
108   - const replaceInfo = processPattern(markdownText, allData);
109   - markdownText = fillPattern(markdownText, replaceInfo, allData);
110   - }
  102 + const data = parseData(initialData);
  103 + let markdownText = this.settings.useMarkdownTextFunction ?
  104 + safeExecute(this.markdownTextFunction, [data]) : this.settings.markdownTextPattern;
  105 + const allData = flatData(data);
  106 + const replaceInfo = processPattern(markdownText, allData);
  107 + markdownText = fillPattern(markdownText, replaceInfo, allData);
111 108 if (this.markdownText !== markdownText) {
112 109 this.markdownText = this.utils.customTranslation(markdownText, markdownText);
113 110 this.cd.detectChanges();
... ...
... ... @@ -17,8 +17,6 @@
17 17 -->
18 18 <div fxLayout="column" fxLayoutAlign="center center" style="width: 100%; height: 100%;">
19 19 <canvas fxFlex #canvas [ngStyle]="{display: qrCodeText && !invalidQrCodeText ? 'block' : 'none'}"></canvas>
20   - <div *ngIf="datasourceDetected">
21   - <div *ngIf="!qrCodeText && !invalidQrCodeText" style="text-align: center" translate>entity.no-data</div>
22   - <div *ngIf="invalidQrCodeText" style="text-align: center" translate>widgets.invalid-qr-code-text</div>
23   - </div>
  20 + <div *ngIf="!qrCodeText && !invalidQrCodeText" style="text-align: center" translate>entity.no-data</div>
  21 + <div *ngIf="invalidQrCodeText" style="text-align: center" translate>widgets.invalid-qr-code-text</div>
24 22 </div>
... ...
... ... @@ -20,7 +20,7 @@ import { WidgetContext } from '@home/models/widget-component.models';
20 20 import { Store } from '@ngrx/store';
21 21 import { AppState } from '@core/core.state';
22 22 import {
23   - fillPattern,
  23 + fillPattern, flatData,
24 24 parseData,
25 25 parseFunction,
26 26 processPattern,
... ... @@ -37,7 +37,7 @@ interface QrCodeWidgetSettings {
37 37 qrCodeTextFunction: string;
38 38 }
39 39
40   -type QrCodeTextFunction = (data: FormattedData) => string;
  40 +type QrCodeTextFunction = (data: FormattedData[]) => string;
41 41
42 42 @Component({
43 43 selector: 'tb-qrcode-widget',
... ... @@ -54,7 +54,6 @@ export class QrCodeWidgetComponent extends PageComponent implements OnInit, Afte
54 54
55 55 qrCodeText: string;
56 56 invalidQrCodeText = false;
57   - datasourceDetected = false;
58 57
59 58 private viewInited: boolean;
60 59 private scheduleUpdateCanvas: boolean;
... ... @@ -97,17 +96,14 @@ export class QrCodeWidgetComponent extends PageComponent implements OnInit, Afte
97 96 }
98 97 ];
99 98 } else {
100   - this.datasourceDetected = false;
101   - }
102   - if (initialData) {
103   - const data = parseData(initialData);
104   - const dataSourceData = data[0];
105   - const pattern = this.settings.useQrCodeTextFunction ?
106   - safeExecute(this.qrCodeTextFunction, [dataSourceData]) : this.settings.qrCodeTextPattern;
107   - const replaceInfo = processPattern(pattern, dataSourceData);
108   - qrCodeText = fillPattern(pattern, replaceInfo, dataSourceData);
109   - this.datasourceDetected = true;
  99 + initialData = [];
110 100 }
  101 + const data = parseData(initialData);
  102 + const pattern = this.settings.useQrCodeTextFunction ?
  103 + safeExecute(this.qrCodeTextFunction, [data]) : this.settings.qrCodeTextPattern;
  104 + const allData = flatData(data);
  105 + const replaceInfo = processPattern(pattern, allData);
  106 + qrCodeText = fillPattern(pattern, replaceInfo, allData);
111 107 this.updateQrCodeText(qrCodeText);
112 108 }
113 109
... ...
  1 +#### Markdown text function
  2 +
  3 +<div class="divider"></div>
  4 +<br/>
  5 +
  6 +*function (data): string*
  7 +
  8 +A JavaScript function used to calculate markdown or HTML content.
  9 +
  10 +**Parameters:**
  11 +
  12 +<ul>
  13 + <li><b>data:</b> <code><a href="https://github.com/thingsboard/thingsboard/blob/5bb6403407aa4898084832d6698aa9ea6d484889/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts#L108" target="_blank">FormattedData[]</a></code> - An array of <a href="https://github.com/thingsboard/thingsboard/blob/5bb6403407aa4898084832d6698aa9ea6d484889/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts#L108" target="_blank">FormattedData</a> objects resolved from configured datasources.<br/>
  14 + Each object represents basic entity properties (ex. <code>entityId</code>, <code>entityName</code>)<br/>and provides access to other entity attributes/timeseries declared in widget datasource configuration.
  15 + </li>
  16 +</ul>
  17 +
  18 +**Returns:**
  19 +
  20 +Should return string value presenting markdown or HTML content.
  21 +
  22 +<div class="divider"></div>
  23 +
  24 +##### Examples
  25 +
  26 +* Display markdown with first entity name information:
  27 +
  28 +```javascript
  29 +return '# Some title\n - Entity name: ' + data[0]['entityName'];
  30 +{:copy-code}
  31 +```
  32 +
  33 +<ul>
  34 +<li>
  35 +Display greetings for currently logged-in user.<br>
  36 +Let's assume widget has first datasource configured using <code>Current User</code> <a target="_blank" href="${baseUrl}/docs/user-guide/ui/aliases/#single-entity">Single entity</a> alias<br>
  37 +and has data keys for <code>firstName</code>, <code>lastName</code> and <code>name</code> entity fields:
  38 +</li>
  39 +</ul>
  40 +
  41 +```javascript
  42 +var userEntity = data[0];
  43 +var userName;
  44 +if (userEntity.firstName || userEntity['First name']) {
  45 + userName = userEntity.firstName || userEntity['First name'];
  46 +} else if (userEntity.lastName || userEntity['Last name']) {
  47 + userName = userEntity.lastName || userEntity['Last name'];
  48 +} else if (userEntity.name || userEntity['Name']) {
  49 + userName = userEntity.name || userEntity['Name'];
  50 +}
  51 +
  52 +var welcomeText = 'Hi, ' + userName + '!\n\n';
  53 +return welcomeText;
  54 +{:copy-code}
  55 +```
  56 +
  57 +<br>
  58 +<br>
... ...
  1 +#### QR code text function
  2 +
  3 +<div class="divider"></div>
  4 +<br/>
  5 +
  6 +*function (data): string*
  7 +
  8 +A JavaScript function used to calculate text to be displayed as QR code.
  9 +
  10 +**Parameters:**
  11 +
  12 +<ul>
  13 + <li><b>data:</b> <code><a href="https://github.com/thingsboard/thingsboard/blob/5bb6403407aa4898084832d6698aa9ea6d484889/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts#L108" target="_blank">FormattedData[]</a></code> - An array of <a href="https://github.com/thingsboard/thingsboard/blob/5bb6403407aa4898084832d6698aa9ea6d484889/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts#L108" target="_blank">FormattedData</a> objects resolved from configured datasources.<br/>
  14 + Each object represents basic entity properties (ex. <code>entityId</code>, <code>entityName</code>)<br/>and provides access to other entity attributes/timeseries declared in widget datasource configuration.
  15 + </li>
  16 +</ul>
  17 +
  18 +**Returns:**
  19 +
  20 +Should return string value presenting text to be displayed as QR code.
  21 +
  22 +<div class="divider"></div>
  23 +
  24 +##### Examples
  25 +
  26 +* Prepare QR code text from name of the first entity if present:
  27 +
  28 +```javascript
  29 +return data[0] ? data[0]['entityName'] : '';
  30 +{:copy-code}
  31 +```
  32 +
  33 +<ul>
  34 +<li>
  35 +Prepare QR code text to use as device claiming info (in this case <code>{deviceName: string, secretKey: string}</code>).<br>
  36 +Let's assume device has <code>claimingData</code> attribute with string JSON value containing <code>secretKey</code> field<br>
  37 +(see <a target="_blank" href="${baseUrl}/docs/user-guide/claiming-devices/">Claiming devices</a>):
  38 +</li>
  39 +</ul>
  40 +
  41 +```javascript
  42 +var entityData = data[0];
  43 +if (entityData) {
  44 + return JSON.stringify({
  45 + deviceName: entityData.entityName,
  46 + secretKey: JSON.parse(entityData.claimingData).secretKey
  47 + });
  48 +} else {
  49 + return '';
  50 +}
  51 +{:copy-code}
  52 +```
  53 +
  54 +<br>
  55 +<br>
... ...