From ad6c251488acac264cf990f2a60c0abd494a70c9 Mon Sep 17 00:00:00 2001 From: GermanBluefox Date: Thu, 9 Nov 2023 18:11:05 +0100 Subject: [PATCH 1/3] Implemented signals for React (new) Widgets Corrected https://github.com/ioBroker/ioBroker.vis-2/issues/106 --- README.md | 3 + src/public/lib/js/visEditWords.js | 9 +- src/src/Attributes/Widget/index.jsx | 22 +++- src/src/Vis/css/vis.css | 24 ++-- src/src/Vis/visBaseWidget.jsx | 5 +- src/src/Vis/visRxWidget.jsx | 171 +++++++++++++++++++++++++++- src/src/i18n/de.json | 9 +- src/src/i18n/en.json | 9 +- src/src/i18n/es.json | 9 +- src/src/i18n/fr.json | 9 +- src/src/i18n/it.json | 9 +- src/src/i18n/nl.json | 9 +- src/src/i18n/pl.json | 9 +- src/src/i18n/pt.json | 9 +- src/src/i18n/ru.json | 9 +- src/src/i18n/uk.json | 9 +- src/src/i18n/zh-cn.json | 9 +- 17 files changed, 293 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index e61444afc..3b0a8c68f 100644 --- a/README.md +++ b/README.md @@ -229,6 +229,9 @@ E.g., if it was used in a menu and the menu is red, the circle would be red. ### **WORK IN PROGRESS** --> ## Changelog +### **WORK IN PROGRESS** +* (bluefox) Implemented the signal icons for react widgets + ### 2.4.0 (2023-11-08) * (foxriver76) fixed issues with icon selector filter when changing category * (foxriver76) fixed problem, that only first widget is pasted diff --git a/src/public/lib/js/visEditWords.js b/src/public/lib/js/visEditWords.js index c3f0a771d..bbc1e9509 100644 --- a/src/public/lib/js/visEditWords.js +++ b/src/public/lib/js/visEditWords.js @@ -387,10 +387,9 @@ Object.assign(systemDictionary, { "signals-cond-0": { "en": "Condition [0]", "de": "Bedingung [0]", "ru": "Условие [0]", "zh-cn": "没有权限"}, "signals-val-0": { "en": "Value for condition [0]", "de": "Wert für die Bedingung [0]", "ru": "Значение для условия [0]", "zh-cn": "对象ID [2]"}, "signals-icon-0": { "en": "Icon path [0]", "de": "Bild [0]", "ru": "Картинка [0]", "zh-cn": "闪烁[2]"}, - "signals-text-0": { "en": "Description [0]", "de": "Beschreibung [0]", "ru": "Описание [0]", "zh-cn": "图标路径[2]"}, "signals-horz-0": { "en": "Horizontal[0]", "de": "Horizontale [0]", "ru": "по горизонтали [0]", "zh-cn": "快门"}, "signals-vert-0": { "en": "Vertical [0]", "de": "Vertikale [0]", "ru": "по вертикали [0]", "zh-cn": "说明[2]"}, - "signals-hide-edit-0": { "en": "Hide by edit [0]", "de": "Nicht zeigen beim Editieren [0]", "ru": "Не показывать в редакторе [0]", "zh-cn": "只读"}, + "signals-hide-edit-0": { "en": "Hide by edit [0]", "de": "Nicht bei Editieren zeigen[0]", "ru": "Не показывать в редакторе [0]", "zh-cn": "只读"}, "signals-icon-size-0": { "en": "Icon size in px[0]", "de": "Bildgröße in px [0]", "ru": "Размер картинки в px [0]", "zh-cn": "条件[2]"}, "signals-icon-style-0": { "en": "CSS icon style [0]", "de": "CSS Bildstil [0]", "ru": "CSS для картинки [0]", "zh-cn": "通过编辑隐藏[2]"}, "signals-text-style-0": { "en": "CSS text style [0]", "de": "CSS Textstil [0]", "ru": "CSS для текста[0]", "zh-cn": "CSS图标样式[2]"}, @@ -400,10 +399,9 @@ Object.assign(systemDictionary, { "signals-cond-1": { "en": "Condition [1]", "de": "Bedingung [1]", "ru": "Условие [1]", "zh-cn": "请使用/dashui/edit.html而不是/dashui/?edit"}, "signals-val-1": { "en": "Value for condition [1]", "de": "Wert für die Bedingung [1]", "ru": "Значение для условия [1]", "zh-cn": "说明[0]"}, "signals-icon-1": { "en": "Icon path [1]", "de": "Bild [1]", "ru": "Картинка [1]", "zh-cn": "条件[0]"}, - "signals-text-1": { "en": "Description [1]", "de": "Beschreibung [1]", "ru": "Описание [1]", "zh-cn": "px中的图标大小[0]"}, "signals-horz-1": { "en": "Horizontal[1]", "de": "Horizontale [1]", "ru": "по горизонтали [1]", "zh-cn": "闪烁[0]"}, "signals-vert-1": { "en": "Vertical [1]", "de": "Vertikale [1]", "ru": "по вертикали [1]", "zh-cn": "类[0]"}, - "signals-hide-edit-1": { "en": "Hide by edit [1]", "de": "Nicht zeigen bei Editieren [1]", "ru": "Не показывать в редакторе [1]", "zh-cn": "刷新树/列表"}, + "signals-hide-edit-1": { "en": "Hide by edit [1]", "de": "Nicht bei Editieren zeigen[1]", "ru": "Не показывать в редакторе [1]", "zh-cn": "刷新树/列表"}, "signals-icon-size-1": { "en": "Icon size in px[1]", "de": "Bildgröße in px [1]", "ru": "Размер картинки в px [1]", "zh-cn": "通过编辑隐藏[0]"}, "signals-icon-style-1": { "en": "CSS icon style [1]", "de": "CSS Bildstil [1]", "ru": "CSS для картинки [1]", "zh-cn": "水平[0]"}, "signals-text-style-1": { "en": "CSS text style [1]", "de": "CSS Textstil [1]", "ru": "CSS для текста[1]", "zh-cn": "对象ID [0]"}, @@ -413,10 +411,9 @@ Object.assign(systemDictionary, { "signals-cond-2": { "en": "Condition [2]", "de": "Bedingung [2]", "ru": "Условие [2]", "zh-cn": "项目"}, "signals-val-2": { "en": "Value for condition [2]", "de": "Wert für die Bedingung [2]", "ru": "Значение для условия [2]", "zh-cn": "说明[1]"}, "signals-icon-2": { "en": "Icon path [2]", "de": "Bild [2]", "ru": "Картинка [2]", "zh-cn": "条件[1]"}, - "signals-text-2": { "en": "Description [2]", "de": "Beschreibung [2]", "ru": "Описание [2]", "zh-cn": "px中的图标大小[1]"}, "signals-horz-2": { "en": "Horizontal[2]", "de": "Horizontale [2]", "ru": "по горизонтали [2]", "zh-cn": "闪烁[1]"}, "signals-vert-2": { "en": "Vertical [2]", "de": "Vertikale [2]", "ru": "по вертикали [2]", "zh-cn": "课程[1]"}, - "signals-hide-edit-2": { "en": "Hide by edit [2]", "de": "Nicht zeigen beim Editieren [2]", "ru": "Не показывать в редакторе [2]", "zh-cn": "改名"}, + "signals-hide-edit-2": { "en": "Hide by edit [2]", "de": "Nicht bei Editieren zeigen[2]", "ru": "Не показывать в редакторе [2]", "zh-cn": "改名"}, "signals-icon-size-2": { "en": "Icon size in px[2]", "de": "Bildgröße in px [2]", "ru": "Размер картинки в px [2]", "zh-cn": "通过编辑隐藏[1]"}, "signals-icon-style-2": { "en": "CSS icon style [2]", "de": "CSS Bildstil [2]", "ru": "CSS для картинки [2]", "zh-cn": "水平[1]"}, "signals-text-style-2": { "en": "CSS text style [2]", "de": "CSS Textstil [2]", "ru": "CSS для текста[2]", "zh-cn": "对象ID [1]"}, diff --git a/src/src/Attributes/Widget/index.jsx b/src/src/Attributes/Widget/index.jsx index eebe470b3..3e6baeac7 100644 --- a/src/src/Attributes/Widget/index.jsx +++ b/src/src/Attributes/Widget/index.jsx @@ -288,16 +288,17 @@ class Widget extends Component { ]; } - static getSignals(count, adapterName) { + static getSignals(count /* , adapterName */) { const result = { name: 'signals', fields: [ { name: 'signals-count', + label: 'signals-count', type: 'select', - noTranslation: true, - options: ['1', '2', '3'], - default: '1', + // noTranslation: true, + options: ['0', '1', '2', '3'], + default: '0', immediateChange: true, }, ], @@ -305,15 +306,25 @@ class Widget extends Component { for (let i = 0; i < count; i++) { result.fields = result.fields.concat([ + { type: 'delimiter' }, { name: `signals-oid-${i}`, type: 'id' }, { name: `signals-cond-${i}`, type: 'select', + noTranslation: true, options: ['==', '!=', '<=', '>=', '<', '>', 'consist', 'not consist', 'exist', 'not exist'], default: '==', }, { name: `signals-val-${i}`, default: true }, - { name: `signals-icon-${i}`, type: 'image', default: `${adapterName}/signals/lowbattery.png` }, + { + name: `signals-icon-${i}`, type: 'image', default: '', hidden: `!!data["signals-smallIcon-${i}"]`, // `${adapterName}/signals/lowbattery.png` }, + }, + { + name: `signals-smallIcon-${i}`, type: 'icon64', default: '', label: `signals-smallIcon-${i}`, hidden: `!!data["signals-icon-${i}"]`, + }, + { + name: `signals-color-${i}`, type: 'color', default: '', hidden: `!data["signals-smallIcon-${i}"] && !data["signals-text-${i}"]`, + }, // `${adapterName}/signals/lowbattery.png` }, { name: `signals-icon-size-${i}`, type: 'slider', options: { min: 1, max: 120, step: 1 }, default: 0, }, @@ -329,7 +340,6 @@ class Widget extends Component { name: `signals-vert-${i}`, type: 'slider', options: { min: -20, max: 120, step: 1 }, default: 0, }, { name: `signals-hide-edit-${i}`, type: 'checkbox', default: false }, - { type: 'delimiter' }, ]); } diff --git a/src/src/Vis/css/vis.css b/src/src/Vis/css/vis.css index 44e58297b..f81672f5e 100644 --- a/src/src/Vis/css/vis.css +++ b/src/src/Vis/css/vis.css @@ -307,6 +307,15 @@ body, html { font-size: 11px; } +.vis-signals-blink { + animation: vis-blink-animation 1s steps(5, start) infinite; +} +@keyframes vis-blink-animation { + to { + visibility: hidden; + } +} + /* IT IS UNKNOWN ARE BELOW CLASSES USED SOMEWHERE */ /* .vis-no-user-select { @@ -365,21 +374,6 @@ body, html { height: 100%; } -.vis-signals-blink { - animation: vis-blink-animation 1s steps(5, start) infinite; - -webkit-animation: vis-blink-animation 1s steps(5, start) infinite; -} -@keyframes vis-blink-animation { - to { - visibility: hidden; - } -} -@-webkit-keyframes vis-blink-animation { - to { - visibility: hidden; - } -} - @-webkit-keyframes vis-waitico-rotate { from { -webkit-transform: rotate(0deg); diff --git a/src/src/Vis/visBaseWidget.jsx b/src/src/Vis/visBaseWidget.jsx index ec9e1818b..547a73b02 100644 --- a/src/src/Vis/visBaseWidget.jsx +++ b/src/src/Vis/visBaseWidget.jsx @@ -1080,7 +1080,7 @@ class VisBaseWidget extends React.Component { }} >
{I18n.t('Unknown widget type "%s"', this.props.tpl)}
-
{ JSON.stringify(this.state.data, null, 2) }
+
{JSON.stringify(this.state.data, null, 2)}
; } @@ -1850,12 +1850,15 @@ class VisBaseWidget extends React.Component { ; } + const signals = this.renderSignals ? this.renderSignals() : null; + return
+ {signals} {widgetName} {widgetMoveButtons} {overlay} diff --git a/src/src/Vis/visRxWidget.jsx b/src/src/Vis/visRxWidget.jsx index 2e87e0399..c1b3ecba4 100644 --- a/src/src/Vis/visRxWidget.jsx +++ b/src/src/Vis/visRxWidget.jsx @@ -20,7 +20,7 @@ import { CardContent, } from '@mui/material'; -import { I18n } from '@iobroker/adapter-react-v5'; +import { I18n, Icon } from '@iobroker/adapter-react-v5'; // eslint-disable-next-line import/no-cycle import VisBaseWidget from './visBaseWidget'; @@ -550,6 +550,175 @@ class VisRxWidget extends VisBaseWidget { }); } + isSignalVisible(index, val) { + const oid = this.state.rxData[`signals-oid-${index}`]; + + if (oid) { + if (val === undefined || val === null) { + val = this.state.values[`${oid}.val`]; + } + + const condition = this.state.rxData[`signals-cond-${index}`]; + let value = this.state.rxData[`signals-val-${index}`]; + + if (val === undefined || val === null) { + return condition === 'not exist'; + } + + if (!condition || value === undefined || value === null) { + return condition === 'not exist'; + } + + if (val === 'null' && condition !== 'exist' && condition !== 'not exist') { + return false; + } + + const t = typeof val; + if (t === 'boolean' || val === 'false' || val === 'true') { + value = value === 'true' || value === true || value === 1 || value === '1'; + } else if (t === 'number') { + value = parseFloat(value); + } else if (t === 'object') { + val = JSON.stringify(val); + } + + switch (condition) { + case '==': + value = value.toString(); + val = val.toString(); + if (val === '1') val = 'true'; + if (value === '1') value = 'true'; + if (val === '0') val = 'false'; + if (value === '0') value = 'false'; + return value === val; + case '!=': + value = value.toString(); + val = val.toString(); + if (val === '1') val = 'true'; + if (value === '1') value = 'true'; + if (val === '0') val = 'false'; + if (value === '0') value = 'false'; + return value !== val; + case '>=': + return val >= value; + case '<=': + return val <= value; + case '>': + return val > value; + case '<': + return val < value; + case 'consist': + value = value.toString(); + val = val.toString(); + return val.toString().includes(value); + case 'not consist': + value = value.toString(); + val = val.toString(); + return !val.toString().includes(value); + case 'exist': + return value !== 'null'; + case 'not exist': + return value === 'null'; + default: + console.log(`Unknown signals condition for ${this.props.id}: ${condition}`); + return false; + } + } else { + return false; + } + } + + static text2style(textStyle, style) { + if (textStyle) { + style = style || {}; + const parts = textStyle.split(';'); + parts.forEach(part => { + const [attr, value] = part.split(':'); + if (attr && value) { + // convert attr into camelCase notation + const attrParts = attr.trim().split('-'); + for (let p = 1; p < attrParts.length; p++) { + attrParts[p] = attrParts[p][0].toUpperCase() + attrParts[p].substring(1); + } + + style[attrParts.join('')] = value.trim(); + } + }); + } + return style; + } + + renderSignal(index) { + const oid = this.state.rxData[`signals-oid-${index}`]; + if (!oid) { + return null; + } + if (this.props.editMode && this.state.rxData[`signals-hide-edit-${index}`]) { + return null; + } + + // check value + if (this.props.editMode || this.isSignalVisible(index)) { + let icon = this.state.rxData[`signals-smallIcon-${index}`]; + let color; + if (icon) { + color = this.state.rxData[`signals-color-${index}`]; + } else { + icon = this.state.rxData[`signals-icon-${index}`]; + } + const style = { + color, + position: 'absolute', + top: `${parseInt(this.state.rxData[`signals-vert-${index}`], 10) || 0}%`, + left: `${parseInt(this.state.rxData[`signals-horz-${index}`], 10) || 0}%`, + zIndex: 10, + textAlign: 'center', + }; + if (icon) { + const imageStyle = { + width: parseFloat(this.state.rxData[`signals-icon-size-${index}`]) || 32, + height: 'auto', + }; + icon = ; + } + VisRxWidget.text2style(this.state.rxData[`signals-icon-style-${index}`], style); + let text = this.state.rxData[`signals-text-${index}`]; + if (text) { + const textStyle = { + color: this.state.rxData[`signals-color-${index}`], + }; + VisRxWidget.text2style(this.state.rxData[`signals-text-style-${index}`], textStyle); + text =
+ {this.state.rxData[`signals-text-${index}`]} +
; + } else { + text = null; + } + + // class name only to address the icon by user's CSS + return
+ {icon} + {text} +
; + } + return null; + } + + renderSignals() { + const count = parseInt(this.state.rxData?.['signals-count'], 10) || 0; + if (!count) { + return null; + } + const result = []; + for (let i = 0; i < count; i++) { + result.push(this.renderSignal(i)); + } + return result; + } + render() { if (!this.state.visible && !this.state.editMode) { return null; diff --git a/src/src/i18n/de.json b/src/src/i18n/de.json index 1dbc4c289..d2f4f5e55 100644 --- a/src/src/i18n/de.json +++ b/src/src/i18n/de.json @@ -595,5 +595,12 @@ "Use field as binding": "Feld als Bindung verwenden", "Deactivate binding and use field as standard input": "Bindung deaktivieren und Feld als Standardeingabe verwenden", "Edit binding": "Bindung bearbeiten", - "Hide menu": "Menü ausblenden" + "Hide menu": "Menü ausblenden", + "signals-count": "Anzahl der Signale", + "signals-smallIcon-1": "Kleines Symbol [1]", + "signals-smallIcon-2": "Kleines Symbol [3]", + "signals-smallIcon-0": "Kleines Symbol [0]", + "signals-text-0": "Text [0]", + "signals-text-1": "Text [1]", + "signals-text-2": "Text [2]" } \ No newline at end of file diff --git a/src/src/i18n/en.json b/src/src/i18n/en.json index e975f887f..358a8267c 100644 --- a/src/src/i18n/en.json +++ b/src/src/i18n/en.json @@ -595,5 +595,12 @@ "Use field as binding": "Use field as binding", "Deactivate binding and use field as standard input": "Deactivate binding and use field as standard input", "Edit binding": "Edit binding", - "Hide menu": "Hide menu" + "Hide menu": "Hide menu", + "signals-count": "Number of signals", + "signals-smallIcon-1": "Small Icon [1]", + "signals-smallIcon-2": "Small icon [3]", + "signals-smallIcon-0": "Small icon [0]", + "signals-text-0": "Text [0]", + "signals-text-1": "Text [1]", + "signals-text-2": "Text [2]" } \ No newline at end of file diff --git a/src/src/i18n/es.json b/src/src/i18n/es.json index 93563efd6..94c6f600c 100644 --- a/src/src/i18n/es.json +++ b/src/src/i18n/es.json @@ -595,5 +595,12 @@ "Use field as binding": "Usar campo como enlace", "Deactivate binding and use field as standard input": "Desactivar el enlace y utilizar el campo como entrada estándar", "Edit binding": "Editar enlace", - "Hide menu": "Ocultar el menú" + "Hide menu": "Ocultar el menú", + "signals-count": "Número de señales", + "signals-smallIcon-1": "Icono pequeño [1]", + "signals-smallIcon-2": "Icono pequeño [3]", + "signals-smallIcon-0": "Icono pequeño [0]", + "signals-text-0": "Texto [0]", + "signals-text-1": "Texto [1]", + "signals-text-2": "Texto [2]" } \ No newline at end of file diff --git a/src/src/i18n/fr.json b/src/src/i18n/fr.json index 2bee6b5c1..da652e192 100644 --- a/src/src/i18n/fr.json +++ b/src/src/i18n/fr.json @@ -595,5 +595,12 @@ "Use field as binding": "Utiliser le champ comme liaison", "Deactivate binding and use field as standard input": "Désactiver la liaison et utiliser le champ comme entrée standard", "Edit binding": "Modifier la liaison", - "Hide menu": "Masquer le menu" + "Hide menu": "Masquer le menu", + "signals-count": "Nombre de signaux", + "signals-smallIcon-1": "Petite icône [1]", + "signals-smallIcon-2": "Petite icône [3]", + "signals-smallIcon-0": "Petite icône [0]", + "signals-text-0": "Texte [0]", + "signals-text-1": "Texte [1]", + "signals-text-2": "Texte [2]" } \ No newline at end of file diff --git a/src/src/i18n/it.json b/src/src/i18n/it.json index 76e23f447..24c7087ba 100644 --- a/src/src/i18n/it.json +++ b/src/src/i18n/it.json @@ -595,5 +595,12 @@ "Use field as binding": "Utilizza il campo come vincolante", "Deactivate binding and use field as standard input": "Disattiva l'associazione e utilizza il campo come input standard", "Edit binding": "Modifica rilegatura", - "Hide menu": "Nascondi menù" + "Hide menu": "Nascondi menù", + "signals-count": "Numero di segnali", + "signals-smallIcon-1": "Icona piccola [1]", + "signals-smallIcon-2": "Icona piccola [3]", + "signals-smallIcon-0": "Icona piccola [0]", + "signals-text-0": "Testo [0]", + "signals-text-1": "Testo [1]", + "signals-text-2": "Testo [2]" } \ No newline at end of file diff --git a/src/src/i18n/nl.json b/src/src/i18n/nl.json index 14be7a791..c5fbb4585 100644 --- a/src/src/i18n/nl.json +++ b/src/src/i18n/nl.json @@ -595,5 +595,12 @@ "Use field as binding": "Gebruik veld als bindend", "Deactivate binding and use field as standard input": "Deactiveer binding en gebruik veld als standaardinvoer", "Edit binding": "Binding bewerken", - "Hide menu": "Menu verbergen" + "Hide menu": "Menu verbergen", + "signals-count": "Aantal signalen", + "signals-smallIcon-1": "Klein icoontje [1]", + "signals-smallIcon-2": "Klein icoontje [3]", + "signals-smallIcon-0": "Klein pictogram [0]", + "signals-text-0": "Tekst [0]", + "signals-text-1": "Tekst [1]", + "signals-text-2": "Tekst [2]" } \ No newline at end of file diff --git a/src/src/i18n/pl.json b/src/src/i18n/pl.json index 1138f0b49..1d6c31328 100644 --- a/src/src/i18n/pl.json +++ b/src/src/i18n/pl.json @@ -595,5 +595,12 @@ "Use field as binding": "Użyj pola jako wiązania", "Deactivate binding and use field as standard input": "Dezaktywuj powiązanie i użyj pola jako standardowego wejścia", "Edit binding": "Edytuj wiązanie", - "Hide menu": "Ukryj menu" + "Hide menu": "Ukryj menu", + "signals-count": "Liczba sygnałów", + "signals-smallIcon-1": "Mała ikona [1]", + "signals-smallIcon-2": "Mała ikona [3]", + "signals-smallIcon-0": "Mała ikona [0]", + "signals-text-0": "Tekst [0]", + "signals-text-1": "Tekst [1]", + "signals-text-2": "Tekst [2]" } \ No newline at end of file diff --git a/src/src/i18n/pt.json b/src/src/i18n/pt.json index 24ea6c876..15c2b100a 100644 --- a/src/src/i18n/pt.json +++ b/src/src/i18n/pt.json @@ -595,5 +595,12 @@ "Use field as binding": "Usar campo como vinculação", "Deactivate binding and use field as standard input": "Desative a vinculação e use o campo como entrada padrão", "Edit binding": "Editar vinculação", - "Hide menu": "Ocultar menu" + "Hide menu": "Ocultar menu", + "signals-count": "Número de sinais", + "signals-smallIcon-1": "Ícone pequeno [1]", + "signals-smallIcon-2": "Ícone pequeno [3]", + "signals-smallIcon-0": "Ícone pequeno [0]", + "signals-text-0": "Texto [0]", + "signals-text-1": "Texto [1]", + "signals-text-2": "Texto [2]" } \ No newline at end of file diff --git a/src/src/i18n/ru.json b/src/src/i18n/ru.json index 458ad099d..2abd29aee 100644 --- a/src/src/i18n/ru.json +++ b/src/src/i18n/ru.json @@ -595,5 +595,12 @@ "Use field as binding": "Использовать поле как привязку", "Deactivate binding and use field as standard input": "Деактивировать привязку и использовать поле в качестве стандартного ввода", "Edit binding": "Изменить привязку", - "Hide menu": "Скрыть меню" + "Hide menu": "Скрыть меню", + "signals-count": "Количество сигналов", + "signals-smallIcon-1": "Маленький значок [1]", + "signals-smallIcon-2": "Маленький значок [3]", + "signals-smallIcon-0": "Маленький значок [0]", + "signals-text-0": "Текст [0]", + "signals-text-1": "Текст [1]", + "signals-text-2": "Текст [2]" } \ No newline at end of file diff --git a/src/src/i18n/uk.json b/src/src/i18n/uk.json index 6f794ac0b..1c05a9457 100644 --- a/src/src/i18n/uk.json +++ b/src/src/i18n/uk.json @@ -595,5 +595,12 @@ "Use field as binding": "Використовувати поле як прив’язку", "Deactivate binding and use field as standard input": "Дезактивувати прив’язку та використовувати поле як стандартний вхід", "Edit binding": "Редагувати прив'язку", - "Hide menu": "Приховати меню" + "Hide menu": "Приховати меню", + "signals-count": "Кількість сигналів", + "signals-smallIcon-1": "Маленький значок [1]", + "signals-smallIcon-2": "Маленький значок [3]", + "signals-smallIcon-0": "Маленький значок [0]", + "signals-text-0": "Текст [0]", + "signals-text-1": "Текст [1]", + "signals-text-2": "Текст [2]" } \ No newline at end of file diff --git a/src/src/i18n/zh-cn.json b/src/src/i18n/zh-cn.json index 3f484d5f1..d2af6b564 100644 --- a/src/src/i18n/zh-cn.json +++ b/src/src/i18n/zh-cn.json @@ -595,5 +595,12 @@ "Use field as binding": "使用字段作为绑定", "Deactivate binding and use field as standard input": "停用绑定并使用字段作为标准输入", "Edit binding": "编辑绑定", - "Hide menu": "隐藏菜单" + "Hide menu": "隐藏菜单", + "signals-count": "信号数量", + "signals-smallIcon-1": "小图标 [1]", + "signals-smallIcon-2": "小图标 [3]", + "signals-smallIcon-0": "小图标 [0]", + "signals-text-0": "文字 [0]", + "signals-text-1": "文本[1]", + "signals-text-2": "文字[2]" } \ No newline at end of file From 9b5b00dfff25c4468064be58cd0bedf7861a735f Mon Sep 17 00:00:00 2001 From: GermanBluefox Date: Thu, 9 Nov 2023 18:12:05 +0100 Subject: [PATCH 2/3] README.md typos --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3b0a8c68f..462c66844 100644 --- a/README.md +++ b/README.md @@ -230,13 +230,13 @@ E.g., if it was used in a menu and the menu is red, the circle would be red. --> ## Changelog ### **WORK IN PROGRESS** -* (bluefox) Implemented the signal icons for react widgets +* (bluefox) Implemented the signal icons for React widgets ### 2.4.0 (2023-11-08) * (foxriver76) fixed issues with icon selector filter when changing category -* (foxriver76) fixed problem, that only first widget is pasted +* (foxriver76) fixed problem, that only the first widget is pasted * (bluefox) added JSON binding operator -* (bluefox) Allowed to use function as filter for Object ID +* (bluefox) Allowed using function as filter for Object ID * (bluefox) Implemented View bar (with no menu) ### 2.3.6 (2023-11-06) From 7d2122db3285e6ccec31e5a0b8fed9028d767737 Mon Sep 17 00:00:00 2001 From: GermanBluefox Date: Thu, 9 Nov 2023 19:05:02 +0100 Subject: [PATCH 3/3] Implemented lastChange indication for React widgets --- README.md | 1 + src/src/Attributes/Widget/WidgetField.jsx | 5 +- src/src/Attributes/Widget/index.jsx | 74 ++++++++--------- src/src/Vis/visBaseWidget.jsx | 45 ++++++----- src/src/Vis/visCanWidget.jsx | 1 + src/src/Vis/visEngine.jsx | 20 ++++- src/src/Vis/visFormatUtils.jsx | 10 +-- src/src/Vis/visRxWidget.jsx | 96 +++++++++++++++++++++++ 8 files changed, 186 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 462c66844..738a84c1d 100644 --- a/README.md +++ b/README.md @@ -231,6 +231,7 @@ E.g., if it was used in a menu and the menu is red, the circle would be red. ## Changelog ### **WORK IN PROGRESS** * (bluefox) Implemented the signal icons for React widgets +* (bluefox) Implemented the last change indication for React widgets ### 2.4.0 (2023-11-08) * (foxriver76) fixed issues with icon selector filter when changing category diff --git a/src/src/Attributes/Widget/WidgetField.jsx b/src/src/Attributes/Widget/WidgetField.jsx index 53ecee2e2..5c4e2fbee 100644 --- a/src/src/Attributes/Widget/WidgetField.jsx +++ b/src/src/Attributes/Widget/WidgetField.jsx @@ -1035,9 +1035,8 @@ const WidgetField = props => { onInputChange={(e, inputValue) => change(inputValue)} onChange={(e, inputValue) => change(inputValue)} classes={{ input: Utils.clsx(props.classes.clearPadding, props.classes.fieldContent) }} - renderOption={field.name === 'font-family' ? - (optionProps, option) => ( -
{option}
) : null} + renderOption={field.name === 'font-family' || field.type === 'lc-font-family' ? + (optionProps, option) =>
{option}
: null} renderInput={params => widgets[wid].tpl === 'tplValueGesture'), - }, - { name: 'gestures-offsetX', default: 0, type: 'number' }, - { name: 'gestures-offsetY', default: 0, type: 'number' }, - { type: 'delimiter' }, - ...(['swiping', 'rotating', 'pinching'].flatMap(gesture => [ - { name: `gestures-${gesture}-oid`, type: 'id' }, - { name: `gestures-${gesture}-value`, default: '' }, - { name: `gestures-${gesture}-minimum`, type: 'number' }, - { name: `gestures-${gesture}-maximum`, type: 'number' }, - { name: `gestures-${gesture}-delta`, type: 'number' }, - { type: 'delimiter' }, - ])), - ...(['swipeRight', 'swipeLeft', 'swipeUp', 'swipeDown', 'rotateLeft', 'rotateRight', 'pinchIn', 'pinchOut'].flatMap(gesture => [ - { name: `gestures-${gesture}-oid`, type: 'id' }, - { name: `gestures-${gesture}-value`, default: '' }, - { name: `gestures-${gesture}-limit`, type: 'number' }, - { type: 'delimiter' }, - ])), - ], - }, + // { + // name: 'gestures', + // fields: [ + // { + // name: 'gestures-indicator', + // type: 'auto', + // options: Object.keys(widgets).filter(wid => widgets[wid].tpl === 'tplValueGesture'), + // }, + // { name: 'gestures-offsetX', default: 0, type: 'number' }, + // { name: 'gestures-offsetY', default: 0, type: 'number' }, + // { type: 'delimiter' }, + // ...(['swiping', 'rotating', 'pinching'].flatMap(gesture => [ + // { name: `gestures-${gesture}-oid`, type: 'id' }, + // { name: `gestures-${gesture}-value`, default: '' }, + // { name: `gestures-${gesture}-minimum`, type: 'number' }, + // { name: `gestures-${gesture}-maximum`, type: 'number' }, + // { name: `gestures-${gesture}-delta`, type: 'number' }, + // { type: 'delimiter' }, + // ])), + // ...(['swipeRight', 'swipeLeft', 'swipeUp', 'swipeDown', 'rotateLeft', 'rotateRight', 'pinchIn', 'pinchOut'].flatMap(gesture => [ + // { name: `gestures-${gesture}-oid`, type: 'id' }, + // { name: `gestures-${gesture}-value`, default: '' }, + // { name: `gestures-${gesture}-limit`, type: 'number' }, + // { type: 'delimiter' }, + // ])), + // ], + // }, { name: 'last_change', fields: [ @@ -473,15 +473,17 @@ class Widget extends Component { name: 'lc-position-horz', type: 'select', options: ['left', /* 'middle', */'right'], default: 'right', }, { - name: 'lc-offset-vert', type: 'slider', options: { min: -120, max: 120, step: 1 }, default: 0, + name: 'lc-offset-vert', type: 'slider', min: -120, max: 120, step: 1, default: 0, }, { - name: 'lc-offset-horz', type: 'slider', options: { min: -120, max: 120, step: 1 }, default: 0, + name: 'lc-offset-horz', type: 'slider', min: -120, max: 120, step: 1, default: 0, }, { name: 'lc-font-size', type: 'auto', options: ['', 'medium', 'xx-small', 'x-small', 'small', 'large', 'x-large', 'xx-large', 'smaller', 'larger', 'initial', 'inherit'], default: '12px', }, - { name: 'lc-font-family', type: 'fontname', default: '' }, + { + name: 'lc-font-family', type: 'auto', default: '', options: fonts, + }, { name: 'lc-font-style', type: 'auto', options: ['', 'normal', 'italic', 'oblique', 'initial', 'inherit'], default: '', }, @@ -493,11 +495,11 @@ class Widget extends Component { }, { name: 'lc-border-color', type: 'color', default: '' }, { - name: 'lc-border-radius', type: 'slider', options: { min: 0, max: 20, step: 1 }, default: 10, + name: 'lc-border-radius', type: 'slider', min: 0, max: 20, step: 1, default: 10, }, - { name: 'lc-padding' }, + { name: 'lc-padding', type: 'slider', min: 0, max: 20, step: 1, default: 3 }, { - name: 'lc-zindex', type: 'slider', options: { min: -10, max: 20, step: 1 }, default: 0, + name: 'lc-zindex', type: 'slider', min: -10, max: 20, step: 1, default: 0, }, ], }, diff --git a/src/src/Vis/visBaseWidget.jsx b/src/src/Vis/visBaseWidget.jsx index 547a73b02..b6af272fd 100644 --- a/src/src/Vis/visBaseWidget.jsx +++ b/src/src/Vis/visBaseWidget.jsx @@ -15,7 +15,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import moment from 'moment'; import { Anchor as AnchorIcon, @@ -1214,7 +1213,8 @@ class VisBaseWidget extends React.Component { formatInterval(timestamp, isMomentJs) { if (isMomentJs) { - return moment(new Date(timestamp)).fromNow(); + // init moment + return this.props.context.moment(new Date(timestamp)).fromNow(); } let diff = Date.now() - timestamp; diff = Math.round(diff / 1000); @@ -1322,11 +1322,8 @@ class VisBaseWidget extends React.Component { } startUpdateInterval() { - if (this.updateInterval) { - return; - } this.updateInterval = this.updateInterval || setInterval(() => { - const timeIntervalEl = this.widDiv.querySelector('.time-interval'); + const timeIntervalEl = (this.widDiv || this.refService.current)?.querySelector('.time-interval'); if (timeIntervalEl) { const time = parseInt(timeIntervalEl.dataset.time, 10); timeIntervalEl.innerHTML = this.formatInterval(time, timeIntervalEl.dataset.moment === 'true'); @@ -1335,7 +1332,7 @@ class VisBaseWidget extends React.Component { } // eslint-disable-next-line react/no-unused-class-component-methods - formatDate(dateObj, format, interval, isMomentJs) { + formatDate(dateObj, format, interval, isMomentJs, forRx) { if (typeof format === 'boolean') { interval = format; format = 'auto'; @@ -1369,7 +1366,13 @@ class VisBaseWidget extends React.Component { } if (interval) { this.startUpdateInterval(); - return `${this.formatInterval(dateObj.getTime(), isMomentJs)}`; + if (forRx) { + return + {this.formatInterval(dateObj.getTime(), isMomentJs)} + ; + } else { + return `${this.formatInterval(dateObj.getTime(), isMomentJs)}`; + } } let v; @@ -1388,21 +1391,21 @@ class VisBaseWidget extends React.Component { } // Month if (format.includes('MM') || format.includes('ММ')) { - v = dateObj.getMonth() + 1; + v = dateObj.getMonth() + 1; if (v < 10) { v = `0${v}`; } format = format.replace('MM', v); format = format.replace('ММ', v); } else if (format.includes('M') || format.includes('М')) { - v = dateObj.getMonth() + 1; + v = dateObj.getMonth() + 1; format = format.replace('M', v); format = format.replace('М', v); } // Day if (format.includes('DD') || format.includes('TT') || format.includes('ДД')) { - v = dateObj.getDate(); + v = dateObj.getDate(); if (v < 10) { v = `0${v}`; } @@ -1410,7 +1413,7 @@ class VisBaseWidget extends React.Component { format = format.replace('TT', v); format = format.replace('ДД', v); } else if (format.includes('D') || format.includes('TT') || format.includes('Д')) { - v = dateObj.getDate(); + v = dateObj.getDate(); format = format.replace('D', v); format = format.replace('T', v); format = format.replace('Д', v); @@ -1418,7 +1421,7 @@ class VisBaseWidget extends React.Component { // hours if (format.includes('hh') || format.includes('SS') || format.includes('чч')) { - v = dateObj.getHours(); + v = dateObj.getHours(); if (v < 10) { v = `0${v}`; } @@ -1426,7 +1429,7 @@ class VisBaseWidget extends React.Component { format = format.replace('SS', v); format = format.replace('чч', v); } else if (format.includes('h') || format.includes('S') || format.includes('ч')) { - v = dateObj.getHours(); + v = dateObj.getHours(); format = format.replace('h', v); format = format.replace('S', v); format = format.replace('ч', v); @@ -1434,21 +1437,21 @@ class VisBaseWidget extends React.Component { // minutes if (format.includes('mm') || format.includes('мм')) { - v = dateObj.getMinutes(); + v = dateObj.getMinutes(); if (v < 10) { v = `0${v}`; } format = format.replace('mm', v); format = format.replace('мм', v); } else if (format.includes('m') || format.includes('м')) { - v = dateObj.getMinutes(); + v = dateObj.getMinutes(); format = format.replace('m', v); format = format.replace('v', v); } // milliseconds if (format.includes('sss') || format.includes('ссс')) { - v = dateObj.getMilliseconds(); + v = dateObj.getMilliseconds(); if (v < 10) { v = `00${v}`; } else if (v < 100) { @@ -1461,11 +1464,13 @@ class VisBaseWidget extends React.Component { // seconds if (format.includes('ss') || format.includes('сс')) { v = dateObj.getSeconds(); - if (v < 10) v = `0${v}`; + if (v < 10) { + v = `0${v}`; + } format = format.replace('ss', v); format = format.replace('cc', v); } else if (format.includes('s') || format.includes('с')) { - v = dateObj.getHours(); + v = dateObj.getHours(); format = format.replace('s', v); format = format.replace('с', v); } @@ -1851,6 +1856,7 @@ class VisBaseWidget extends React.Component { } const signals = this.renderSignals ? this.renderSignals() : null; + const lastChange = this.renderLastChange ? this.renderLastChange(style) : null; return
{signals} + {lastChange} {widgetName} {widgetMoveButtons} {overlay} diff --git a/src/src/Vis/visCanWidget.jsx b/src/src/Vis/visCanWidget.jsx index 0c32062c0..3199cd684 100644 --- a/src/src/Vis/visCanWidget.jsx +++ b/src/src/Vis/visCanWidget.jsx @@ -931,6 +931,7 @@ class VisCanWidget extends VisBaseWidget { this.props.id, widget, widgetData || widgetContext.data, + this.props.context.moment, ); if (item.type === 'data') { diff --git a/src/src/Vis/visEngine.jsx b/src/src/Vis/visEngine.jsx index 99ee04b88..93a9f391b 100644 --- a/src/src/Vis/visEngine.jsx +++ b/src/src/Vis/visEngine.jsx @@ -15,6 +15,18 @@ import React from 'react'; import PropTypes from 'prop-types'; +import moment from 'moment'; +import 'moment/locale/de'; +import 'moment/locale/ru'; +import 'moment/locale/en-gb'; +import 'moment/locale/fr'; +import 'moment/locale/it'; +import 'moment/locale/es'; +import 'moment/locale/pl'; +import 'moment/locale/nl'; +import 'moment/locale/pt'; +import 'moment/locale/uk'; +import 'moment/locale/zh-cn'; import { Button, @@ -135,6 +147,9 @@ class VisEngine extends React.Component { timeStart: JSON.parse(window.localStorage.getItem('timeStart')) || null, }; + // set moment locale + moment.locale(window.systemLang); + // this.jsonViews = JSON.stringify(props.views); // this.divRef = React.createRef(); @@ -685,9 +700,9 @@ class VisEngine extends React.Component { return result; }, formatBinding: (format, view, wid, widget, widgetData, values) => - this.formatUtils.formatBinding(format, view, wid, widget, widgetData, values), + this.formatUtils.formatBinding(format, view, wid, widget, widgetData, values, moment), getViewOfWidget: id => { - // find view of this widget + // find a view of this widget for (const v in this.props.views) { if (v === '___settings') { continue; @@ -2035,6 +2050,7 @@ ${this.scripts} onIgnoreMouseEvents: this.props.runtime ? null : this.props.onIgnoreMouseEvents, disableInteraction: this.props.disableInteraction, toggleTheme: this.props.toggleTheme, + moment, }; const views = Object.keys(this.props.views).map(view => { diff --git a/src/src/Vis/visFormatUtils.jsx b/src/src/Vis/visFormatUtils.jsx index 0b87c8081..b97249f40 100644 --- a/src/src/Vis/visFormatUtils.jsx +++ b/src/src/Vis/visFormatUtils.jsx @@ -12,8 +12,6 @@ * Licensees may copy, distribute, display, and perform the work and make derivative works based on it only for noncommercial purposes. * (Free for non-commercial use). */ - -import * as moment from 'moment'; import { extractBinding } from './visUtils'; class VisFormatUtils { @@ -84,7 +82,7 @@ class VisFormatUtils { return Number.isNaN(value) ? '' : value.toFixed(decimals || 0).replace(format[0], format[1]).replace(/\B(?=(\d{3})+(?!\d))/g, format[0]); } - formatMomentDate(dateObj, _format, useTodayOrYesterday) { + formatMomentDate(dateObj, _format, useTodayOrYesterday, moment) { useTodayOrYesterday = typeof useTodayOrYesterday !== 'undefined' ? useTodayOrYesterday : false; if (!dateObj) return ''; @@ -280,7 +278,7 @@ class VisFormatUtils { return result; } - formatBinding(format, view, wid, widget, widgetData, values) { + formatBinding(format, view, wid, widget, widgetData, values, moment) { values = values || this.vis.states; const oids = this.extractBinding(format); @@ -431,9 +429,9 @@ class VisFormatUtils { const params = oids[t].operations[k].arg.split(','); if (params.length === 1) { - value = this.formatMomentDate(value, params[0]); + value = this.formatMomentDate(value, params[0], false, moment); } else if (params.length === 2) { - value = this.formatMomentDate(value, params[0], params[1]); + value = this.formatMomentDate(value, params[0], params[1], moment); } else { value = 'error'; } diff --git a/src/src/Vis/visRxWidget.jsx b/src/src/Vis/visRxWidget.jsx index c1b3ecba4..07640415a 100644 --- a/src/src/Vis/visRxWidget.jsx +++ b/src/src/Vis/visRxWidget.jsx @@ -274,6 +274,7 @@ class VisRxWidget extends VisBaseWidget { this.props.context.views[item.view].widgets[this.props.id], newState.rxData, newState.values, + this.props.context.moment, ); if (item.type === 'data') { @@ -707,6 +708,101 @@ class VisRxWidget extends VisBaseWidget { return null; } + renderLastChange(widgetStyle) { + // show last change + const border = parseInt(this.state.rxData['lc-border-radius'], 10) || 0; + const style = { + backgroundColor: 'rgba(182, 182, 182, 0.6)', + fontFamily: 'Tahoma', + position: 'absolute', + zIndex: 0, + borderRadius: this.state.rxData['lc-position-horz'] === 'left' ? + `${border}px 0 0 ${border}px` : + (this.state.rxData['lc-position-horz'] === 'right' ? `0 ${border}px ${border}px 0` : border), + whiteSpace: 'nowrap', + }; + const fontSize = this.state.rxData['lc-font-size']; + if (fontSize) { + if (fontSize.match(/\D/)) { + style.fontSize = this.state.rxData['lc-font-size']; + } else { + style.fontSize = parseFloat(this.state.rxData['lc-font-size']); + } + } + if (this.state.rxData['lc-font-style']) { + style.fontStyle = this.state.rxData['lc-font-style']; + } + if (this.state.rxData['lc-font-family']) { + style.fontFamily = this.state.rxData['lc-font-family']; + } + if (this.state.rxData['lc-bkg-color']) { + style.backgroundColor = this.state.rxData['lc-bkg-color']; + } + if (this.state.rxData['lc-color']) { + style.color = this.state.rxData['lc-color']; + } + if (this.state.rxData['lc-border-width']) { + style.borderWidth = parseInt(this.state.rxData['lc-border-width'], 10) || 0; + } + if (this.state.rxData['lc-border-style']) { + style.borderStyle = this.state.rxData['lc-border-style']; + } + if (this.state.rxData['lc-border-color']) { + style.borderColor = this.state.rxData['lc-border-color']; + } + const padding = parseInt(this.state.rxData['lc-padding'], 10); + if (padding) { + style.padding = padding; + } else { + style.paddingTop = 3; + style.paddingBottom = 3; + } + if (this.state.rxData['lc-zindex']) { + style.zIndex = this.state.rxData['lc-zindex']; + } + if (this.state.rxData['lc-position-vert'] === 'top') { + style.top = parseInt(this.state.rxData['lc-offset-vert'], 10); + } else if (this.state.rxData['lc-position-vert'] === 'bottom') { + style.bottom = parseInt(this.state.rxData['lc-offset-vert'], 10); + } else if (this.state.rxData['lc-position-vert'] === 'middle') { + style.top = `calc(50% + ${parseInt(this.state.rxData['lc-offset-vert'], 10) - 10}px)`; + } + const offset = parseFloat(this.state.rxData['lc-offset-horz']) || 0; + if (this.state.rxData['lc-position-horz'] === 'left') { + style.right = `calc(100% - ${offset}px)`; + if (!padding) { + style.paddingRight = 10; + style.paddingLeft = 10; + } + } else if (this.state.rxData['lc-position-horz'] === 'right') { + style.left = `calc(100% + ${offset}px)`; + if (!padding) { + style.paddingRight = 10; + style.paddingLeft = 10; + } + } else if (this.state.rxData['lc-position-horz'] === 'middle') { + style.left = `calc(50% + ${offset}px)`; + } + + const divLastChange = window.document.createElement('div'); + // `
${this.binds.basic.formatDate(this.states.attr(`${data['lc-oid']}.${data['lc-type'] === 'last-change' ? 'lc' : 'ts'}`), data['lc-format'], data['lc-is-interval'], data['lc-is-moment'])}
` + divLastChange.className = ''; + + widgetStyle.overflow = 'visible'; + return
+ {this.formatDate( + this.state.values[`${this.state.rxData['lc-oid']}.${this.state.rxData['lc-type'] === 'last-change' ? 'lc' : 'ts'}`], + this.state.rxData['lc-format'], + this.state.rxData['lc-is-interval'], + this.state.rxData['lc-is-moment'], + true, + )} +
; + } + renderSignals() { const count = parseInt(this.state.rxData?.['signals-count'], 10) || 0; if (!count) {