diff --git a/README.md b/README.md index c1bf2b127..4e3f716b5 100644 --- a/README.md +++ b/README.md @@ -294,6 +294,10 @@ npm run start ### **WORK IN PROGRESS** --> ## Changelog +### **WORK IN PROGRESS** +* (bluefox) Converted the CanJSWidget to typescript +* (bluefox) Added "clone" button to the attribute groups + ### 2.10.2 (2024-07-10) * (bluefox) Removed incompatible package for styles * (bluefox) All widgets must be updated diff --git a/packages/iobroker.vis-2/src/src/Utils/utils.tsx b/packages/iobroker.vis-2/src/src/Utils/utils.tsx index ad3cfbfcb..4aa54c644 100644 --- a/packages/iobroker.vis-2/src/src/Utils/utils.tsx +++ b/packages/iobroker.vis-2/src/src/Utils/utils.tsx @@ -18,7 +18,7 @@ export const NOTHING_SELECTED = 'nothing_selected'; * * @param style the style to modify */ -export function calculateOverflow(style: React.CSSProperties): void { +export function calculateOverflow(style: CSSStyleDeclaration | React.CSSProperties): void { if (!style.overflowX && !style.overflowY) { style.overflow = 'visible'; } else if (style.overflow) { diff --git a/packages/iobroker.vis-2/src/src/Vis/Widgets/Tabs/TabsSliderTabs.jsx b/packages/iobroker.vis-2/src/src/Vis/Widgets/Tabs/TabsSliderTabs.tsx similarity index 85% rename from packages/iobroker.vis-2/src/src/Vis/Widgets/Tabs/TabsSliderTabs.jsx rename to packages/iobroker.vis-2/src/src/Vis/Widgets/Tabs/TabsSliderTabs.tsx index 74e329d82..5654ef847 100644 --- a/packages/iobroker.vis-2/src/src/Vis/Widgets/Tabs/TabsSliderTabs.jsx +++ b/packages/iobroker.vis-2/src/src/Vis/Widgets/Tabs/TabsSliderTabs.tsx @@ -14,15 +14,34 @@ */ import React from 'react'; -import PropTypes from 'prop-types'; import { Tab, Tabs } from '@mui/material'; import { Icon } from '@iobroker/adapter-react-v5'; // eslint-disable-next-line import/no-cycle -import VisRxWidget from '../../visRxWidget'; +import VisRxWidget, { type VisRxWidgetState } from '../../visRxWidget'; +import type {RxRenderWidgetProps} from "@iobroker/types-vis-2"; -class TabsSliderTabs extends VisRxWidget { +interface RxData { + show_tabs: number; + vertical: boolean; + variant: '' | 'centered' | 'fullWidth'; + color: string; + [key: `title_tab_${number}`]: string; + [key: `contains_view_${number}`]: string; + [key: `icon_tab_${number}`]: string; + [key: `image_tab_${number}`]: string; + [key: `icon_size_${number}`]: number; + [key: `icon_color_${number}`]: string; + [key: `overflow_x_${number}`]: '' | 'visible' | 'hidden' | 'scroll' | 'auto' | 'initial' | 'inherit'; + [key: `overflow_y_${number}`]: '' | 'visible' | 'hidden' | 'scroll' | 'auto' | 'initial' | 'inherit'; +} + +interface TabsSliderTabsState extends VisRxWidgetState { + tabIndex: number; +} + +class TabsSliderTabs extends VisRxWidget { static getWidgetInfo() { return { id: 'tplSTab', @@ -53,7 +72,7 @@ class TabsSliderTabs extends VisRxWidget { name: 'variant', type: 'select', label: 'vis_2_widgets_widgets_tabs_variant', - hidden: data => data.vertical, + hidden: '!!data.vertical', options: [ { value: '', label: 'vis_2_widgets_widgets_tabs_variant_default' }, { value: 'centered', label: 'vis_2_widgets_widgets_tabs_variant_centered' }, @@ -88,13 +107,13 @@ class TabsSliderTabs extends VisRxWidget { name: 'icon_tab_', type: 'icon64', label: 'vis_2_widgets_widgets_tabs_tab_icon', - hidden: (data, index) => data[`image_tab_${index}`], + hidden: '!!data["image_tab_" + index"]', }, { name: 'image_tab_', type: 'image', label: 'vis_2_widgets_widgets_tabs_tab_image', - hidden: (data, index) => data[`icon_tab_${index}`], + hidden: '!!data["icon_tab_" + index"]', }, { name: 'icon_size_', @@ -102,13 +121,13 @@ class TabsSliderTabs extends VisRxWidget { min: 0, max: 100, label: 'vis_2_widgets_widgets_tabs_tab_icon_size', - hidden: (data, index) => !data[`icon_tab_${index}`] && !data[`image_tab_${index}`], + hidden: '!data["icon_tab_" + index"] && !data["image_tab_" + index"]', }, { name: 'icon_color_', type: 'color', label: 'vis_2_widgets_widgets_tabs_tab_icon_color', - hidden: (data, index) => !data[`icon_tab_${index}`], + hidden: '!data["icon_tab_" + index"]', }, { name: 'overflow_x_', @@ -131,14 +150,15 @@ class TabsSliderTabs extends VisRxWidget { width: 250, height: 250, }, - }; + } as const; } async componentDidMount() { super.componentDidMount(); - let tabIndex = window.localStorage.getItem(`${this.props.id}-tabIndex`); - if (tabIndex) { - tabIndex = parseInt(tabIndex, 10) || 0; + const tabIndexStr = window.localStorage.getItem(`${this.props.id}-tabIndex`); + let tabIndex = 0; + if (tabIndexStr) { + tabIndex = parseInt(tabIndexStr, 10) || 0; } this.setState({ tabIndex }); } @@ -150,7 +170,7 @@ class TabsSliderTabs extends VisRxWidget { getWidgetView() { const view = this.state.rxData[`contains_view_${this.state.tabIndex + 1}`]; - const style = { + const style: React.CSSProperties = { flex: 1, position: 'relative', }; @@ -181,7 +201,7 @@ class TabsSliderTabs extends VisRxWidget { ; } - renderWidgetBody(props) { + renderWidgetBody(props: RxRenderWidgetProps): React.JSX.Element { super.renderWidgetBody(props); // set default width and height @@ -193,7 +213,7 @@ class TabsSliderTabs extends VisRxWidget { } const tabs = []; - for (let t = 0; t < parseInt(this.state.rxData.show_tabs, 10); t++) { + for (let t = 0; t < parseInt(this.state.rxData.show_tabs as unknown as string, 10); t++) { const color = this.state.rxData[`icon_color_${t + 1}`]; const icon = this.state.rxData[`icon_tab_${t + 1}`]; let image = this.state.rxData[`image_tab_${t + 1}`]; @@ -247,11 +267,4 @@ class TabsSliderTabs extends VisRxWidget { } } -TabsSliderTabs.propTypes = { - id: PropTypes.string.isRequired, - context: PropTypes.object.isRequired, - view: PropTypes.string.isRequired, - editMode: PropTypes.bool.isRequired, -}; - export default TabsSliderTabs; diff --git a/packages/iobroker.vis-2/src/src/Vis/visBaseWidget.tsx b/packages/iobroker.vis-2/src/src/Vis/visBaseWidget.tsx index 45a1169ee..0f8297b62 100644 --- a/packages/iobroker.vis-2/src/src/Vis/visBaseWidget.tsx +++ b/packages/iobroker.vis-2/src/src/Vis/visBaseWidget.tsx @@ -101,23 +101,23 @@ export interface WidgetStyleState extends WidgetStyle { } export interface VisBaseWidgetState { + applyBindings?: false | true | { top: string | number; left: string | number }; data: WidgetDataState | GroupDataState; - style: WidgetStyleState; + draggable?: boolean; editMode: boolean; - rxStyle?: WidgetStyleState; - applyBindings?: false | true | { top: string | number; left: string | number }; + gap?: number; + hideHelper?: boolean; + isHidden?: boolean; multiViewWidget?: boolean; - selected?: boolean; - selectedOne?: boolean; resizable?: boolean; resizeHandles?: ResizeHandler[]; - widgetHint?: 'light' | 'dark' | 'hide'; - hideHelper?: boolean; - isHidden?: boolean; - gap?: number; - draggable?: boolean; + rxStyle?: WidgetStyleState; + selected?: boolean; + selectedOne?: boolean; showRelativeMoveMenu?: boolean; + style: WidgetStyleState; usedInWidget: boolean; + widgetHint?: 'light' | 'dark' | 'hide'; } export interface VisBaseWidgetMovement { @@ -156,6 +156,14 @@ interface VisBaseWidget { renderLastChange(style: unknown): React.ReactNode; } +interface CanHTMLDivElement extends HTMLDivElement { + _customHandlers?: { + onShow: (el: HTMLDivElement, id: string) => void; + onHide: (el: HTMLDivElement, id: string) => void; + }; + _storedDisplay?: React.CSSProperties['display']; +} + class VisBaseWidget = VisBaseWidgetState> extends React.Component { static FORBIDDEN_CHARS = /[^._\-/ :!#$%&()+=@^{}|~]+/g; // from https://github.com/ioBroker/ioBroker.js-controller/blob/master/packages/common/lib/common/tools.js @@ -169,7 +177,7 @@ class VisBaseWidget = VisBaseWidgetSt protected refService = React.createRef(); - protected widDiv: null | HTMLDivElement = null; + protected widDiv: null | CanHTMLDivElement = null; readonly onCommandBound: typeof this.onCommand; @@ -519,8 +527,8 @@ class VisBaseWidget = VisBaseWidgetSt }); } - static parseStyle(style: string, isRxStyle: boolean) { - const result: Record = {}; + static parseStyle(style: string, isRxStyle?: boolean): Record { + const result: Record = {}; // style is like "height: 10; width: 20" (style || '').split(';').forEach(part => { part = part.trim(); @@ -1304,7 +1312,7 @@ class VisBaseWidget = VisBaseWidgetSt * @param _props */ // eslint-disable-next-line class-methods-use-this,no-unused-vars, @typescript-eslint/no-unused-vars - renderWidgetBody(_props: RxRenderWidgetProps): React.JSX.Element | null { + renderWidgetBody(_props: RxRenderWidgetProps): React.JSX.Element | React.JSX.Element[] | null { // Default render method. Normally it should be overwritten return
= VisBaseWidgetSt if (value && typeof value === 'string' && !value.includes('{')) { anyStyle[attr] = VisBaseWidget.correctStylePxValue(value); } - } else if (this.props.context.allWidgets[this.props.id] && - this.props.context.allWidgets[this.props.id].style && - this.props.context.allWidgets[this.props.id].style[attr] !== undefined - ) { - // try to steal style by canWidget - if (!(this.props.context.allWidgets[this.props.id].style[attr] as string).includes('{')) { - anyStyle[attr] = VisBaseWidget.correctStylePxValue(this.props.context.allWidgets[this.props.id].style[attr]); + } else { + const styleVal: string | number | undefined | null = (this.props.context.allWidgets[this.props.id]?.style as unknown as Record)?.[attr]; + if (styleVal !== undefined && styleVal !== null) { + // try to steal style by canWidget + if (!styleVal.toString().includes('{')) { + anyStyle[attr] = VisBaseWidget.correctStylePxValue(styleVal); + } } } } diff --git a/packages/iobroker.vis-2/src/src/Vis/visCanWidget.jsx b/packages/iobroker.vis-2/src/src/Vis/visCanWidget.tsx similarity index 63% rename from packages/iobroker.vis-2/src/src/Vis/visCanWidget.jsx rename to packages/iobroker.vis-2/src/src/Vis/visCanWidget.tsx index 8ce525491..a4b6f4562 100644 --- a/packages/iobroker.vis-2/src/src/Vis/visCanWidget.jsx +++ b/packages/iobroker.vis-2/src/src/Vis/visCanWidget.tsx @@ -2,7 +2,7 @@ * ioBroker.vis * https://github.com/ioBroker/ioBroker.vis * - * Copyright (c) 2022-2023 Denis Haev https://github.com/GermanBluefox, + * Copyright (c) 2022-2024 Denis Haev https://github.com/GermanBluefox, * Creative Common Attribution-NonCommercial (CC BY-NC) * * http://creativecommons.org/licenses/by-nc/4.0/ @@ -14,35 +14,60 @@ */ import React from 'react'; -import PropTypes from 'prop-types'; import { calculateOverflow, isVarFinite, deepClone } from '@/Utils/utils'; import { replaceGroupAttr, addClass, getUsedObjectIDsInWidget, } from './visUtils'; -import VisBaseWidget from './visBaseWidget'; +import VisBaseWidget, {type VisBaseWidgetProps, type VisBaseWidgetState, VisWidgetCommand} from './visBaseWidget'; +import { + CanObservable, + CanWidgetStore, + ResizeHandler, + type RxRenderWidgetProps, SingleWidget, SingleWidgetId, + StateID, VisLinkContextBinding, VisLinkContextItem, VisLinkContextSignalItem, VisStateUsage, VisViewProps, + WidgetData, WidgetStyle, +} from '@iobroker/types-vis-2'; + +interface WidgetDataWithParsedFilter extends WidgetData { + wid: SingleWidgetId; + filterKeyParsed?: string[]; +} + +type VisWidgetCanCommand = VisWidgetCommand | 'updatePosition' | 'updateContainers' | 'changeFilter' | 'collectFilters'; -const analyzeDraggableResizable = (el, result, widgetStyle) => { +interface VisCanWidgetState extends VisBaseWidgetState { + mounted?: boolean; + legacyViewContainers?: string[]; + virtualHeight?: number; + virtualWidth?: number; +} + +function analyzeDraggableResizable( + el: HTMLElement, + result: Partial | null, + widgetStyle: WidgetStyle, +): Partial { result = result || {}; result.resizable = true; result.draggable = true; - if (el && el.dataset) { - let resizableOptions = el.dataset.visResizable; - if (resizableOptions) { + if (el?.dataset) { + const resizableOptionsStr = el.dataset.visResizable; + if (resizableOptionsStr) { + let resizableOptions: { disabled?: boolean; handles?: string } | null = null; try { - resizableOptions = JSON.parse(resizableOptions); + resizableOptions = JSON.parse(resizableOptionsStr); } catch (error) { console.error(`Cannot parse resizable options by ${el.getAttribute('id')}: ${resizableOptions}`); - resizableOptions = null; } if (resizableOptions) { if (resizableOptions.disabled !== undefined) { result.resizable = !resizableOptions.disabled; } if (resizableOptions.handles !== undefined) { - result.resizeHandles = resizableOptions.handles.split(',').map(h => h.trim()); + result.resizeHandles = resizableOptions.handles.split(',').map(h => h.trim()) as ResizeHandler[]; } } if (widgetStyle && !result.resizable && (!widgetStyle.width || !widgetStyle.height)) { @@ -51,13 +76,13 @@ const analyzeDraggableResizable = (el, result, widgetStyle) => { } } - let draggableOptions = el.dataset.visDraggable; - if (draggableOptions) { + let draggableOptionsStr = el.dataset.visDraggable; + if (draggableOptionsStr) { + let draggableOptions: { disabled?: boolean } | null = null; try { - draggableOptions = JSON.parse(draggableOptions); + draggableOptions = JSON.parse(draggableOptionsStr); } catch (error) { console.error(`Cannot parse draggable options by ${el.getAttribute('id')}: ${draggableOptions}`); - draggableOptions = null; } if (draggableOptions) { if (draggableOptions.disabled !== undefined) { @@ -69,41 +94,58 @@ const analyzeDraggableResizable = (el, result, widgetStyle) => { result.hideHelper = el.dataset.visHideHelper === 'true'; } return result; -}; +} -class VisCanWidget extends VisBaseWidget { - constructor(props) { - super(props); +class VisCanWidget extends VisBaseWidget { + private readonly refViews: Record> = {}; + + readonly isCanWidget = true; + + private bindings: Record; + + private IDs: StateID[]; + + private bindingsTimer: ReturnType | null = null; + + private filterDisplay: React.CSSProperties['display'] | undefined; - this.refViews = {}; + private lastState: string | undefined; - this.isCanWidget = true; + private oldEditMode: boolean | undefined; - this.state = { + private oldData: string | undefined; + + private oldStyle: string | undefined; + + private updateOnStyle: boolean | undefined; + + constructor(props: VisBaseWidgetProps) { + super(props); + + Object.assign(this.state, { mounted: false, legacyViewContainers: [], hideHelper: false, resizable: true, draggable: true, - ...this.state, - }; + }); this.setupSubscriptions(); - this.props.context.linkContext.registerChangeHandler(this.props.id, this.changeHandler); + props.context.linkContext.registerChangeHandler(props.id, this.changeHandler); // legacy support // if (props.tpl?.includes('materialdesign') && this.props.context.buildLegacyStructures) { // event if no materialdesign widget used, the legacy structures must build, // because the materialdesign set tries to call vis.subscribing.byViews - this.props.context.buildLegacyStructures(); + props.context.buildLegacyStructures(); // } } setupSubscriptions() { this.bindings = {}; - const linkContext = { + const linkContext: VisStateUsage = { IDs: [], bindings: this.bindings, visibility: this.props.context.linkContext.visibility, @@ -111,7 +153,12 @@ class VisCanWidget extends VisBaseWidget { signals: this.props.context.linkContext.signals, }; - getUsedObjectIDsInWidget(this.props.context.views, this.props.view, this.props.id, linkContext); + getUsedObjectIDsInWidget( + this.props.context.views, + this.props.view, + this.props.id, + linkContext, + ); this.IDs = linkContext.IDs; @@ -122,7 +169,7 @@ class VisCanWidget extends VisBaseWidget { }); // free mem - Object.keys(linkContext).forEach(attr => linkContext[attr] = null); + Object.keys(linkContext).forEach(attr => (linkContext as Record)[attr] = null); } componentDidMount() { @@ -133,7 +180,7 @@ class VisCanWidget extends VisBaseWidget { if (!this.widDiv) { // link could be a ref or direct a div (e.g., by groups) // console.log('Widget mounted'); - this.renderWidget(() => { + this.renderWidget(undefined, undefined, undefined, undefined, () => { const newState = { mounted: true }; if (this.props.context.allWidgets[this.props.id]) { @@ -172,14 +219,19 @@ class VisCanWidget extends VisBaseWidget { this.destroy(); } - static applyStyle(el, style, isSelected, editMode) { + static applyStyle( + el: HTMLDivElement, + style: WidgetStyle | string, + isSelected?: boolean, + editMode?: boolean, + ): void { if (typeof style === 'string') { // style is a string // "height: 10; width: 20" - style = VisBaseWidget.parseStyle(style); - Object.keys(style).forEach(attr => { + const styleFromString: Record = VisBaseWidget.parseStyle(style); + Object.keys(styleFromString).forEach(attr => { if (!attr.startsWith('_')) { - let value = style[attr]; + let value: string | number = styleFromString[attr]; if (attr === 'top' || attr === 'left' || attr === 'width' || attr === 'height') { if (value !== '0' && value !== 0 && value !== null && value !== '' && value.toString().match(/^[+-]?([0-9]*[.])?[0-9]+$/)) { value = `${value}px`; @@ -191,20 +243,21 @@ class VisCanWidget extends VisBaseWidget { attr = attr.replace(/-([a-z])/g, g => g[1].toUpperCase()); } if (value) { - el.style[attr] = value; - } else if (el.style[attr]) { // delete style - el.style[attr] = ''; + (el.style as any as Record)[attr] = value; + } else if ((el.style as any as Record)[attr]) { // delete style + (el.style as any as Record)[attr] = ''; } } }); } else if (style) { + const styleObj: WidgetStyle = style as WidgetStyle; // style is an object // { // height: 10, // } - Object.keys(style).forEach(attr => { - if (attr && style[attr] !== undefined && style[attr] !== null && !attr.startsWith('_')) { - let value = style[attr]; + Object.keys(styleObj).forEach(attr => { + let value = (styleObj as Record)[attr]; + if (attr && value !== undefined && value !== null && !attr.startsWith('_')) { if (attr === 'top' || attr === 'left' || attr === 'width' || attr === 'height') { if (value !== '0' && value !== 0 && value !== null && value !== '' && value.toString().match(/^[+-]?([0-9]*[.])?[0-9]+$/)) { value = `${value}px`; @@ -215,9 +268,9 @@ class VisCanWidget extends VisBaseWidget { attr = attr.replace(/-([a-z])/g, g => g[1].toUpperCase()); } if (value) { - el.style[attr] = value; - } else if (el.style[attr]) { // delete style - el.style[attr] = ''; + (el.style as unknown as Record)[attr] = value; + } else if ((el.style as unknown as Record)[attr]) { // delete style + (el.style as unknown as Record)[attr] = ''; } } }); @@ -225,14 +278,14 @@ class VisCanWidget extends VisBaseWidget { if (editMode) { if (isSelected) { // z-index - el.style.zIndex = (500 + (parseInt(style['z-index'], 10) || 0)).toString(); + el.style.zIndex = (500 + (parseInt(styleObj['z-index'] as unknown as string, 10) || 0)).toString(); } const zIndex = parseInt(el.style.zIndex, 10) || 0; // apply zIndex to parent - const overlay = el.parentNode.querySelector(`#rx_${el.id}`); + const overlay: HTMLDivElement = el.parentNode.querySelector(`#rx_${el.id}`); if (overlay) { - overlay.style.zIndex = zIndex + 1; + overlay.style.zIndex = (zIndex + 1).toString(); } el.style.userSelect = 'none'; el.style.pointerEvents = 'none'; @@ -251,8 +304,12 @@ class VisCanWidget extends VisBaseWidget { } // this method may be not in form onCommand = command => {} - onCommand(command, options) { - const result = super.onCommand(command, options); + onCommand( + command: VisWidgetCanCommand, + options?: { filter?: string[] }, + ) { + const result: boolean = super.onCommand(command, options); + if (result === false) { if (command === 'updatePosition') { // move by canJS widgets the name and overlapping div @@ -267,9 +324,9 @@ class VisCanWidget extends VisBaseWidget { // try to find 'vis-view-container' in it const containers = this.widDiv.querySelectorAll('.vis-view-container'); if (containers.length) { - const legacyViewContainers = []; + const legacyViewContainers: string[] = []; for (let v = 0; v < containers.length; v++) { - const view = (containers[v].dataset.visContains || '').trim(); + const view = ((containers[v] as HTMLDivElement).dataset.visContains || '').trim(); if (view && view !== 'undefined' && view !== 'null') { legacyViewContainers.push(view); containers[v].className = addClass(containers[v].className, 'vis-editmode-helper'); @@ -288,7 +345,7 @@ class VisCanWidget extends VisBaseWidget { } // if filter was disabled - if (!options || !options.filter.length) { + if (!options?.filter?.length) { // just show if it was hidden if (this.filterDisplay !== undefined) { this.widDiv.style.display = this.filterDisplay; @@ -308,7 +365,7 @@ class VisCanWidget extends VisBaseWidget { } } } else { - const wFilters = this.props.context.allWidgets[this.props.id]?.data.filterkey; + const wFilters: string[] | undefined = this.props.context.allWidgets[this.props.id]?.data.filterkey as unknown as (string[] | undefined); if (wFilters) { // we cannot use "find", as it is "can" observable @@ -349,30 +406,32 @@ class VisCanWidget extends VisBaseWidget { return result; } - componentDidUpdate(/* prevProps, prevState, snapshot */) { - if (this.state.legacyViewContainers.length) { - console.log('widget updated'); - // place all views to corresponding containers - Object.keys(this.refViews).forEach(view => { - if (this.refViews[view].current) { - const container = this.widDiv.querySelector(`.vis-view-container[data-vis-contains="${view}"]`); - const current = this.refViews[view].current; - if (current && !current.refView?.current) { - // it is just div - if (current.parentNode !== container) { - // current._originalParent = current.parentNode; - // container.appendChild(current); - } - } else if (current?.refView?.current && container && current.refView.current.parentNode !== container) { - current.refView.current._originalParent = current.refView.current.parentNode; - container.appendChild(current.refView.current); - } - } - }); - } - } - - destroy = update => { + // following code is inactive + // componentDidUpdate(/* prevProps, prevState, snapshot */) { + // if (this.state.legacyViewContainers.length) { + // console.log('widget updated'); + // // place all views to corresponding containers + // Object.keys(this.refViews).forEach(view => { + // if (this.refViews[view].current) { + // const container = this.widDiv.querySelector(`.vis-view-container[data-vis-contains="${view}"]`); + // const current = this.refViews[view].current; + // + // if (current && !current.refView?.current) { + // // it is just div + // if (current.parentNode !== container) { + // // current._originalParent = current.parentNode; + // // container.appendChild(current); + // } + // } else if (current?.refView?.current && container && current.refView.current.parentNode !== container) { + // current.refView.current._originalParent = current.refView.current.parentNode; + // container.appendChild(current.refView.current); + // } + // } + // }); + // } + // } + + destroy(update?: boolean): void { // !update && console.log('destroy ' + this.props.id); // destroy map if (this.props.context.allWidgets[this.props.id]) { @@ -381,6 +440,7 @@ class VisCanWidget extends VisBaseWidget { // do not destroy groups by update if (this.widDiv && (!update || !this.props.id.startsWith('g'))) { + // @ts-expect-error it is OK const $wid = this.props.context.jQuery(this.widDiv); const destroy = $wid.data('destroy'); @@ -395,7 +455,11 @@ class VisCanWidget extends VisBaseWidget { } }; - changeHandler = (type, item, stateId) => { + changeHandler = ( + type: 'style' | 'signal' | 'visibility' | 'lastChange' | 'binding', + item?: VisLinkContextBinding | VisLinkContextItem | VisLinkContextSignalItem, + stateId?: string, + ) => { // console.log(`[${this.props.id}] update widget because of "${type}" "${stateId}": ${JSON.stringify(state)}`); if (this.widDiv) { if (type === 'style') { @@ -404,9 +468,9 @@ class VisCanWidget extends VisBaseWidget { VisCanWidget.applyStyle(this.widDiv, this.props.context.allWidgets[this.props.id].style, this.state.selected, this.state.editMode); } } else if (type === 'signal') { - this.updateSignal(item); + this.updateSignal(item as VisLinkContextSignalItem); } else if (type === 'visibility') { - this.updateVisibility(item); + this.updateVisibility(); } else if (type === 'lastChange') { this.updateLastChange(); } else if (type === 'binding') { @@ -415,9 +479,9 @@ class VisCanWidget extends VisBaseWidget { } }; - updateSignal(item) { + updateSignal(item: VisLinkContextSignalItem): void { if (this.widDiv) { - const signalDiv = this.widDiv.querySelector(`.vis-signal[data-index="${item.index}"]`); + const signalDiv: HTMLDivElement = this.widDiv.querySelector(`.vis-signal[data-index="${item.index}"]`); if (signalDiv) { if (this.isSignalVisible(item.index)) { signalDiv.style.display = ''; @@ -435,7 +499,7 @@ class VisCanWidget extends VisBaseWidget { const lcDiv = this.widDiv.querySelector('.vis-last-change'); if (lcDiv) { lcDiv.innerHTML = window.vis.binds.basic.formatDate( - this.props.context.canStates.attr(`${widgetData['lc-oid']}.${widgetData['lc-type'] === 'last-change' ? 'lc' : 'ts'}`), + this.props.context.canStates.attr(`${widgetData['lc-oid']}.${widgetData['lc-type'] === 'last-change' ? 'lc' : 'ts'}`) as string, widgetData['lc-format'], widgetData['lc-is-interval'], widgetData['lc-is-moment'], @@ -451,7 +515,14 @@ class VisCanWidget extends VisBaseWidget { if (this.widDiv && !this.state.editMode) { const widgetData = this.props.context.allWidgets[this.props.id]?.data; if (widgetData) { - if (VisBaseWidget.isWidgetHidden(widgetData, this.props.context.canStates, this.props.id) || this.isWidgetFilteredOut(widgetData)) { + if ( + VisBaseWidget.isWidgetHidden( + widgetData, + this.props.context.canStates, + this.props.id + ) || + this.isWidgetFilteredOut(widgetData) + ) { this.widDiv._storedDisplay = this.widDiv.style.display; this.widDiv.style.display = 'none'; @@ -476,170 +547,171 @@ class VisCanWidget extends VisBaseWidget { } } - addGestures(widgetData) { - // gestures - const gestures = ['swipeRight', 'swipeLeft', 'swipeUp', 'swipeDown', 'rotateLeft', 'rotateRight', 'pinchIn', 'pinchOut', 'swiping', 'rotating', 'pinching']; - const $$wid = this.props.context.$$(`#${this.props.id}`); - const $wid = this.props.context.jQuery(this.widDiv); - const offsetX = parseInt(widgetData['gestures-offsetX']) || 0; - const offsetY = parseInt(widgetData['gestures-offsetY']) || 0; - - gestures.forEach(gesture => { - const oid = widgetData[`gestures-${gesture}-oid`]; - if (widgetData && oid) { - const val = widgetData[`gestures-${gesture}-value`]; - const delta = parseInt(widgetData[`gestures-${gesture}-delta`]) || 10; - const limit = parseFloat(widgetData[`gestures-${gesture}-limit`]) || false; - const max = parseFloat(widgetData[`gestures-${gesture}-maximum`]) || 100; - const min = parseFloat(widgetData[`gestures-${gesture}-minimum`]) || 0; - - let newVal = null; - let valState = this.props.context.canStates.attr(`${oid}.val`); - let $indicator; - if (valState !== undefined && valState !== null) { - $wid.on('touchmove', evt => evt.preventDefault()); - - $wid.css({ - '-webkit-user-select': 'none', - '-khtml-user-select': 'none', - '-moz-user-select': 'none', - '-ms-user-select': 'none', - 'user-select': 'none', - }); - - $$wid[gesture](data => { - valState = this.props.context.canStates.attr(`${oid}.val`); - - if (val === 'toggle') { - if (valState === true) { - newVal = false; - } else if (valState === false) { - newVal = true; - } else { - newVal = null; - return; - } - } else if (gesture === 'swiping' || gesture === 'rotating' || gesture === 'pinching') { - if (newVal === null) { - $indicator = this.$(`#${widgetData['gestures-indicator']}`); - // create default indicator - if (!$indicator.length) { - // noinspection JSJQueryEfficiency - $indicator = this.$('#gestureIndicator'); - if (!$indicator.length) { - this.$('body').append('
'); - $indicator = this.$('#gestureIndicator'); - - // eslint-disable-next-line @typescript-eslint/no-this-alias - const that = this; - // eslint-disable-next-line func-names - $indicator.on('gestureUpdate', function (event, evData) { - const $el = that.$(this); - if (evData.val === null) { - $el.hide(); - } else { - $el.html(evData.val); - $el.css({ - left: `${parseInt(evData.x) - $el.width() / 2}px`, - top: `${parseInt(evData.y) - $el.height() / 2}px`, - }).show(); - } - }); - } - } - - this.$(this.root).css({ - '-webkit-user-select': 'none', - '-khtml-user-select': 'none', - '-moz-user-select': 'none', - '-ms-user-select': 'none', - 'user-select': 'none', - }); - - this.$(document).on('mouseup.gesture touchend.gesture', () => { - if (newVal !== null) { - this.props.context.setValue(oid, newVal); - newVal = null; - } - $indicator.trigger('gestureUpdate', { val: null }); - this.$(document).off('mouseup.gesture touchend.gesture'); - - this.$(this.root).css({ - '-webkit-user-select': 'text', - '-khtml-user-select': 'text', - '-moz-user-select': 'text', - '-ms-user-select': 'text', - 'user-select': 'text', - }); - }); - } - let swipeDelta; - let indicatorX; - let indicatorY = 0; - - switch (gesture) { - case 'swiping': - swipeDelta = Math.abs(data.touch.delta.x) > Math.abs(data.touch.delta.y) ? data.touch.delta.x : data.touch.delta.y * (-1); - swipeDelta = swipeDelta > 0 ? Math.floor(swipeDelta / delta) : Math.ceil(swipeDelta / delta); - indicatorX = data.touch.x; - indicatorY = data.touch.y; - break; - - case 'rotating': - swipeDelta = data.touch.delta; - swipeDelta = swipeDelta > 0 ? Math.floor(swipeDelta / delta) : Math.ceil(swipeDelta / delta); - if (data.touch.touches[0].y < data.touch.touches[1].y) { - indicatorX = data.touch.touches[1].x; - indicatorY = data.touch.touches[1].y; - } else { - indicatorX = data.touch.touches[0].x; - indicatorY = data.touch.touches[0].y; - } - break; - - case 'pinching': - swipeDelta = data.touch.delta; - swipeDelta = swipeDelta > 0 ? Math.floor(swipeDelta / delta) : Math.ceil(swipeDelta / delta); - if (data.touch.touches[0].y < data.touch.touches[1].y) { - indicatorX = data.touch.touches[1].x; - indicatorY = data.touch.touches[1].y; - } else { - indicatorX = data.touch.touches[0].x; - indicatorY = data.touch.touches[0].y; - } - break; - - default: - break; - } - - newVal = (parseFloat(valState) || 0) + (parseFloat(val) || 1) * swipeDelta; - newVal = Math.max(min, Math.min(max, newVal)); - $indicator.trigger('gestureUpdate', { - val: newVal, - x: indicatorX + offsetX, - y: indicatorY + offsetY, - }); - return; - } else if (limit !== false) { - newVal = (parseFloat(valState) || 0) + (parseFloat(val) || 1); - if (parseFloat(val) > 0 && newVal > limit) { - newVal = limit; - } else if (parseFloat(val) < 0 && newVal < limit) { - newVal = limit; - } - } else { - newVal = val; - } - this.props.context.setValue(oid, newVal); - newVal = null; - }); - } - } - }); - } - - isSignalVisible(index, widgetData) { + // addGestures(widgetData: WidgetData): void { + // // gestures + // const gestures = ['swipeRight', 'swipeLeft', 'swipeUp', 'swipeDown', 'rotateLeft', 'rotateRight', 'pinchIn', 'pinchOut', 'swiping', 'rotating', 'pinching']; + // const $$wid = this.props.context.$$(`#${this.props.id}`); + // // @ts-expect-error jquery is too old for TypeScript + // const $wid: unknown = this.props.context.jQuery(this.widDiv); + // const offsetX = parseInt(widgetData['gestures-offsetX']) || 0; + // const offsetY = parseInt(widgetData['gestures-offsetY']) || 0; + // + // gestures.forEach(gesture => { + // const oid = widgetData[`gestures-${gesture}-oid`]; + // if (widgetData && oid) { + // const val = widgetData[`gestures-${gesture}-value`]; + // const delta = parseInt(widgetData[`gestures-${gesture}-delta`]) || 10; + // const limit = parseFloat(widgetData[`gestures-${gesture}-limit`]) || false; + // const max = parseFloat(widgetData[`gestures-${gesture}-maximum`]) || 100; + // const min = parseFloat(widgetData[`gestures-${gesture}-minimum`]) || 0; + // + // let newVal = null; + // let valState = this.props.context.canStates.attr(`${oid}.val`); + // let $indicator; + // if (valState !== undefined && valState !== null) { + // $wid.on('touchmove', evt => evt.preventDefault()); + // + // $wid.css({ + // '-webkit-user-select': 'none', + // '-khtml-user-select': 'none', + // '-moz-user-select': 'none', + // '-ms-user-select': 'none', + // 'user-select': 'none', + // }); + // + // $$wid[gesture](data => { + // valState = this.props.context.canStates.attr(`${oid}.val`); + // + // if (val === 'toggle') { + // if (valState === true) { + // newVal = false; + // } else if (valState === false) { + // newVal = true; + // } else { + // newVal = null; + // return; + // } + // } else if (gesture === 'swiping' || gesture === 'rotating' || gesture === 'pinching') { + // if (newVal === null) { + // $indicator = this.$(`#${widgetData['gestures-indicator']}`); + // // create default indicator + // if (!$indicator.length) { + // // noinspection JSJQueryEfficiency + // $indicator = this.$('#gestureIndicator'); + // if (!$indicator.length) { + // this.$('body').append('
'); + // $indicator = this.$('#gestureIndicator'); + // + // // eslint-disable-next-line @typescript-eslint/no-this-alias + // const that = this; + // // eslint-disable-next-line func-names + // $indicator.on('gestureUpdate', function (event, evData) { + // const $el = that.$(this); + // if (evData.val === null) { + // $el.hide(); + // } else { + // $el.html(evData.val); + // $el.css({ + // left: `${parseInt(evData.x) - $el.width() / 2}px`, + // top: `${parseInt(evData.y) - $el.height() / 2}px`, + // }).show(); + // } + // }); + // } + // } + // + // this.$(this.root).css({ + // '-webkit-user-select': 'none', + // '-khtml-user-select': 'none', + // '-moz-user-select': 'none', + // '-ms-user-select': 'none', + // 'user-select': 'none', + // }); + // + // this.$(document).on('mouseup.gesture touchend.gesture', () => { + // if (newVal !== null) { + // this.props.context.setValue(oid, newVal); + // newVal = null; + // } + // $indicator.trigger('gestureUpdate', { val: null }); + // this.$(document).off('mouseup.gesture touchend.gesture'); + // + // this.$(this.root).css({ + // '-webkit-user-select': 'text', + // '-khtml-user-select': 'text', + // '-moz-user-select': 'text', + // '-ms-user-select': 'text', + // 'user-select': 'text', + // }); + // }); + // } + // let swipeDelta; + // let indicatorX; + // let indicatorY = 0; + // + // switch (gesture) { + // case 'swiping': + // swipeDelta = Math.abs(data.touch.delta.x) > Math.abs(data.touch.delta.y) ? data.touch.delta.x : data.touch.delta.y * (-1); + // swipeDelta = swipeDelta > 0 ? Math.floor(swipeDelta / delta) : Math.ceil(swipeDelta / delta); + // indicatorX = data.touch.x; + // indicatorY = data.touch.y; + // break; + // + // case 'rotating': + // swipeDelta = data.touch.delta; + // swipeDelta = swipeDelta > 0 ? Math.floor(swipeDelta / delta) : Math.ceil(swipeDelta / delta); + // if (data.touch.touches[0].y < data.touch.touches[1].y) { + // indicatorX = data.touch.touches[1].x; + // indicatorY = data.touch.touches[1].y; + // } else { + // indicatorX = data.touch.touches[0].x; + // indicatorY = data.touch.touches[0].y; + // } + // break; + // + // case 'pinching': + // swipeDelta = data.touch.delta; + // swipeDelta = swipeDelta > 0 ? Math.floor(swipeDelta / delta) : Math.ceil(swipeDelta / delta); + // if (data.touch.touches[0].y < data.touch.touches[1].y) { + // indicatorX = data.touch.touches[1].x; + // indicatorY = data.touch.touches[1].y; + // } else { + // indicatorX = data.touch.touches[0].x; + // indicatorY = data.touch.touches[0].y; + // } + // break; + // + // default: + // break; + // } + // + // newVal = (parseFloat(valState) || 0) + (parseFloat(val) || 1) * swipeDelta; + // newVal = Math.max(min, Math.min(max, newVal)); + // $indicator.trigger('gestureUpdate', { + // val: newVal, + // x: indicatorX + offsetX, + // y: indicatorY + offsetY, + // }); + // return; + // } else if (limit !== false) { + // newVal = (parseFloat(valState) || 0) + (parseFloat(val) || 1); + // if (parseFloat(val) > 0 && newVal > limit) { + // newVal = limit; + // } else if (parseFloat(val) < 0 && newVal < limit) { + // newVal = limit; + // } + // } else { + // newVal = val; + // } + // this.props.context.setValue(oid, newVal); + // newVal = null; + // }); + // } + // } + // }); + // } + + isSignalVisible(index: number, widgetData?: WidgetData) { widgetData = widgetData || this.props.context.allWidgets[this.props.id].data; if (!widgetData) { @@ -653,7 +725,7 @@ class VisCanWidget extends VisBaseWidget { const oid = widgetData[`signals-oid-${index}`]; if (oid) { - let val = this.props.context.canStates.attr(`${oid}.val`); + const val = this.props.context.canStates.attr(`${oid}.val`); const condition = widgetData[`signals-cond-${index}`]; let value = widgetData[`signals-val-${index}`]; @@ -669,49 +741,66 @@ class VisCanWidget extends VisBaseWidget { if (val === 'null' && condition !== 'exist' && condition !== 'not exist') { return false; } + let valNotNull: string | number | boolean = val as string | number | boolean; - const t = typeof val; + const t = typeof valNotNull; 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); + valNotNull = JSON.stringify(valNotNull); } 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; + valNotNull = valNotNull.toString(); + if (valNotNull === '1') { + valNotNull = 'true'; + } + if (value === '1') { + value = 'true'; + } + if (valNotNull === '0') { + valNotNull = 'false'; + } + if (value === '0') { + value = 'false'; + } + return value === valNotNull; 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; + valNotNull = valNotNull.toString(); + if (valNotNull === '1') { + valNotNull = 'true'; + } + if (value === '1') { + value = 'true'; + } + if (valNotNull === '0') { + valNotNull = 'false'; + } + if (value === '0') { + value = 'false'; + } + return value !== valNotNull; case '>=': - return val >= value; + return valNotNull >= value; case '<=': - return val <= value; + return valNotNull <= value; case '>': - return val > value; + return valNotNull > value; case '<': - return val < value; + return valNotNull < value; case 'consist': value = value.toString(); - val = val.toString(); - return val.toString().includes(value); + valNotNull = valNotNull.toString(); + return valNotNull.toString().includes(value); case 'not consist': value = value.toString(); - val = val.toString(); - return !val.toString().includes(value); + valNotNull = valNotNull.toString(); + return !valNotNull.toString().includes(value); case 'exist': return value !== 'null'; case 'not exist': @@ -725,7 +814,7 @@ class VisCanWidget extends VisBaseWidget { } } - addSignalIcon(widgetData, index) { + addSignalIcon(widgetData?: WidgetData, index?: number) { widgetData = widgetData || this.props.context.allWidgets[this.props.id]?.data; if (!widgetData) { return; @@ -734,7 +823,7 @@ class VisCanWidget extends VisBaseWidget { //