diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/Basic/BasicFilterDropdown.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/Basic/BasicFilterDropdown.tsx index 31eb4234..e1f46e4a 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/Basic/BasicFilterDropdown.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/Basic/BasicFilterDropdown.tsx @@ -1,553 +1,553 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 React, { useState } from 'react'; - -import { Button, ButtonGroup, FormControl, InputLabel, MenuItem, Select } from '@mui/material'; -import { Edit } from '@mui/icons-material'; - -import { I18n, Icon } from '@iobroker/adapter-react-v5'; - -import type { - RxRenderWidgetProps, - RxWidgetInfo, - WidgetData, - RxWidgetInfoAttributesField, - RxWidgetInfoCustomComponentProperties, - RxWidgetInfoCustomComponentContext, - VisWidgetCommand, -} from '@iobroker/types-vis-2'; -import VisRxWidget from '@/Vis/visRxWidget'; -import FiltersEditorDialog from './FiltersEditorDialog'; - -interface Item { - id?: string; - label: string; - value: string; - icon?: string; - image?: string; - color?: string; - activeColor?: string; - default?: boolean; -} - -type RxData = { - items: Item[]; - type: 'dropdown' | 'horizontal_buttons' | 'vertical_buttons'; - widgetTitle?: string; - autoFocus?: boolean; - multiple?: boolean; - noAllOption?: boolean; - noFilterText?: string; - dropdownVariant?: 'standard' | 'outlined' | 'filled'; - buttonsVariant?: 'outlined' | 'contained' | 'text'; - dropdownSmall?: boolean; -}; - -function processFilter(filters: string[]): void { - const len = filters.length; - for (let f = 0; f < len; f++) { - const filter = filters[f]; - if (filter.includes(',')) { - const ff = filter - .split(',') - .map(t => t.trim()) - .filter(t => t); - const first = ff.shift(); - if (first) { - filters[f] = first; - if (ff.length) { - filters.push(...ff); - } - } - } else if (filter.includes(';')) { - const ff = filter - .split(';') - .map(t => t.trim()) - .filter(t => t); - const first = ff.shift(); - if (first) { - filters[f] = first; - if (ff.length) { - filters.push(...ff); - } - } - } - } -} - -interface ItemsEditorProps { - data: any; - setData: (data: any) => void; - context: RxWidgetInfoCustomComponentContext; -} - -const ItemsEditor = (props: ItemsEditorProps): React.JSX.Element => { - const [open, setOpen] = useState(false); - - let items = props.data.items; - // convert data from "filters" to "items" - if (open && !items && props.data.filters) { - items = props.data.filters - .split(';') - .map((item: string) => ({ label: item.trim(), value: item.trim() })) - .filter((item: Item) => item.value); - } - if (open && typeof items === 'string') { - try { - items = JSON.parse(items); - } catch { - items = []; - } - } - - return ( - <> - - {open ? ( - { - if (newItems) { - const data = JSON.parse(JSON.stringify(props.data)); - data.items = JSON.stringify(newItems); - props.setData(data); - } - setOpen(false); - }} - /> - ) : null} - - ); -}; - -class BasicFilterDropdown extends VisRxWidget { - private editMode: boolean | undefined; - - static getWidgetInfo(): RxWidgetInfo { - return { - id: 'tplFilterDropdown', - visSet: 'basic', - visName: 'filter - dropdown', - visPrev: 'widgets/basic/img/Prev_FilterDropdown.png', - visAttrs: [ - { - name: 'common', - fields: [ - { - name: 'items', - label: 'editor', - type: 'custom', - noBinding: true, - component: ( - _field: RxWidgetInfoAttributesField, - data: WidgetData, - onDataChange: (newData: WidgetData) => void, - props: RxWidgetInfoCustomComponentProperties, - ) => ( - - ), - default: '[]', - }, - { - name: 'type', - label: 'Type', - type: 'select', - options: [ - { - value: 'dropdown', - label: 'basic_filter_type_dropdown', - }, - { - value: 'horizontal_buttons', - label: 'basic_filter_type_horizontal_buttons', - }, - { - value: 'vertical_buttons', - label: 'basic_filter_type_vertical_buttons', - }, - ], - default: 'horizontal_buttons', - }, - { - name: 'widgetTitle', - label: 'name', - hidden: 'data.type !== "dropdown"', - }, - { - name: 'autoFocus', - type: 'checkbox', - hidden: 'data.type !== "dropdown"', - }, - { - name: 'multiple', - label: 'basic_filter_multiple', - type: 'checkbox', - }, - { - name: 'noAllOption', - label: 'basic_no_all_option', - type: 'checkbox', - }, - { - name: 'noFilterText', - label: 'basic_no_filter_text', - type: 'text', - hidden: '!!data.noAllOption', - }, - { - name: 'dropdownVariant', - label: 'jqui_variant', - type: 'select', - noTranslation: true, - options: [ - { - value: 'standard', - label: 'standard', - }, - { - value: 'outlined', - label: 'outlined', - }, - { - value: 'filled', - label: 'filled', - }, - ], - default: 'standard', - hidden: 'data.type !== "dropdown"', - }, - { - name: 'buttonsVariant', - label: 'jqui_variant', - type: 'select', - noTranslation: true, - options: [ - { - value: 'outlined', - label: 'outlined', - }, - { - value: 'contained', - label: 'contained', - }, - { - value: 'text', - label: 'text', - }, - ], - default: 'outlined', - hidden: 'data.type === "dropdown"', - }, - { - name: 'dropdownSmall', - label: 'basic_small', - default: false, - type: 'checkbox', - hidden: 'data.type !== "dropdown"', - }, - ], - }, - ], - // visWidgetLabel: 'value_string', // Label of widget - visDefaultStyle: { - width: 200, - height: 50, - }, - }; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo(): RxWidgetInfo { - return BasicFilterDropdown.getWidgetInfo(); - } - - componentDidMount(): void { - super.componentDidMount(); - // apply default filter - if (!this.props.editMode) { - this.editMode = false; - const items = this.getItems(); - if (items.find((item: Item) => item.default && item.value)) { - const filter: string[] = []; - items.forEach((item: Item) => item.default && filter.push(item.value)); - processFilter(filter); - setTimeout(() => { - const view = this.props.askView('getViewClass'); - view.onCommand('changeFilter', { filter }); - }, 0); - } - } else { - this.editMode = true; - } - } - - onCommand(command: VisWidgetCommand): void { - if (command === 'changeFilter') { - // analyse filter - this.forceUpdate(); - } - super.onCommand(command); - } - - renderDropdown(items: Item[]): React.JSX.Element { - const viewsActiveFilter: string[] = this.props.viewsActiveFilter[this.props.view] || []; - let value; - if (this.state.rxData.multiple) { - value = viewsActiveFilter; - } else { - value = viewsActiveFilter[0] || ''; - } - return ( - - {this.state.rxData.widgetTitle ? {this.state.rxData.widgetTitle} : null} - - - ); - } - - renderButtons(items: Item[]): React.JSX.Element { - const viewsActiveFilter: string[] = this.props.viewsActiveFilter[this.props.view] || []; - return ( - - {this.state.rxData.noAllOption ? null : ( - - )} - {items.map(option => { - let image = option.icon; - if (!image && option.image) { - image = option.image; - if (image.startsWith('_PRJ_NAME/')) { - image = image.replace( - '_PRJ_NAME/', - `../${this.props.context.adapterName}.${this.props.context.instance}/${this.props.context.projectName}/`, - ); - } - } - return ( - - ); - })} - - ); - } - - getItems(): { label: string; value: string }[] { - let items = this.state.data.items; - // convert data from "filters" to "items" - if (!items && this.state.data.filters) { - items = this.state.data.filters - .split(';') - .map((item: string) => ({ label: item.trim(), value: item.trim() })) - .filter((item: Item) => item.value); - } - if (typeof items === 'string') { - try { - items = JSON.parse(items); - } catch { - items = []; - } - } - items = items || []; - return items; - } - - renderWidgetBody(props: RxRenderWidgetProps): React.JSX.Element { - super.renderWidgetBody(props); - if (props.style.width === undefined) { - props.style.width = 200; - } - if (props.style.height === undefined) { - props.style.height = 50; - } - - const items = this.getItems(); - if (this.editMode !== undefined && this.editMode !== this.props.editMode) { - this.editMode = this.props.editMode; - // apply default filter if not in edit mode - if (!this.editMode && items.find((item: Item) => item.default && item.value)) { - const filter: string[] = []; - items.forEach((item: Item) => item.default && filter.push(item.value)); - processFilter(filter); - setTimeout(() => { - const view = this.props.askView('getViewClass'); - view.onCommand('changeFilter', { filter }); - }, 0); - } - } - - const type = this.state.rxData.type || 'dropdown'; - if (type === 'horizontal_buttons' || type === 'vertical_buttons') { - return this.renderButtons(items); - } - - return this.renderDropdown(items); - } -} - -export default BasicFilterDropdown; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2024-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 React, { useState } from 'react'; + +import { Button, ButtonGroup, FormControl, InputLabel, MenuItem, Select } from '@mui/material'; +import { Edit } from '@mui/icons-material'; + +import { I18n, Icon } from '@iobroker/adapter-react-v5'; + +import type { + RxRenderWidgetProps, + RxWidgetInfo, + WidgetData, + RxWidgetInfoAttributesField, + RxWidgetInfoCustomComponentProperties, + RxWidgetInfoCustomComponentContext, + VisWidgetCommand, +} from '@iobroker/types-vis-2'; +import VisRxWidget from '@/Vis/visRxWidget'; +import FiltersEditorDialog from './FiltersEditorDialog'; + +interface Item { + id?: string; + label: string; + value: string; + icon?: string; + image?: string; + color?: string; + activeColor?: string; + default?: boolean; +} + +type RxData = { + items: Item[]; + type: 'dropdown' | 'horizontal_buttons' | 'vertical_buttons'; + widgetTitle?: string; + autoFocus?: boolean; + multiple?: boolean; + noAllOption?: boolean; + noFilterText?: string; + dropdownVariant?: 'standard' | 'outlined' | 'filled'; + buttonsVariant?: 'outlined' | 'contained' | 'text'; + dropdownSmall?: boolean; +}; + +function processFilter(filters: string[]): void { + const len = filters.length; + for (let f = 0; f < len; f++) { + const filter = filters[f]; + if (filter.includes(',')) { + const ff = filter + .split(',') + .map(t => t.trim()) + .filter(t => t); + const first = ff.shift(); + if (first) { + filters[f] = first; + if (ff.length) { + filters.push(...ff); + } + } + } else if (filter.includes(';')) { + const ff = filter + .split(';') + .map(t => t.trim()) + .filter(t => t); + const first = ff.shift(); + if (first) { + filters[f] = first; + if (ff.length) { + filters.push(...ff); + } + } + } + } +} + +interface ItemsEditorProps { + data: any; + setData: (data: any) => void; + context: RxWidgetInfoCustomComponentContext; +} + +const ItemsEditor = (props: ItemsEditorProps): React.JSX.Element => { + const [open, setOpen] = useState(false); + + let items = props.data.items; + // convert data from "filters" to "items" + if (open && !items && props.data.filters) { + items = props.data.filters + .split(';') + .map((item: string) => ({ label: item.trim(), value: item.trim() })) + .filter((item: Item) => item.value); + } + if (open && typeof items === 'string') { + try { + items = JSON.parse(items); + } catch { + items = []; + } + } + + return ( + <> + + {open ? ( + { + if (newItems) { + const data = JSON.parse(JSON.stringify(props.data)); + data.items = JSON.stringify(newItems); + props.setData(data); + } + setOpen(false); + }} + /> + ) : null} + + ); +}; + +class BasicFilterDropdown extends VisRxWidget { + private editMode: boolean | undefined; + + static getWidgetInfo(): RxWidgetInfo { + return { + id: 'tplFilterDropdown', + visSet: 'basic', + visName: 'filter - dropdown', + visPrev: 'widgets/basic/img/Prev_FilterDropdown.png', + visAttrs: [ + { + name: 'common', + fields: [ + { + name: 'items', + label: 'editor', + type: 'custom', + noBinding: true, + component: ( + _field: RxWidgetInfoAttributesField, + data: WidgetData, + onDataChange: (newData: WidgetData) => void, + props: RxWidgetInfoCustomComponentProperties, + ) => ( + + ), + default: '[]', + }, + { + name: 'type', + label: 'Type', + type: 'select', + options: [ + { + value: 'dropdown', + label: 'basic_filter_type_dropdown', + }, + { + value: 'horizontal_buttons', + label: 'basic_filter_type_horizontal_buttons', + }, + { + value: 'vertical_buttons', + label: 'basic_filter_type_vertical_buttons', + }, + ], + default: 'horizontal_buttons', + }, + { + name: 'widgetTitle', + label: 'name', + hidden: 'data.type !== "dropdown"', + }, + { + name: 'autoFocus', + type: 'checkbox', + hidden: 'data.type !== "dropdown"', + }, + { + name: 'multiple', + label: 'basic_filter_multiple', + type: 'checkbox', + }, + { + name: 'noAllOption', + label: 'basic_no_all_option', + type: 'checkbox', + }, + { + name: 'noFilterText', + label: 'basic_no_filter_text', + type: 'text', + hidden: '!!data.noAllOption', + }, + { + name: 'dropdownVariant', + label: 'jqui_variant', + type: 'select', + noTranslation: true, + options: [ + { + value: 'standard', + label: 'standard', + }, + { + value: 'outlined', + label: 'outlined', + }, + { + value: 'filled', + label: 'filled', + }, + ], + default: 'standard', + hidden: 'data.type !== "dropdown"', + }, + { + name: 'buttonsVariant', + label: 'jqui_variant', + type: 'select', + noTranslation: true, + options: [ + { + value: 'outlined', + label: 'outlined', + }, + { + value: 'contained', + label: 'contained', + }, + { + value: 'text', + label: 'text', + }, + ], + default: 'outlined', + hidden: 'data.type === "dropdown"', + }, + { + name: 'dropdownSmall', + label: 'basic_small', + default: false, + type: 'checkbox', + hidden: 'data.type !== "dropdown"', + }, + ], + }, + ], + // visWidgetLabel: 'value_string', // Label of widget + visDefaultStyle: { + width: 200, + height: 50, + }, + }; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo(): RxWidgetInfo { + return BasicFilterDropdown.getWidgetInfo(); + } + + componentDidMount(): void { + super.componentDidMount(); + // apply default filter + if (!this.props.editMode) { + this.editMode = false; + const items = this.getItems(); + if (items.find((item: Item) => item.default && item.value)) { + const filter: string[] = []; + items.forEach((item: Item) => item.default && filter.push(item.value)); + processFilter(filter); + setTimeout(() => { + const view = this.props.askView('getViewClass'); + view.onCommand('changeFilter', { filter }); + }, 0); + } + } else { + this.editMode = true; + } + } + + onCommand(command: VisWidgetCommand): void { + if (command === 'changeFilter') { + // analyse filter + this.forceUpdate(); + } + super.onCommand(command); + } + + renderDropdown(items: Item[]): React.JSX.Element { + const viewsActiveFilter: string[] = this.props.viewsActiveFilter[this.props.view] || []; + let value; + if (this.state.rxData.multiple) { + value = viewsActiveFilter; + } else { + value = viewsActiveFilter[0] || ''; + } + return ( + + {this.state.rxData.widgetTitle ? {this.state.rxData.widgetTitle} : null} + + + ); + } + + renderButtons(items: Item[]): React.JSX.Element { + const viewsActiveFilter: string[] = this.props.viewsActiveFilter[this.props.view] || []; + return ( + + {this.state.rxData.noAllOption ? null : ( + + )} + {items.map(option => { + let image = option.icon; + if (!image && option.image) { + image = option.image; + if (image.startsWith('_PRJ_NAME/')) { + image = image.replace( + '_PRJ_NAME/', + `../${this.props.context.adapterName}.${this.props.context.instance}/${this.props.context.projectName}/`, + ); + } + } + return ( + + ); + })} + + ); + } + + getItems(): { label: string; value: string }[] { + let items = this.state.data.items; + // convert data from "filters" to "items" + if (!items && this.state.data.filters) { + items = this.state.data.filters + .split(';') + .map((item: string) => ({ label: item.trim(), value: item.trim() })) + .filter((item: Item) => item.value); + } + if (typeof items === 'string') { + try { + items = JSON.parse(items); + } catch { + items = []; + } + } + items = items || []; + return items; + } + + renderWidgetBody(props: RxRenderWidgetProps): React.JSX.Element { + super.renderWidgetBody(props); + if (props.style.width === undefined) { + props.style.width = 200; + } + if (props.style.height === undefined) { + props.style.height = 50; + } + + const items = this.getItems(); + if (this.editMode !== undefined && this.editMode !== this.props.editMode) { + this.editMode = this.props.editMode; + // apply default filter if not in edit mode + if (!this.editMode && items.find((item: Item) => item.default && item.value)) { + const filter: string[] = []; + items.forEach((item: Item) => item.default && filter.push(item.value)); + processFilter(filter); + setTimeout(() => { + const view = this.props.askView('getViewClass'); + view.onCommand('changeFilter', { filter }); + }, 0); + } + } + + const type = this.state.rxData.type || 'dropdown'; + if (type === 'horizontal_buttons' || type === 'vertical_buttons') { + return this.renderButtons(items); + } + + return this.renderDropdown(items); + } +} + +export default BasicFilterDropdown; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/Basic/BasicHtml.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/Basic/BasicHtml.tsx index 2d7d6eb2..7d7b5d6a 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/Basic/BasicHtml.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/Basic/BasicHtml.tsx @@ -1,114 +1,114 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 React from 'react'; - -import type { GetRxDataFromWidget, RxRenderWidgetProps, RxWidgetInfo } from '@iobroker/types-vis-2'; -import VisRxWidget from '../../visRxWidget'; -import DangerousHtmlWithScript from '../Utils/DangerousHtmlWithScript'; - -// eslint-disable-next-line no-use-before-define -type RxData = GetRxDataFromWidget; - -class BasicHtml extends VisRxWidget { - interval: ReturnType | null = null; - - static getWidgetInfo(): RxWidgetInfo { - return { - id: 'tplHtml', - visSet: 'basic', - visName: 'HTML', - visPrev: 'widgets/basic/img/Prev_HTML.png', - visAttrs: [ - { - name: 'common', - fields: [ - { - name: 'html', - type: 'html', - }, - { - name: 'refreshInterval', - type: 'slider', - min: 0, - max: 180000, - step: 100, - }, - ], - }, - ], - // visWidgetLabel: 'value_string', // Label of widget - visDefaultStyle: { - width: 200, - height: 130, - }, - } as const; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo(): RxWidgetInfo { - return BasicHtml.getWidgetInfo(); - } - - componentWillUnmount(): void { - if (this.interval) { - clearInterval(this.interval); - this.interval = null; - } - super.componentWillUnmount(); - } - - componentDidMount(): void { - super.componentDidMount(); - if (parseInt(this.state.rxData.refreshInterval as unknown as string, 10)) { - this.interval = setInterval( - () => this.forceUpdate(), - parseInt(this.state.rxData.refreshInterval as unknown as string, 10), - ); - } - } - - onRxDataChanged(prevRxData: typeof this.state.rxData): void { - super.onRxDataChanged(prevRxData); - if (this.interval) { - clearInterval(this.interval); - this.interval = null; - } - if (parseInt(this.state.rxData.refreshInterval as unknown as string, 10)) { - this.interval = setInterval( - () => this.forceUpdate(), - parseInt(this.state.rxData.refreshInterval as unknown as string, 10), - ); - } - } - - /** - * Renders the widget - */ - renderWidgetBody(props: RxRenderWidgetProps): React.JSX.Element { - super.renderWidgetBody(props); - - return ( - - ); - } -} - -export default BasicHtml; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 React from 'react'; + +import type { GetRxDataFromWidget, RxRenderWidgetProps, RxWidgetInfo } from '@iobroker/types-vis-2'; +import VisRxWidget from '../../visRxWidget'; +import DangerousHtmlWithScript from '../Utils/DangerousHtmlWithScript'; + +// eslint-disable-next-line no-use-before-define +type RxData = GetRxDataFromWidget; + +class BasicHtml extends VisRxWidget { + interval: ReturnType | null = null; + + static getWidgetInfo(): RxWidgetInfo { + return { + id: 'tplHtml', + visSet: 'basic', + visName: 'HTML', + visPrev: 'widgets/basic/img/Prev_HTML.png', + visAttrs: [ + { + name: 'common', + fields: [ + { + name: 'html', + type: 'html', + }, + { + name: 'refreshInterval', + type: 'slider', + min: 0, + max: 180000, + step: 100, + }, + ], + }, + ], + // visWidgetLabel: 'value_string', // Label of widget + visDefaultStyle: { + width: 200, + height: 130, + }, + } as const; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo(): RxWidgetInfo { + return BasicHtml.getWidgetInfo(); + } + + componentWillUnmount(): void { + if (this.interval) { + clearInterval(this.interval); + this.interval = null; + } + super.componentWillUnmount(); + } + + componentDidMount(): void { + super.componentDidMount(); + if (parseInt(this.state.rxData.refreshInterval as unknown as string, 10)) { + this.interval = setInterval( + () => this.forceUpdate(), + parseInt(this.state.rxData.refreshInterval as unknown as string, 10), + ); + } + } + + onRxDataChanged(prevRxData: typeof this.state.rxData): void { + super.onRxDataChanged(prevRxData); + if (this.interval) { + clearInterval(this.interval); + this.interval = null; + } + if (parseInt(this.state.rxData.refreshInterval as unknown as string, 10)) { + this.interval = setInterval( + () => this.forceUpdate(), + parseInt(this.state.rxData.refreshInterval as unknown as string, 10), + ); + } + } + + /** + * Renders the widget + */ + renderWidgetBody(props: RxRenderWidgetProps): React.JSX.Element { + super.renderWidgetBody(props); + + return ( + + ); + } +} + +export default BasicHtml; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/Basic/BasicHtmlNav.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/Basic/BasicHtmlNav.tsx index 52c9e0bd..77413a8d 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/Basic/BasicHtmlNav.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/Basic/BasicHtmlNav.tsx @@ -1,101 +1,101 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 React from 'react'; - -import type { GetRxDataFromWidget, RxRenderWidgetProps, RxWidgetInfo } from '@iobroker/types-vis-2'; -import VisRxWidget from '@/Vis/visRxWidget'; - -import DangerousHtmlWithScript from '../Utils/DangerousHtmlWithScript'; - -// eslint-disable-next-line no-use-before-define -type RxData = GetRxDataFromWidget; - -class BasicHtmlNav extends VisRxWidget { - static getWidgetInfo(): RxWidgetInfo { - return { - id: 'tplHtmlNav', - visSet: 'basic', - visName: 'HTML navigation', - visPrev: 'widgets/basic/img/Prev_HTMLnavigation.png', - visAttrs: [ - { - name: 'common', - fields: [ - { - name: 'html', - type: 'html', - }, - { - name: 'nav_view', - type: 'views', - }, - { - name: 'sub_view', - label: 'basic_sub_view', - type: 'text', - tooltip: 'sub_view_tooltip', - hidden: '!data.nav_view', - }, - ], - }, - ], - // visWidgetLabel: 'value_string', // Label of widget - visDefaultStyle: { - width: 200, - height: 130, - }, - }; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo(): RxWidgetInfo { - return BasicHtmlNav.getWidgetInfo(); - } - - onNavigate = (): void => { - if (this.state.rxData.nav_view) { - this.props.context.changeView( - (this.state.rxData.nav_view || '').toString(), - this.state.rxData.sub_view ? this.state.rxData.sub_view.toString() : undefined, - ); - } - }; - - /** - * Renders the widget - */ - renderWidgetBody(props: RxRenderWidgetProps): React.JSX.Element { - super.renderWidgetBody(props); - if (props.style.width === undefined) { - props.style.width = 200; - } - if (props.style.height === undefined) { - props.style.height = 130; - } - - return ( - - ); - } -} - -export default BasicHtmlNav; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 React from 'react'; + +import type { GetRxDataFromWidget, RxRenderWidgetProps, RxWidgetInfo } from '@iobroker/types-vis-2'; +import VisRxWidget from '@/Vis/visRxWidget'; + +import DangerousHtmlWithScript from '../Utils/DangerousHtmlWithScript'; + +// eslint-disable-next-line no-use-before-define +type RxData = GetRxDataFromWidget; + +class BasicHtmlNav extends VisRxWidget { + static getWidgetInfo(): RxWidgetInfo { + return { + id: 'tplHtmlNav', + visSet: 'basic', + visName: 'HTML navigation', + visPrev: 'widgets/basic/img/Prev_HTMLnavigation.png', + visAttrs: [ + { + name: 'common', + fields: [ + { + name: 'html', + type: 'html', + }, + { + name: 'nav_view', + type: 'views', + }, + { + name: 'sub_view', + label: 'basic_sub_view', + type: 'text', + tooltip: 'sub_view_tooltip', + hidden: '!data.nav_view', + }, + ], + }, + ], + // visWidgetLabel: 'value_string', // Label of widget + visDefaultStyle: { + width: 200, + height: 130, + }, + }; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo(): RxWidgetInfo { + return BasicHtmlNav.getWidgetInfo(); + } + + onNavigate = (): void => { + if (this.state.rxData.nav_view) { + this.props.context.changeView( + (this.state.rxData.nav_view || '').toString(), + this.state.rxData.sub_view ? this.state.rxData.sub_view.toString() : undefined, + ); + } + }; + + /** + * Renders the widget + */ + renderWidgetBody(props: RxRenderWidgetProps): React.JSX.Element { + super.renderWidgetBody(props); + if (props.style.width === undefined) { + props.style.width = 200; + } + if (props.style.height === undefined) { + props.style.height = 130; + } + + return ( + + ); + } +} + +export default BasicHtmlNav; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/Basic/BasicLink.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/Basic/BasicLink.tsx index 204bfe87..6ffc0d19 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/Basic/BasicLink.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/Basic/BasicLink.tsx @@ -1,107 +1,107 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 React from 'react'; - -import type { RxRenderWidgetProps, RxWidgetInfo } from '@iobroker/types-vis-2'; -import VisRxWidget from '@/Vis/visRxWidget'; - -import DangerousHtmlWithScript from '../Utils/DangerousHtmlWithScript'; - -type RxData = { - html: string; - href: string; - // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents - target: 'auto' | '_blank' | '_self' | '_parent' | '_top' | string; -}; - -class BasicLink extends VisRxWidget { - static getWidgetInfo(): RxWidgetInfo { - return { - id: 'tplLink', - visSet: 'basic', - visName: 'link', - visPrev: 'widgets/basic/img/Prev_tplLink.png', - visAttrs: [ - { - name: 'common', - fields: [ - { - name: 'html', - type: 'html', - }, - { - name: 'href', - type: 'url', - }, - { - name: 'target', - label: 'target', - type: 'auto', - options: ['auto', '_blank', '_self', '_parent', '_top'], - hidden: '!data.href', - }, - ], - }, - ], - // visWidgetLabel: 'value_string', // Label of widget - visDefaultStyle: { - width: 200, - height: 130, - }, - }; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo(): RxWidgetInfo { - return BasicLink.getWidgetInfo(); - } - - /** - * Renders the widget - * - * @return {Element} - */ - renderWidgetBody(props: RxRenderWidgetProps): React.JSX.Element { - super.renderWidgetBody(props); - if (props.style.width === undefined) { - props.style.width = 200; - } - if (props.style.height === undefined) { - props.style.height = 130; - } - - return ( - - - - ); - } -} - -export default BasicLink; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 React from 'react'; + +import type { RxRenderWidgetProps, RxWidgetInfo } from '@iobroker/types-vis-2'; +import VisRxWidget from '@/Vis/visRxWidget'; + +import DangerousHtmlWithScript from '../Utils/DangerousHtmlWithScript'; + +type RxData = { + html: string; + href: string; + // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents + target: 'auto' | '_blank' | '_self' | '_parent' | '_top' | string; +}; + +class BasicLink extends VisRxWidget { + static getWidgetInfo(): RxWidgetInfo { + return { + id: 'tplLink', + visSet: 'basic', + visName: 'link', + visPrev: 'widgets/basic/img/Prev_tplLink.png', + visAttrs: [ + { + name: 'common', + fields: [ + { + name: 'html', + type: 'html', + }, + { + name: 'href', + type: 'url', + }, + { + name: 'target', + label: 'target', + type: 'auto', + options: ['auto', '_blank', '_self', '_parent', '_top'], + hidden: '!data.href', + }, + ], + }, + ], + // visWidgetLabel: 'value_string', // Label of widget + visDefaultStyle: { + width: 200, + height: 130, + }, + }; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo(): RxWidgetInfo { + return BasicLink.getWidgetInfo(); + } + + /** + * Renders the widget + * + * @return {Element} + */ + renderWidgetBody(props: RxRenderWidgetProps): React.JSX.Element { + super.renderWidgetBody(props); + if (props.style.width === undefined) { + props.style.width = 200; + } + if (props.style.height === undefined) { + props.style.height = 130; + } + + return ( + + + + ); + } +} + +export default BasicLink; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/Basic/BasicSvgBool.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/Basic/BasicSvgBool.tsx index 1f86e9ac..11958c44 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/Basic/BasicSvgBool.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/Basic/BasicSvgBool.tsx @@ -1,161 +1,161 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 React from 'react'; - -import type { RxRenderWidgetProps, RxWidgetInfo } from '@iobroker/types-vis-2'; -import VisRxWidget from '../../visRxWidget'; - -// eslint-disable-next-line no-use-before-define -type RxData = { - oid: string; - no_control: boolean; - svg_false: string; - svg_true: string; - svg_opacity: number; -}; - -class BasicSvgBool extends VisRxWidget { - static getWidgetInfo(): RxWidgetInfo { - return { - id: 'tplValueBoolCtrlSvg', - visSet: 'basic', - visName: 'Bool SVG', - visWidgetLabel: 'qui_Bool SVG', - visPrev: 'widgets/basic/img/Prev_ValueBoolCtrlSvg.png', - visAttrs: [ - { - name: 'common', - fields: [ - { - name: 'oid', - type: 'id', - }, - { - name: 'no_control', - type: 'checkbox', - }, - { - name: 'svg_false', - type: 'html', - default: - "", - }, - { - name: 'svg_true', - type: 'html', - default: - "", - }, - { - name: 'svg_opacity', - type: 'slider', - min: 0, - max: 1, - step: 0.05, - }, - ], - }, - ], - // visWidgetLabel: 'value_string', // Label of widget - visDefaultStyle: { - width: 85, - height: 85, - }, - } as const; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo(): RxWidgetInfo { - return BasicSvgBool.getWidgetInfo(); - } - - onSvgClick(): void { - const oid: string = this.state.rxData.oid; - let val = this.state.values[`${this.state.rxData.oid}.val`]; - if (val === null || val === '' || val === undefined || val === false || val === 'false') { - this.props.context.setValue(oid, true); - } else if (val === true || val === 'true') { - this.props.context.setValue(oid, false); - } else { - val = parseFloat(val); - if (val >= 0.5) { - val = 1; - } else { - val = 0; - } - this.props.context.setValue(oid, 1 - val); - } - } - - renderSvg(): React.JSX.Element { - let str = this.state.values[`${this.state.rxData.oid}.val`]; - if (typeof str === 'string') { - str = str.toLowerCase(); - } - const val = parseFloat(str); - let opacity = this.state.rxData.svg_opacity; - if (this.props.editMode) { - if (opacity === undefined || opacity === null || (opacity as unknown as string) === '') { - opacity = 1; - } - - if (parseFloat(opacity as unknown as string) < 0.2) { - opacity = 0.2; - } - } - let svg; - if ( - val === 0 || - str === 'false' || - str === '0' || - str === 'off' || - str === false || - str === null || - str === undefined - ) { - svg = this.state.rxData.svg_false; - } else { - svg = this.state.rxData.svg_true; - } - - return ( - - ); - } - - renderWidgetBody(props: RxRenderWidgetProps): React.JSX.Element { - super.renderWidgetBody(props); - - return ( -
this.onSvgClick()} - className="vis-widget-body" - > - {this.renderSvg()} -
- ); - } -} - -export default BasicSvgBool; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 React from 'react'; + +import type { RxRenderWidgetProps, RxWidgetInfo } from '@iobroker/types-vis-2'; +import VisRxWidget from '../../visRxWidget'; + +// eslint-disable-next-line no-use-before-define +type RxData = { + oid: string; + no_control: boolean; + svg_false: string; + svg_true: string; + svg_opacity: number; +}; + +class BasicSvgBool extends VisRxWidget { + static getWidgetInfo(): RxWidgetInfo { + return { + id: 'tplValueBoolCtrlSvg', + visSet: 'basic', + visName: 'Bool SVG', + visWidgetLabel: 'qui_Bool SVG', + visPrev: 'widgets/basic/img/Prev_ValueBoolCtrlSvg.png', + visAttrs: [ + { + name: 'common', + fields: [ + { + name: 'oid', + type: 'id', + }, + { + name: 'no_control', + type: 'checkbox', + }, + { + name: 'svg_false', + type: 'html', + default: + "", + }, + { + name: 'svg_true', + type: 'html', + default: + "", + }, + { + name: 'svg_opacity', + type: 'slider', + min: 0, + max: 1, + step: 0.05, + }, + ], + }, + ], + // visWidgetLabel: 'value_string', // Label of widget + visDefaultStyle: { + width: 85, + height: 85, + }, + } as const; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo(): RxWidgetInfo { + return BasicSvgBool.getWidgetInfo(); + } + + onSvgClick(): void { + const oid: string = this.state.rxData.oid; + let val = this.state.values[`${this.state.rxData.oid}.val`]; + if (val === null || val === '' || val === undefined || val === false || val === 'false') { + this.props.context.setValue(oid, true); + } else if (val === true || val === 'true') { + this.props.context.setValue(oid, false); + } else { + val = parseFloat(val); + if (val >= 0.5) { + val = 1; + } else { + val = 0; + } + this.props.context.setValue(oid, 1 - val); + } + } + + renderSvg(): React.JSX.Element { + let str = this.state.values[`${this.state.rxData.oid}.val`]; + if (typeof str === 'string') { + str = str.toLowerCase(); + } + const val = parseFloat(str); + let opacity = this.state.rxData.svg_opacity; + if (this.props.editMode) { + if (opacity === undefined || opacity === null || (opacity as unknown as string) === '') { + opacity = 1; + } + + if (parseFloat(opacity as unknown as string) < 0.2) { + opacity = 0.2; + } + } + let svg; + if ( + val === 0 || + str === 'false' || + str === '0' || + str === 'off' || + str === false || + str === null || + str === undefined + ) { + svg = this.state.rxData.svg_false; + } else { + svg = this.state.rxData.svg_true; + } + + return ( + + ); + } + + renderWidgetBody(props: RxRenderWidgetProps): React.JSX.Element { + super.renderWidgetBody(props); + + return ( +
this.onSvgClick()} + className="vis-widget-body" + > + {this.renderSvg()} +
+ ); + } +} + +export default BasicSvgBool; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiBinaryState.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiBinaryState.tsx index faffac1b..4ae65e27 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiBinaryState.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiBinaryState.tsx @@ -1,990 +1,990 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 React, { type CSSProperties } from 'react'; - -import { Button, Fab, FormControlLabel, Tooltip, Checkbox, Switch, ButtonGroup } from '@mui/material'; - -import { I18n, Icon } from '@iobroker/adapter-react-v5'; - -import VisBaseWidget from '../../visBaseWidget'; -import type { - RxRenderWidgetProps, - RxWidgetInfo, - RxWidgetInfoAttributesField, - VisBaseWidgetProps, - Writeable, -} from '@iobroker/types-vis-2'; -import type { VisRxWidgetState } from '../../visRxWidget'; -// eslint-disable-next-line no-duplicate-imports -import VisRxWidget from '../../visRxWidget'; - -type RxData = { - type: 'button' | 'round-button' | 'html' | 'radio' | 'checkbox' | 'image' | 'switch'; - oid: string; - readOnly: boolean; - pushMode: boolean; - invert: boolean; - test: '' | boolean; - click_id: string; - text_false: string; - text_true: string; - color_false: string; - color_true: string; - html_true: string; - html_false: string; - alt_false: string; - alt_true: string; - equal_text_length: boolean; - jquery_style: boolean; - padding: number; - variant: 'contained' | 'outlined' | 'standard' | 'text'; - orientation: 'horizontal' | 'vertical'; - notEqualLength: boolean; - html_prepend: string; - html_append: string; - src_false: string; - icon_false: string; - icon_color_false: string; - invert_icon_false: boolean; - imageHeight_false: number; - src_true: string; - icon_true: string; - icon_color_true: string; - invert_icon_true: boolean; - imageHeight_true: number; - off_text: string; - on_text: string; -}; - -interface JQuiBinaryStateState extends VisRxWidgetState { - isOn: boolean; - height: number; - width: number; -} - -class JQuiBinaryState extends VisRxWidget { - private textsLengthCache: Record = {}; - - constructor(props: VisBaseWidgetProps) { - super(props); - Object.assign(this.state, { - isOn: false, - height: 0, - width: 0, - }); - } - - static getWidgetInfo(): RxWidgetInfo { - return { - id: 'tplJquiBool', - visSet: 'jqui', - visName: 'Html Bool', - visWidgetLabel: 'jqui_binary_control', - visPrev: 'widgets/jqui/img/Prev_BinaryControl.png', - visOrder: 14, - visAttrs: [ - { - name: 'common', - fields: [ - { - name: 'type', - label: 'jqui_type', - type: 'select', - noTranslation: true, - default: 'button', - options: ['button', 'round-button', 'html', 'radio', 'checkbox', 'image', 'switch'], - }, - { - name: 'oid', - type: 'id', - }, - { - name: 'readOnly', - type: 'checkbox', - }, - { - name: 'pushMode', - type: 'checkbox', - label: 'jqui_push_mode', - tooltip: 'jqui_push_mode_tooltip', - hidden: (data: any) => - data.type !== 'button' && data.type !== 'round-button' && data.type !== 'image', - }, - { - name: 'click_id', - type: 'id', - noSubscribe: true, - hidden: (data: any) => !!data.readOnly, - }, - { - name: 'invert', - label: 'jqui_inverted', - type: 'checkbox', - }, - { - name: 'test', - type: 'select', - label: 'jqui_test', - options: [ - { value: '', label: 'none' }, - { value: true, label: 'jqui_true' }, - { value: false, label: 'jqui_false' }, - ], - }, - ], - }, - { - name: 'html', - label: 'jqui_html', - hidden: (data: any) => data.type !== 'html', - fields: [ - { - name: 'html_true', - type: 'html', - label: 'jqui_html_true', - }, - { - name: 'html_false', - type: 'html', - label: 'jqui_html_false', - }, - ], - }, - { - name: 'text', - label: 'group_text', - fields: [ - { - name: 'text_false', - type: 'text', - label: 'text_false', - default: I18n.t('jqui_off').replace('jqui_', ''), - hidden: (data: any) => data.type === 'image' || data.type === 'html', - }, - { - name: 'text_true', - type: 'text', - label: 'text_true', - default: I18n.t('jqui_on').replace('jqui_', ''), - hidden: (data: any) => - data.type === 'image' || data.type === 'html' || data.type === 'round-button', - }, - { - name: 'color_false', - type: 'color', - label: 'color_false', - hidden: (data: any) => data.type === 'image' || data.type === 'html' || !data.text_false, - }, - { - name: 'color_true', - type: 'color', - label: 'color_true', - hidden: (data: any) => data.type === 'image' || data.type === 'html' || !data.text_true, - }, - { - name: 'alt_false', - type: 'text', - label: 'alt_false', - }, - { - name: 'alt_true', - type: 'text', - label: 'alt_true', - }, - { - name: 'equal_text_length', - type: 'checkbox', - label: 'jqui_equal_text_length', - tooltip: 'jqui_equal_text_length_tooltip', - hidden: (data: any) => !data.text_true || !data.text_false, - }, - ], - }, - { - name: 'style', - fields: [ - { - name: 'jquery_style', - label: 'jqui_jquery_style', - type: 'checkbox', - hidden: (data: any) => data.type !== 'button', - }, - { - name: 'padding', - type: 'slider', - min: 0, - max: 100, - default: 5, - // hidden: (data: any) => !data.no_style && !data.jquery_style, - }, - { - name: 'variant', - label: 'jqui_variant', - type: 'select', - noTranslation: true, - options: ['contained', 'outlined', 'standard'], - default: 'contained', - hidden: (data: any) => data.type !== 'button' && data.type !== 'radio', - }, - { - name: 'orientation', - label: 'jqui_orientation', - type: 'select', - noTranslation: true, - options: ['horizontal', 'vertical'], - default: 'horizontal', - hidden: (data: any) => data.type !== 'radio', - }, - { - name: 'notEqualLength', - label: 'jqui_not_equal_length', - type: 'checkbox', - hidden: (data: any) => data.type !== 'radio' || data.orientation !== 'horizontal', - }, - { name: 'html_prepend', type: 'html' }, - { name: 'html_append', type: 'html' }, - ], - }, - { - name: 'icon_false', - label: 'group_icon_false', - fields: [ - { - name: 'src_false', - label: 'jqui_image', - type: 'image', - hidden: (data: any) => data.icon_false, - }, - { - name: 'icon_false', - label: 'jqui_icon', - type: 'icon64', - hidden: (data: any) => data.src_false, - }, - { - name: 'icon_color_false', - label: 'jqui_color', - type: 'color', - hidden: (data: any) => !data.icon_false, - }, - - { - name: 'invert_icon_false', - label: 'jqui_invert_icon', - type: 'checkbox', - hidden: (data: any) => !data.src_false && !data.icon_false, - }, - { - name: 'imageHeight_false', - label: 'jqui_image_height', - type: 'slider', - min: 0, - max: 200, - hidden: (data: any) => !data.src_false && !data.icon_false, - }, - ], - }, - { - name: 'icon_true', - label: 'group_icon_true', - fields: [ - { - name: 'src_true', - label: 'jqui_image', - type: 'image', - hidden: (data: any) => data.icon_true, - }, - { - name: 'icon_true', - label: 'jqui_icon', - type: 'icon64', - hidden: (data: any) => data.src_true, - }, - { - name: 'icon_color_true', - label: 'jqui_color', - type: 'color', - hidden: (data: any) => !data.icon_true && !data.icon_false, - }, - { - name: 'invert_icon_true', - label: 'jqui_invert_icon', - type: 'checkbox', - hidden: (data: any) => !data.src_true && !data.icon_true, - }, - { - name: 'imageHeight_true', - label: 'jqui_image_height', - type: 'slider', - min: 0, - max: 200, - hidden: (data: any) => !data.src_true && !data.icon_true, - }, - ], - }, - ], - } as const; - } - - async componentDidMount(): Promise { - super.componentDidMount(); - - // convert old tplIconStateBool data to JquiBinaryState data - if ( - (this.props.tpl === 'tplIconStateBool' || this.props.tpl === 'tplIconStatePushButton') && - (this.state.data.false_text || this.state.data.true_text) - ) { - const data = JSON.parse(JSON.stringify(this.state.data)); - data.text_false = data.false_text; - data.text_true = data.true_text; - data.src_false = data.false_src; - data.src_true = data.true_src; - data.alt_false = data.false_alt; - data.alt_true = data.true_alt; - data.invert_icon_false = data.invert_icon; - data.invert_icon_true = data.invert_icon; - if (this.props.tpl === 'tplIconStatePushButton') { - data.pushMode = true; - } - - data.false_text = null; - data.true_text = null; - - if (this.props.context.onWidgetsChanged) { - setTimeout( - () => - this.props.context.onWidgetsChanged([ - { - wid: this.props.id, - view: this.props.view, - data, - }, - ]), - 100, - ); - } - } - - if (this.state.rxData.oid && this.state.rxData.oid !== 'nothing_selected') { - try { - const state = await this.props.context.socket.getState(this.state.rxData.oid); - this.onStateUpdated(this.state.rxData.oid, state); - } catch (error) { - console.error(`Cannot get state ${this.state.rxData.oid}: ${error}`); - } - } - } - - static findField( - widgetInfo: RxWidgetInfo, - name: string, - ): Writeable | null { - return VisRxWidget.findField(widgetInfo, name) as unknown as Writeable; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo(): RxWidgetInfo { - return JQuiBinaryState.getWidgetInfo(); - } - - onStateUpdated(id: string, state: ioBroker.State): void { - if (id === this.state.rxData.oid && state) { - const isOn = - state.val === true || - state.val === 'true' || - state.val === 1 || - state.val === '1' || - state.val === 'on' || - state.val === 'ON' || - state.val === 'On'; - if (this.state.isOn !== isOn) { - this.setState({ isOn }); - } - } - } - - getControlOid(): string { - if (this.state.rxData.click_id && this.state.rxData.click_id !== 'nothing_selected') { - return this.state.rxData.click_id; - } - if (this.state.rxData.oid && this.state.rxData.oid !== 'nothing_selected') { - return this.state.rxData.oid; - } - return ''; - } - - onClick(isOn?: boolean): void { - if (this.state.rxData.readOnly || this.props.editMode) { - return; - } - if (isOn !== false && isOn !== true) { - isOn = !this.isOn(); // toggle - } - - const oid = this.getControlOid(); - if (oid) { - this.props.context.setValue(oid, isOn); - } - - this.setState({ isOn }); - } - - // "My" is used to avoid conflicts with parent class - onMyMouseDown(): void { - const oid = this.getControlOid(); - if (oid) { - this.props.context.setValue(oid, !this.state.rxData.invert); - } - - this.setState({ isOn: true }); - } - - // "My" is used to avoid conflicts with parent class - onMyMouseUp(): void { - const oid = this.getControlOid(); - if (oid) { - this.props.context.setValue(oid, !!this.state.rxData.invert); - } - - this.setState({ isOn: false }); - } - - isOn(): boolean { - let value; - if (this.props.editMode && (this.state.rxData.test === true || this.state.rxData.test === false)) { - value = this.state.rxData.test; - } else { - value = this.state.isOn; - } - - if (value === undefined || value === null) { - value = false; - } - if (this.state.rxData.invert) { - value = !value; - } - return value; - } - - renderIcon(isOn: boolean, doNotFallback?: boolean): React.JSX.Element | null { - let icon; - let invert; - let height; - let color; - if (isOn) { - icon = this.state.rxData.src_true || this.state.rxData.icon_true; - if (icon) { - invert = this.state.rxData.invert_icon_true; - height = this.state.rxData.imageHeight_true; - } - color = this.state.rxData.icon_color_true; - } - if (!icon && (!isOn || !doNotFallback)) { - icon = this.state.rxData.src_false || this.state.rxData.icon_false; - if (icon) { - invert = this.state.rxData.invert_icon_false; - height = this.state.rxData.imageHeight_false; - color = color || this.state.rxData.icon_color_false; - } - } - const style: CSSProperties = {}; - if (invert) { - style.filter = 'invert(1)'; - } - if (color) { - style.color = color; - } - if (height) { - style.height = height; - style.width = 'auto'; - } else if (this.state.width && this.state.rxData.type === 'image') { - if (this.state.width < this.state.height) { - style.width = `calc(100% - ${(this.state.rxData.padding || 0) * 2}px)`; - style.height = 'auto'; - style.maxHeight = '100%'; - } else { - style.height = `calc(100% - ${(this.state.rxData.padding || 0) * 2}px)`; - style.width = 'auto'; - style.maxWidth = '100%'; - } - } else if (this.state.rxData.type === 'round-button') { - if (this.state.width < this.state.height) { - style.width = `calc(70% - ${(this.state.rxData.padding || 0) * 2}px)`; - style.height = 'auto'; - style.maxHeight = '100%'; - } else { - style.height = `calc(70% - ${(this.state.rxData.padding || 0) * 2}px)`; - style.width = 'auto'; - style.maxWidth = '100%'; - } - } else if (this.state.width < this.state.height) { - style.width = `calc(100% - ${(this.state.rxData.padding || 0) * 2}px)`; - style.height = 'auto'; - style.maxHeight = '100%'; - } else { - style.height = `calc(100% - ${(this.state.rxData.padding || 0) * 2}px)`; - style.width = 'auto'; - style.maxWidth = '100%'; - } - - if (this.state.rxData.padding) { - style.padding = this.state.rxData.padding; - } - - if (icon) { - if (icon.startsWith('_PRJ_NAME/')) { - icon = icon.replace( - '_PRJ_NAME/', - `../${this.props.context.adapterName}.${this.props.context.instance}/${this.props.context.projectName}/`, - ); - } - - return ( - - ); - } - return null; - } - - getTextWidth(text: string): number { - if (!this.refService.current) { - return (text as unknown as number) * 14; - } - if (this.textsLengthCache[text]) { - return this.textsLengthCache[text]; - } - const el = document.createElement('div'); - el.style.position = 'absolute'; - el.style.visibility = 'hidden'; - el.style.height = 'auto'; - el.style.width = 'auto'; - el.innerHTML = text; - this.refService.current.appendChild(el); - const width = el.clientWidth; - this.refService.current.removeChild(el); - this.textsLengthCache[text] = width; - return width; - } - - renderText(isOn: boolean): { text: React.ReactNode; color?: string } { - let text; - let color; - - if (isOn) { - text = this.state.rxData.text_true !== undefined ? this.state.rxData.text_true : this.state.rxData.on_text; // back compatibility with radio on/off - color = this.state.rxData.color_true; - } - - text = - text || - (this.state.rxData.text_false !== undefined ? this.state.rxData.text_false : this.state.rxData.off_text); // back compatibility with radio on/off - color = color || this.state.rxData.color_false; - - if (this.state.rxData.equal_text_length && this.state.rxData.text_false && this.state.rxData.text_true) { - // get the length of false text - const falseLength = this.getTextWidth(this.state.rxData.text_false); - const trueLength = this.getTextWidth(this.state.rxData.text_true); - const length = Math.max(falseLength, trueLength); - return { - text:
{text}
, - color, - }; - } - - return { text, color }; - } - - renderButton(isOn: boolean, style: CSSProperties): React.JSX.Element { - const icon = this.renderIcon(isOn); - const text = this.renderText(isOn); - - style.color = text.color || undefined; - - // Button - return ( - - ); - } - - renderFab(isOn: boolean, style: CSSProperties): React.JSX.Element { - const icon = this.renderIcon(isOn); - const text = this.renderText(isOn); - - style.zIndex = this.props.editMode ? 0 : undefined; - // Fab - return ( - this.onMyMouseDown() - : undefined - } - onMouseUp={ - this.state.rxData.pushMode && !this.state.rxData.readOnly && !this.props.editMode - ? () => this.onMyMouseUp() - : undefined - } - onClick={ - !this.state.rxData.pushMode && !this.state.rxData.readOnly && !this.props.editMode - ? () => this.onClick() - : undefined - } - > - {icon || text.text} - - ); - } - - renderHtml(isOn: boolean): React.JSX.Element[] { - let html; - if (isOn) { - html = this.state.rxData.html_true; - } - if (!html) { - html = this.state.rxData.html_false; - } - - const icon = this.renderIcon(isOn); - - return [ - this.state.rxData.html_prepend ? ( - - ) : null, - icon, - html ? ( - - ) : null, - this.state.rxData.html_append ? ( - - ) : null, - ]; - } - - renderSwitch(isOn: boolean, style: CSSProperties): React.JSX.Element { - const on = this.state.rxData.text_true !== undefined ? this.state.rxData.text_true : this.state.rxData.on_text; // back compatibility with radio on/off - const textColorOn = this.state.rxData.color_true; - const off = - this.state.rxData.text_false !== undefined ? this.state.rxData.text_false : this.state.rxData.off_text; // back compatibility with radio on/off - const textColorOff = this.state.rxData.color_false; - - const onIcon = this.renderIcon(true, true); - const offIcon = this.renderIcon(false); - - if ((on || onIcon) && (off || offIcon)) { - style.display = 'flex'; - style.alignItems = 'center'; - - return ( -
-
{off}
-
{offIcon}
- this.onClick()} - /> -
{onIcon}
-
{on}
-
- ); - } - if (off || offIcon) { - let text; - if (offIcon && off) { - text = ( -
- {offIcon} - {off} -
- ); - } else { - text = off || offIcon; - } - style.marginLeft = 5; - return ( - this.onClick()} - /> - } - label={text} - /> - ); - } - - return ( -
- this.onClick()} - /> -
- ); - } - - renderCheckbox(isOn: boolean, style: CSSProperties): React.JSX.Element { - let text = isOn - ? this.state.rxData.text_true !== undefined - ? this.state.rxData.text_true - : this.state.rxData.on_text // back compatibility with radio on/off - : this.state.rxData.text_false !== undefined - ? this.state.rxData.text_false - : this.state.rxData.off_text; // back compatibility with radio on/off - if (!text) { - text = - this.state.rxData.text_false !== undefined ? this.state.rxData.text_false : this.state.rxData.off_text; // back compatibility with radio on/off - } - const icon = isOn ? this.renderIcon(true) : this.renderIcon(false); - style.marginLeft = 5; - - if (text || icon) { - return ( - this.onClick()} - /> - } - label={ -
- {icon} - {text} -
- } - /> - ); - } - - return ( - this.onClick()} - /> - ); - } - - renderRadio(isOn: boolean, style: CSSProperties): React.JSX.Element { - const on = this.state.rxData.text_true !== undefined ? this.state.rxData.text_true : this.state.rxData.on_text; // back compatibility with radio on/off - const off = - this.state.rxData.text_false !== undefined ? this.state.rxData.text_false : this.state.rxData.off_text; // back compatibility with radio on/off - const onIcon = this.renderIcon(true); - const offIcon = this.renderIcon(false); - - let variant = this.state.rxData.variant === undefined ? 'contained' : this.state.rxData.variant; - if (variant === 'standard') { - variant = 'text'; - } - - const buttonStyle = - this.state.rxData.orientation === 'vertical' - ? { height: '50%' } - : this.state.rxData.notEqualLength - ? undefined - : { width: '50%' }; - - return ( - - - - - ); - } - - componentDidUpdate(/* prevProps, prevState */): void { - if (!this.refService.current) { - return; - } - if ( - this.state.rxData.type === 'image' || - this.state.rxData.type === 'html' || - (this.state.rxData.type === 'button' && this.state.rxData.jquery_style) - ) { - if (this.state.rxData.type === 'button') { - const el = this.refService.current.getElementsByClassName('vis-widget-body'); - if (el?.length && !(this.refService.current as any)._jQueryDone) { - (this.refService.current as any)._jQueryDone = true; - (window.jQuery as any)(el[0]).button(); - const textEl = el[0].getElementsByClassName('ui-button-text'); - if (textEl?.length) { - (textEl[0] as any).style.display = 'flex'; - (textEl[0] as any).style.alignItems = 'center'; - } - } - } - - if ( - this.refService.current.clientWidth !== this.state.width || - this.refService.current.clientHeight !== this.state.height - ) { - this.setState({ - width: this.refService.current.clientWidth, - height: this.refService.current.clientHeight, - }); - } - } - } - - renderWidgetBody(props: RxRenderWidgetProps): React.JSX.Element { - super.renderWidgetBody(props); - const isOn = this.isOn(); - - const buttonStyle: CSSProperties = {}; - // apply style from the element - Object.keys(this.state.rxStyle).forEach(attr => { - const value = this.state.rxStyle[attr as keyof typeof this.state.rxStyle]; - if (value !== null && value !== undefined && VisRxWidget.POSSIBLE_MUI_STYLES.includes(attr)) { - attr = attr.replace(/(-\w)/g, text => text[1].toUpperCase()); - (buttonStyle as any)[attr] = value; - } - }); - - let type = this.state.rxData.type; - if (!type && this.props.tpl === 'tplJquiRadio') { - type = 'radio'; - } - if (buttonStyle.borderWidth) { - buttonStyle.borderWidth = VisBaseWidget.correctStylePxValue(buttonStyle.borderWidth); - } - if (buttonStyle.fontSize) { - buttonStyle.fontSize = VisBaseWidget.correctStylePxValue(buttonStyle.fontSize); - } - - // extra no rxData here, as it is not possible to set it with bindings - buttonStyle.width = '100%'; - buttonStyle.height = '100%'; - buttonStyle.minWidth = 'unset'; - let content; - const bodyStyle: CSSProperties = { textAlign: 'center' }; - if (type === 'radio') { - content = this.renderRadio(isOn, buttonStyle); - } else if (type === 'html' || (type === 'button' && this.state.rxData.jquery_style)) { - bodyStyle.display = 'flex'; - bodyStyle.flexDirection = this.state.height > this.state.width ? 'column' : 'row'; - bodyStyle.alignItems = 'center'; - bodyStyle.justifyContent = 'center'; - bodyStyle.cursor = !this.state.rxData.readOnly ? 'pointer' : undefined; - - content = this.renderHtml(isOn); - } else if (type === 'switch') { - content = this.renderSwitch(isOn, buttonStyle); - } else if (type === 'checkbox') { - content = this.renderCheckbox(isOn, buttonStyle); - } else if (type === 'image') { - bodyStyle.cursor = !this.state.rxData.readOnly ? 'pointer' : undefined; - content = this.renderIcon(isOn, !!buttonStyle); - } else if (type === 'round-button') { - content = this.renderFab(isOn, buttonStyle); - } else if (!this.state.rxData.jquery_style) { - content = this.renderButton(isOn, buttonStyle); - } - - const result = ( -
this.onClick() : undefined} - > - {content} -
- ); - if (isOn && this.state.rxData.alt_true) { - return ( - - {result} - - ); - } - if (!isOn && this.state.rxData.alt_false) { - return ( - - {result} - - ); - } - return result; - } -} - -export default JQuiBinaryState; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 React, { type CSSProperties } from 'react'; + +import { Button, Fab, FormControlLabel, Tooltip, Checkbox, Switch, ButtonGroup } from '@mui/material'; + +import { I18n, Icon } from '@iobroker/adapter-react-v5'; + +import VisBaseWidget from '../../visBaseWidget'; +import type { + RxRenderWidgetProps, + RxWidgetInfo, + RxWidgetInfoAttributesField, + VisBaseWidgetProps, + Writeable, +} from '@iobroker/types-vis-2'; +import type { VisRxWidgetState } from '../../visRxWidget'; +// eslint-disable-next-line no-duplicate-imports +import VisRxWidget from '../../visRxWidget'; + +type RxData = { + type: 'button' | 'round-button' | 'html' | 'radio' | 'checkbox' | 'image' | 'switch'; + oid: string; + readOnly: boolean; + pushMode: boolean; + invert: boolean; + test: '' | boolean; + click_id: string; + text_false: string; + text_true: string; + color_false: string; + color_true: string; + html_true: string; + html_false: string; + alt_false: string; + alt_true: string; + equal_text_length: boolean; + jquery_style: boolean; + padding: number; + variant: 'contained' | 'outlined' | 'standard' | 'text'; + orientation: 'horizontal' | 'vertical'; + notEqualLength: boolean; + html_prepend: string; + html_append: string; + src_false: string; + icon_false: string; + icon_color_false: string; + invert_icon_false: boolean; + imageHeight_false: number; + src_true: string; + icon_true: string; + icon_color_true: string; + invert_icon_true: boolean; + imageHeight_true: number; + off_text: string; + on_text: string; +}; + +interface JQuiBinaryStateState extends VisRxWidgetState { + isOn: boolean; + height: number; + width: number; +} + +class JQuiBinaryState extends VisRxWidget { + private textsLengthCache: Record = {}; + + constructor(props: VisBaseWidgetProps) { + super(props); + Object.assign(this.state, { + isOn: false, + height: 0, + width: 0, + }); + } + + static getWidgetInfo(): RxWidgetInfo { + return { + id: 'tplJquiBool', + visSet: 'jqui', + visName: 'Html Bool', + visWidgetLabel: 'jqui_binary_control', + visPrev: 'widgets/jqui/img/Prev_BinaryControl.png', + visOrder: 14, + visAttrs: [ + { + name: 'common', + fields: [ + { + name: 'type', + label: 'jqui_type', + type: 'select', + noTranslation: true, + default: 'button', + options: ['button', 'round-button', 'html', 'radio', 'checkbox', 'image', 'switch'], + }, + { + name: 'oid', + type: 'id', + }, + { + name: 'readOnly', + type: 'checkbox', + }, + { + name: 'pushMode', + type: 'checkbox', + label: 'jqui_push_mode', + tooltip: 'jqui_push_mode_tooltip', + hidden: (data: any) => + data.type !== 'button' && data.type !== 'round-button' && data.type !== 'image', + }, + { + name: 'click_id', + type: 'id', + noSubscribe: true, + hidden: (data: any) => !!data.readOnly, + }, + { + name: 'invert', + label: 'jqui_inverted', + type: 'checkbox', + }, + { + name: 'test', + type: 'select', + label: 'jqui_test', + options: [ + { value: '', label: 'none' }, + { value: true, label: 'jqui_true' }, + { value: false, label: 'jqui_false' }, + ], + }, + ], + }, + { + name: 'html', + label: 'jqui_html', + hidden: (data: any) => data.type !== 'html', + fields: [ + { + name: 'html_true', + type: 'html', + label: 'jqui_html_true', + }, + { + name: 'html_false', + type: 'html', + label: 'jqui_html_false', + }, + ], + }, + { + name: 'text', + label: 'group_text', + fields: [ + { + name: 'text_false', + type: 'text', + label: 'text_false', + default: I18n.t('jqui_off').replace('jqui_', ''), + hidden: (data: any) => data.type === 'image' || data.type === 'html', + }, + { + name: 'text_true', + type: 'text', + label: 'text_true', + default: I18n.t('jqui_on').replace('jqui_', ''), + hidden: (data: any) => + data.type === 'image' || data.type === 'html' || data.type === 'round-button', + }, + { + name: 'color_false', + type: 'color', + label: 'color_false', + hidden: (data: any) => data.type === 'image' || data.type === 'html' || !data.text_false, + }, + { + name: 'color_true', + type: 'color', + label: 'color_true', + hidden: (data: any) => data.type === 'image' || data.type === 'html' || !data.text_true, + }, + { + name: 'alt_false', + type: 'text', + label: 'alt_false', + }, + { + name: 'alt_true', + type: 'text', + label: 'alt_true', + }, + { + name: 'equal_text_length', + type: 'checkbox', + label: 'jqui_equal_text_length', + tooltip: 'jqui_equal_text_length_tooltip', + hidden: (data: any) => !data.text_true || !data.text_false, + }, + ], + }, + { + name: 'style', + fields: [ + { + name: 'jquery_style', + label: 'jqui_jquery_style', + type: 'checkbox', + hidden: (data: any) => data.type !== 'button', + }, + { + name: 'padding', + type: 'slider', + min: 0, + max: 100, + default: 5, + // hidden: (data: any) => !data.no_style && !data.jquery_style, + }, + { + name: 'variant', + label: 'jqui_variant', + type: 'select', + noTranslation: true, + options: ['contained', 'outlined', 'standard'], + default: 'contained', + hidden: (data: any) => data.type !== 'button' && data.type !== 'radio', + }, + { + name: 'orientation', + label: 'jqui_orientation', + type: 'select', + noTranslation: true, + options: ['horizontal', 'vertical'], + default: 'horizontal', + hidden: (data: any) => data.type !== 'radio', + }, + { + name: 'notEqualLength', + label: 'jqui_not_equal_length', + type: 'checkbox', + hidden: (data: any) => data.type !== 'radio' || data.orientation !== 'horizontal', + }, + { name: 'html_prepend', type: 'html' }, + { name: 'html_append', type: 'html' }, + ], + }, + { + name: 'icon_false', + label: 'group_icon_false', + fields: [ + { + name: 'src_false', + label: 'jqui_image', + type: 'image', + hidden: (data: any) => data.icon_false, + }, + { + name: 'icon_false', + label: 'jqui_icon', + type: 'icon64', + hidden: (data: any) => data.src_false, + }, + { + name: 'icon_color_false', + label: 'jqui_color', + type: 'color', + hidden: (data: any) => !data.icon_false, + }, + + { + name: 'invert_icon_false', + label: 'jqui_invert_icon', + type: 'checkbox', + hidden: (data: any) => !data.src_false && !data.icon_false, + }, + { + name: 'imageHeight_false', + label: 'jqui_image_height', + type: 'slider', + min: 0, + max: 200, + hidden: (data: any) => !data.src_false && !data.icon_false, + }, + ], + }, + { + name: 'icon_true', + label: 'group_icon_true', + fields: [ + { + name: 'src_true', + label: 'jqui_image', + type: 'image', + hidden: (data: any) => data.icon_true, + }, + { + name: 'icon_true', + label: 'jqui_icon', + type: 'icon64', + hidden: (data: any) => data.src_true, + }, + { + name: 'icon_color_true', + label: 'jqui_color', + type: 'color', + hidden: (data: any) => !data.icon_true && !data.icon_false, + }, + { + name: 'invert_icon_true', + label: 'jqui_invert_icon', + type: 'checkbox', + hidden: (data: any) => !data.src_true && !data.icon_true, + }, + { + name: 'imageHeight_true', + label: 'jqui_image_height', + type: 'slider', + min: 0, + max: 200, + hidden: (data: any) => !data.src_true && !data.icon_true, + }, + ], + }, + ], + } as const; + } + + async componentDidMount(): Promise { + super.componentDidMount(); + + // convert old tplIconStateBool data to JquiBinaryState data + if ( + (this.props.tpl === 'tplIconStateBool' || this.props.tpl === 'tplIconStatePushButton') && + (this.state.data.false_text || this.state.data.true_text) + ) { + const data = JSON.parse(JSON.stringify(this.state.data)); + data.text_false = data.false_text; + data.text_true = data.true_text; + data.src_false = data.false_src; + data.src_true = data.true_src; + data.alt_false = data.false_alt; + data.alt_true = data.true_alt; + data.invert_icon_false = data.invert_icon; + data.invert_icon_true = data.invert_icon; + if (this.props.tpl === 'tplIconStatePushButton') { + data.pushMode = true; + } + + data.false_text = null; + data.true_text = null; + + if (this.props.context.onWidgetsChanged) { + setTimeout( + () => + this.props.context.onWidgetsChanged([ + { + wid: this.props.id, + view: this.props.view, + data, + }, + ]), + 100, + ); + } + } + + if (this.state.rxData.oid && this.state.rxData.oid !== 'nothing_selected') { + try { + const state = await this.props.context.socket.getState(this.state.rxData.oid); + this.onStateUpdated(this.state.rxData.oid, state); + } catch (error) { + console.error(`Cannot get state ${this.state.rxData.oid}: ${error}`); + } + } + } + + static findField( + widgetInfo: RxWidgetInfo, + name: string, + ): Writeable | null { + return VisRxWidget.findField(widgetInfo, name) as unknown as Writeable; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo(): RxWidgetInfo { + return JQuiBinaryState.getWidgetInfo(); + } + + onStateUpdated(id: string, state: ioBroker.State): void { + if (id === this.state.rxData.oid && state) { + const isOn = + state.val === true || + state.val === 'true' || + state.val === 1 || + state.val === '1' || + state.val === 'on' || + state.val === 'ON' || + state.val === 'On'; + if (this.state.isOn !== isOn) { + this.setState({ isOn }); + } + } + } + + getControlOid(): string { + if (this.state.rxData.click_id && this.state.rxData.click_id !== 'nothing_selected') { + return this.state.rxData.click_id; + } + if (this.state.rxData.oid && this.state.rxData.oid !== 'nothing_selected') { + return this.state.rxData.oid; + } + return ''; + } + + onClick(isOn?: boolean): void { + if (this.state.rxData.readOnly || this.props.editMode) { + return; + } + if (isOn !== false && isOn !== true) { + isOn = !this.isOn(); // toggle + } + + const oid = this.getControlOid(); + if (oid) { + this.props.context.setValue(oid, isOn); + } + + this.setState({ isOn }); + } + + // "My" is used to avoid conflicts with parent class + onMyMouseDown(): void { + const oid = this.getControlOid(); + if (oid) { + this.props.context.setValue(oid, !this.state.rxData.invert); + } + + this.setState({ isOn: true }); + } + + // "My" is used to avoid conflicts with parent class + onMyMouseUp(): void { + const oid = this.getControlOid(); + if (oid) { + this.props.context.setValue(oid, !!this.state.rxData.invert); + } + + this.setState({ isOn: false }); + } + + isOn(): boolean { + let value; + if (this.props.editMode && (this.state.rxData.test === true || this.state.rxData.test === false)) { + value = this.state.rxData.test; + } else { + value = this.state.isOn; + } + + if (value === undefined || value === null) { + value = false; + } + if (this.state.rxData.invert) { + value = !value; + } + return value; + } + + renderIcon(isOn: boolean, doNotFallback?: boolean): React.JSX.Element | null { + let icon; + let invert; + let height; + let color; + if (isOn) { + icon = this.state.rxData.src_true || this.state.rxData.icon_true; + if (icon) { + invert = this.state.rxData.invert_icon_true; + height = this.state.rxData.imageHeight_true; + } + color = this.state.rxData.icon_color_true; + } + if (!icon && (!isOn || !doNotFallback)) { + icon = this.state.rxData.src_false || this.state.rxData.icon_false; + if (icon) { + invert = this.state.rxData.invert_icon_false; + height = this.state.rxData.imageHeight_false; + color = color || this.state.rxData.icon_color_false; + } + } + const style: CSSProperties = {}; + if (invert) { + style.filter = 'invert(1)'; + } + if (color) { + style.color = color; + } + if (height) { + style.height = height; + style.width = 'auto'; + } else if (this.state.width && this.state.rxData.type === 'image') { + if (this.state.width < this.state.height) { + style.width = `calc(100% - ${(this.state.rxData.padding || 0) * 2}px)`; + style.height = 'auto'; + style.maxHeight = '100%'; + } else { + style.height = `calc(100% - ${(this.state.rxData.padding || 0) * 2}px)`; + style.width = 'auto'; + style.maxWidth = '100%'; + } + } else if (this.state.rxData.type === 'round-button') { + if (this.state.width < this.state.height) { + style.width = `calc(70% - ${(this.state.rxData.padding || 0) * 2}px)`; + style.height = 'auto'; + style.maxHeight = '100%'; + } else { + style.height = `calc(70% - ${(this.state.rxData.padding || 0) * 2}px)`; + style.width = 'auto'; + style.maxWidth = '100%'; + } + } else if (this.state.width < this.state.height) { + style.width = `calc(100% - ${(this.state.rxData.padding || 0) * 2}px)`; + style.height = 'auto'; + style.maxHeight = '100%'; + } else { + style.height = `calc(100% - ${(this.state.rxData.padding || 0) * 2}px)`; + style.width = 'auto'; + style.maxWidth = '100%'; + } + + if (this.state.rxData.padding) { + style.padding = this.state.rxData.padding; + } + + if (icon) { + if (icon.startsWith('_PRJ_NAME/')) { + icon = icon.replace( + '_PRJ_NAME/', + `../${this.props.context.adapterName}.${this.props.context.instance}/${this.props.context.projectName}/`, + ); + } + + return ( + + ); + } + return null; + } + + getTextWidth(text: string): number { + if (!this.refService.current) { + return (text as unknown as number) * 14; + } + if (this.textsLengthCache[text]) { + return this.textsLengthCache[text]; + } + const el = document.createElement('div'); + el.style.position = 'absolute'; + el.style.visibility = 'hidden'; + el.style.height = 'auto'; + el.style.width = 'auto'; + el.innerHTML = text; + this.refService.current.appendChild(el); + const width = el.clientWidth; + this.refService.current.removeChild(el); + this.textsLengthCache[text] = width; + return width; + } + + renderText(isOn: boolean): { text: React.ReactNode; color?: string } { + let text; + let color; + + if (isOn) { + text = this.state.rxData.text_true !== undefined ? this.state.rxData.text_true : this.state.rxData.on_text; // back compatibility with radio on/off + color = this.state.rxData.color_true; + } + + text = + text || + (this.state.rxData.text_false !== undefined ? this.state.rxData.text_false : this.state.rxData.off_text); // back compatibility with radio on/off + color = color || this.state.rxData.color_false; + + if (this.state.rxData.equal_text_length && this.state.rxData.text_false && this.state.rxData.text_true) { + // get the length of false text + const falseLength = this.getTextWidth(this.state.rxData.text_false); + const trueLength = this.getTextWidth(this.state.rxData.text_true); + const length = Math.max(falseLength, trueLength); + return { + text:
{text}
, + color, + }; + } + + return { text, color }; + } + + renderButton(isOn: boolean, style: CSSProperties): React.JSX.Element { + const icon = this.renderIcon(isOn); + const text = this.renderText(isOn); + + style.color = text.color || undefined; + + // Button + return ( + + ); + } + + renderFab(isOn: boolean, style: CSSProperties): React.JSX.Element { + const icon = this.renderIcon(isOn); + const text = this.renderText(isOn); + + style.zIndex = this.props.editMode ? 0 : undefined; + // Fab + return ( + this.onMyMouseDown() + : undefined + } + onMouseUp={ + this.state.rxData.pushMode && !this.state.rxData.readOnly && !this.props.editMode + ? () => this.onMyMouseUp() + : undefined + } + onClick={ + !this.state.rxData.pushMode && !this.state.rxData.readOnly && !this.props.editMode + ? () => this.onClick() + : undefined + } + > + {icon || text.text} + + ); + } + + renderHtml(isOn: boolean): React.JSX.Element[] { + let html; + if (isOn) { + html = this.state.rxData.html_true; + } + if (!html) { + html = this.state.rxData.html_false; + } + + const icon = this.renderIcon(isOn); + + return [ + this.state.rxData.html_prepend ? ( + + ) : null, + icon, + html ? ( + + ) : null, + this.state.rxData.html_append ? ( + + ) : null, + ]; + } + + renderSwitch(isOn: boolean, style: CSSProperties): React.JSX.Element { + const on = this.state.rxData.text_true !== undefined ? this.state.rxData.text_true : this.state.rxData.on_text; // back compatibility with radio on/off + const textColorOn = this.state.rxData.color_true; + const off = + this.state.rxData.text_false !== undefined ? this.state.rxData.text_false : this.state.rxData.off_text; // back compatibility with radio on/off + const textColorOff = this.state.rxData.color_false; + + const onIcon = this.renderIcon(true, true); + const offIcon = this.renderIcon(false); + + if ((on || onIcon) && (off || offIcon)) { + style.display = 'flex'; + style.alignItems = 'center'; + + return ( +
+
{off}
+
{offIcon}
+ this.onClick()} + /> +
{onIcon}
+
{on}
+
+ ); + } + if (off || offIcon) { + let text; + if (offIcon && off) { + text = ( +
+ {offIcon} + {off} +
+ ); + } else { + text = off || offIcon; + } + style.marginLeft = 5; + return ( + this.onClick()} + /> + } + label={text} + /> + ); + } + + return ( +
+ this.onClick()} + /> +
+ ); + } + + renderCheckbox(isOn: boolean, style: CSSProperties): React.JSX.Element { + let text = isOn + ? this.state.rxData.text_true !== undefined + ? this.state.rxData.text_true + : this.state.rxData.on_text // back compatibility with radio on/off + : this.state.rxData.text_false !== undefined + ? this.state.rxData.text_false + : this.state.rxData.off_text; // back compatibility with radio on/off + if (!text) { + text = + this.state.rxData.text_false !== undefined ? this.state.rxData.text_false : this.state.rxData.off_text; // back compatibility with radio on/off + } + const icon = isOn ? this.renderIcon(true) : this.renderIcon(false); + style.marginLeft = 5; + + if (text || icon) { + return ( + this.onClick()} + /> + } + label={ +
+ {icon} + {text} +
+ } + /> + ); + } + + return ( + this.onClick()} + /> + ); + } + + renderRadio(isOn: boolean, style: CSSProperties): React.JSX.Element { + const on = this.state.rxData.text_true !== undefined ? this.state.rxData.text_true : this.state.rxData.on_text; // back compatibility with radio on/off + const off = + this.state.rxData.text_false !== undefined ? this.state.rxData.text_false : this.state.rxData.off_text; // back compatibility with radio on/off + const onIcon = this.renderIcon(true); + const offIcon = this.renderIcon(false); + + let variant = this.state.rxData.variant === undefined ? 'contained' : this.state.rxData.variant; + if (variant === 'standard') { + variant = 'text'; + } + + const buttonStyle = + this.state.rxData.orientation === 'vertical' + ? { height: '50%' } + : this.state.rxData.notEqualLength + ? undefined + : { width: '50%' }; + + return ( + + + + + ); + } + + componentDidUpdate(/* prevProps, prevState */): void { + if (!this.refService.current) { + return; + } + if ( + this.state.rxData.type === 'image' || + this.state.rxData.type === 'html' || + (this.state.rxData.type === 'button' && this.state.rxData.jquery_style) + ) { + if (this.state.rxData.type === 'button') { + const el = this.refService.current.getElementsByClassName('vis-widget-body'); + if (el?.length && !(this.refService.current as any)._jQueryDone) { + (this.refService.current as any)._jQueryDone = true; + (window.jQuery as any)(el[0]).button(); + const textEl = el[0].getElementsByClassName('ui-button-text'); + if (textEl?.length) { + (textEl[0] as any).style.display = 'flex'; + (textEl[0] as any).style.alignItems = 'center'; + } + } + } + + if ( + this.refService.current.clientWidth !== this.state.width || + this.refService.current.clientHeight !== this.state.height + ) { + this.setState({ + width: this.refService.current.clientWidth, + height: this.refService.current.clientHeight, + }); + } + } + } + + renderWidgetBody(props: RxRenderWidgetProps): React.JSX.Element { + super.renderWidgetBody(props); + const isOn = this.isOn(); + + const buttonStyle: CSSProperties = {}; + // apply style from the element + Object.keys(this.state.rxStyle).forEach(attr => { + const value = this.state.rxStyle[attr as keyof typeof this.state.rxStyle]; + if (value !== null && value !== undefined && VisRxWidget.POSSIBLE_MUI_STYLES.includes(attr)) { + attr = attr.replace(/(-\w)/g, text => text[1].toUpperCase()); + (buttonStyle as any)[attr] = value; + } + }); + + let type = this.state.rxData.type; + if (!type && this.props.tpl === 'tplJquiRadio') { + type = 'radio'; + } + if (buttonStyle.borderWidth) { + buttonStyle.borderWidth = VisBaseWidget.correctStylePxValue(buttonStyle.borderWidth); + } + if (buttonStyle.fontSize) { + buttonStyle.fontSize = VisBaseWidget.correctStylePxValue(buttonStyle.fontSize); + } + + // extra no rxData here, as it is not possible to set it with bindings + buttonStyle.width = '100%'; + buttonStyle.height = '100%'; + buttonStyle.minWidth = 'unset'; + let content; + const bodyStyle: CSSProperties = { textAlign: 'center' }; + if (type === 'radio') { + content = this.renderRadio(isOn, buttonStyle); + } else if (type === 'html' || (type === 'button' && this.state.rxData.jquery_style)) { + bodyStyle.display = 'flex'; + bodyStyle.flexDirection = this.state.height > this.state.width ? 'column' : 'row'; + bodyStyle.alignItems = 'center'; + bodyStyle.justifyContent = 'center'; + bodyStyle.cursor = !this.state.rxData.readOnly ? 'pointer' : undefined; + + content = this.renderHtml(isOn); + } else if (type === 'switch') { + content = this.renderSwitch(isOn, buttonStyle); + } else if (type === 'checkbox') { + content = this.renderCheckbox(isOn, buttonStyle); + } else if (type === 'image') { + bodyStyle.cursor = !this.state.rxData.readOnly ? 'pointer' : undefined; + content = this.renderIcon(isOn, !!buttonStyle); + } else if (type === 'round-button') { + content = this.renderFab(isOn, buttonStyle); + } else if (!this.state.rxData.jquery_style) { + content = this.renderButton(isOn, buttonStyle); + } + + const result = ( +
this.onClick() : undefined} + > + {content} +
+ ); + if (isOn && this.state.rxData.alt_true) { + return ( + + {result} + + ); + } + if (!isOn && this.state.rxData.alt_false) { + return ( + + {result} + + ); + } + return result; + } +} + +export default JQuiBinaryState; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiButton.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiButton.tsx index 7ad86157..decadfac 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiButton.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiButton.tsx @@ -1,1042 +1,1042 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 type { CSSProperties } from 'react'; -import React from 'react'; - -import { - Button, - Dialog, - DialogContent, - DialogTitle, - IconButton, - Popper, - Paper, - TextField, - DialogActions, -} from '@mui/material'; - -import { Close, Check } from '@mui/icons-material'; - -import { I18n, Icon, Utils, IconCopy } from '@iobroker/adapter-react-v5'; - -import VisBaseWidget from '@/Vis/visBaseWidget'; -import type { - RxRenderWidgetProps, - RxWidgetInfo, - RxWidgetInfoAttributesField, - RxWidgetInfoWriteable, - Writeable, - VisBaseWidgetProps, - VisWidgetCommand, -} from '@iobroker/types-vis-2'; -import { isVarFinite } from '../../../Utils/utils'; -import VisRxWidget, { type VisRxWidgetState } from '../../visRxWidget'; - -// eslint-disable-next-line no-use-before-define -export type JQuiButtonDataProps = { - buttontext: string; - html: string; - Password: string; - nav_view: string; - buttontext_view: boolean; - sub_view: string; - href: string; - url: string; - target: string; - no_style: boolean; - jquery_style: boolean; - padding: number; - variant: string; - color: string; - html_prepend: string; - html_append: string; - visResizable: boolean; - src: string; - icon: string; - invert_icon: boolean; - imageHeight: number; - html_dialog: string; - contains_view: string; - title: string; - modal: boolean; - dialog_width: string; - dialog_height: string; - dialog_class: string; - persistent: boolean; - preload: boolean; - closeOnClick: boolean; - hideCloseButton: boolean; - dialogBackgroundColor: string; - dialogTitleColor: string; - overflowX: string; - overflowY: string; - setId: string; - setValue: string; - dialogName: string; - externalDialog: boolean; - autoclose: number | string | boolean; - text: string; -}; - -export interface JQuiButtonState extends VisRxWidgetState { - width: number; - height: number; - dialogVisible: boolean; - showPassword: boolean; - password: string; - passwordError: boolean; -} - -class JQuiButton< - P extends JQuiButtonDataProps = JQuiButtonDataProps, - S extends JQuiButtonState = JQuiButtonState, -> extends VisRxWidget { - refButton: React.RefObject; - - refDialog: React.RefObject; - - hideTimeout: ReturnType; - - setObjectType: string; - - constructor(props: VisBaseWidgetProps) { - super(props); - (this.state as JQuiButtonState).width = 0; - (this.state as JQuiButtonState).height = 0; - (this.state as JQuiButtonState).dialogVisible = false; - (this.state as JQuiButtonState).showPassword = false; - (this.state as JQuiButtonState).password = ''; - this.refButton = React.createRef(); - this.refDialog = React.createRef(); - } - - static getWidgetInfo(): RxWidgetInfo { - return { - id: 'tplJquiButtonLink', - visSet: 'jqui', - visName: 'Button Link', - visSetLabel: 'jqui_set_label', - visWidgetLabel: 'jqui_button_link', - visPrev: 'widgets/jqui/img/Prev_ButtonLink.png', - visOrder: 1, - visAttrs: [ - { - name: 'common', - fields: [ - { - name: 'buttontext', - type: 'text', - default: 'URL', - hidden: (data: any) => - !!data.html || !!(data.buttontext_view && data.nav_view) || !!data.externalDialog, - }, - { - name: 'html', - type: 'html', - default: '', - tooltip: 'jqui_html_tooltip', - disabled: (data: any) => - !!data.buttontext || !!data.icon || !!data.src || !!data.externalDialog, - }, - { - name: 'Password', - type: 'password', - label: 'password', - tooltip: 'jqui_password_tooltip', - disabled: (data: any) => - !data.nav_view && !data.url && !data.href && !data.html_dialog && !data.contains_view, - }, - ], - }, - { - name: 'view', - label: 'jqui_view_group', - hidden: (data: any) => !!data.url || !!data.href || !!data.html_dialog || !!data.contains_view, - fields: [ - { - name: 'nav_view', - label: 'jqui_nav_view', - type: 'views', - }, - { - name: 'buttontext_view', - label: 'jqui_buttontext_view', - type: 'checkbox', - hidden: (data: any) => !data.nav_view, - }, - { - name: 'sub_view', - label: 'basic_sub_view', - type: 'text', - tooltip: 'sub_view_tooltip', - hidden: (data: any) => !data.nav_view, - }, - ], - }, - { - name: 'URL', - label: 'jqui_url_group', - hidden: (data: any) => !!data.html_dialog || !!data.contains_view || !!data.nav_view, - fields: [ - { - name: 'href', - label: 'jqui_url_in_browser', - type: 'url', - hidden: (data: any) => !!data.url, - tooltip: 'jqui_href_tooltip', - }, - { - name: 'url', - label: 'jqui_url_in_background', - type: 'url', - hidden: (data: any) => !!data.href, - tooltip: 'jqui_url_tooltip', - }, - { - name: 'target', - type: 'auto', - options: ['_blank', '_self', '_parent', '_top'], - hidden: (data: any) => !!data.url || !data.href, - }, - ], - }, - { - name: 'style', - label: 'Style', - hidden: (data: any) => !!data.externalDialog, - fields: [ - { name: 'no_style', type: 'checkbox', hidden: (data: any) => data.jquery_style }, - { - name: 'jquery_style', - label: 'jqui_jquery_style', - type: 'checkbox', - hidden: (data: any) => data.no_style, - }, - { - name: 'padding', - type: 'slider', - min: 0, - max: 100, - default: 5, - // hidden: (data: any) => !data.no_style && !data.jquery_style, - }, - { - name: 'variant', - label: 'jqui_variant', - type: 'select', - noTranslation: true, - options: ['contained', 'outlined', 'standard'], - default: 'contained', - hidden: (data: any) => data.jquery_style || data.no_style, - }, - { - name: 'color', - label: 'jqui_button_color', - type: 'select', - noTranslation: true, - options: ['', 'primary', 'secondary'], - default: '', - hidden: (data: any) => data.jquery_style || data.no_style, - }, - { name: 'html_prepend', type: 'html' }, - { name: 'html_append', type: 'html' }, - { - name: 'visResizable', // reserved name for resizable - label: 'visResizable', - type: 'checkbox', - default: false, - desiredSize: false, // If sizes should be deleted or set to specific value. `false` - delete sizes, or {width: 100, height: 100} - }, - ], - }, - { - name: 'icon', - hidden: (data: any) => !!data.externalDialog || data.jquery_style, - fields: [ - { - name: 'src', - label: 'jqui_image', - type: 'image', - hidden: (data: any) => data.icon, - }, - { - name: 'icon', - label: 'jqui_icon', - type: 'icon64', - hidden: (data: any) => data.src, - }, - { - name: 'invert_icon', - type: 'checkbox', - hidden: (data: any) => !data.icon && !data.src, - }, - { - name: 'imageHeight', - type: 'slider', - min: 0, - max: 200, - default: 100, - hidden: (data: any) => !data.src, - }, - ], - }, - { - name: 'dialog', - hidden: (data: any) => !!data.url || !!data.href || !!data.nav_view, - fields: [ - { - name: 'html_dialog', - type: 'html', - hidden: (data: any) => !!data.contains_view, - }, - { - name: 'contains_view', - type: 'views', - hidden: (data: any) => !!data.html_dialog, - }, - { - name: 'title', - type: 'text', - hidden: (data: any) => !data.html_dialog && !data.contains_view, - }, - { - name: 'autoclose', - type: 'slider', - min: 0, - max: 30000, - step: 100, - hidden: (data: any) => !data.html_dialog && !data.contains_view, - }, - { - name: 'modal', - type: 'checkbox', - default: true, - hidden: (data: any) => !data.html_dialog && !data.contains_view, - }, - { - name: 'dialog_width', - type: 'text', - hidden: (data: any) => !data.html_dialog && !data.contains_view, - }, - { - name: 'dialog_height', - type: 'text', - hidden: (data: any) => !data.html_dialog && !data.contains_view, - }, - { - name: 'dialog_class', - label: 'CSS Class', - type: 'text', - hidden: (data: any) => !data.html_dialog && !data.contains_view, - }, - { - name: 'persistent', - type: 'checkbox', - hidden: (data: any) => !data.html_dialog && !data.contains_view, - }, - { - name: 'preload', - type: 'checkbox', - hidden: (data: any) => !data.html_dialog && !data.contains_view, - }, - { - name: 'closeOnClick', - type: 'checkbox', - hidden: (data: any) => !data.html_dialog && !data.contains_view, - }, - { - name: 'hideCloseButton', - label: 'jqui_hide_close_button', - type: 'checkbox', - hidden: (data: any) => !data.html_dialog && !data.contains_view && !!data.closeOnClick, - }, - { - name: 'dialogBackgroundColor', - label: 'Background color', - type: 'color', - hidden: (data: any) => !data.html_dialog && !data.contains_view, - }, - { - name: 'dialogTitleColor', - type: 'color', - label: 'Text color', - hidden: (data: any) => !data.html_dialog && !data.contains_view, - }, - /* - { - name: 'dialog_top', - type: 'text', - hidden: (data: any) => !data.html_dialog && !data.contains_view, - }, - { - name: 'dialog_left', - type: 'text', - hidden: (data: any) => !data.html_dialog && !data.contains_view, - }, - */ - { - name: 'overflowX', - type: 'select', - noTranslation: true, - options: ['', 'auto', 'hidden', 'visible', 'scroll', 'initial', 'inherit'], - hidden: (data: any) => !data.html_dialog && !data.contains_view, - }, - { - name: 'overflowY', - type: 'select', - noTranslation: true, - options: ['', 'auto', 'hidden', 'visible', 'scroll', 'initial', 'inherit'], - hidden: (data: any) => !data.html_dialog && !data.contains_view, - }, - { - name: 'setId', - type: 'id', - tooltip: 'jqui_dialog_set_id_tooltip', - hidden: (data: any) => !data.html_dialog && !data.contains_view, - }, - { - name: 'setValue', - type: 'text', - hidden: (data: any) => !data.setId || (!data.html_dialog && !data.contains_view), - }, - { - name: 'dialogName', - label: 'jqui_dialog_name', - type: 'text', - hidden: (data: any) => !data.html_dialog && !data.contains_view, - }, - { - name: 'externalDialog', - label: 'jqui_external_dialog', - tooltip: 'jqui_external_dialog_tooltip', - type: 'checkbox', - hidden: (data: any) => !data.html_dialog && !data.contains_view, - }, - ], - }, - ], - } as const; - } - - static findField( - widgetInfo: RxWidgetInfo | RxWidgetInfoWriteable, - name: string, - ): Writeable | null { - return VisRxWidget.findField(widgetInfo as RxWidgetInfo, name) as unknown as Writeable; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo(): RxWidgetInfo { - return JQuiButton.getWidgetInfo(); - } - - async componentDidMount() { - super.componentDidMount(); - await this.componentDidUpdate(); - } - - async componentWillUnmount() { - this.hideTimeout && clearTimeout(this.hideTimeout); - this.hideTimeout = null; - super.componentWillUnmount(); - } - - async componentDidUpdate() { - if (this.refButton.current) { - if (this.state.rxData.jquery_style && !(this.refButton.current as any)._jQueryDone) { - (this.refButton.current as any)._jQueryDone = true; - (window.jQuery as any)(this.refButton.current).button(); - } - if ( - this.refButton.current.clientWidth !== this.state.width || - this.refButton.current.clientHeight !== this.state.height - ) { - this.setState({ - width: this.refButton.current.clientWidth, - height: this.refButton.current.clientHeight, - }); - } - } - // from base class - if ( - this.refService.current && - (this.state.rxData.html_dialog || this.state.rxData.contains_view) && - !(this.refService.current as any)._showDialog - ) { - (this.refService.current as any)._showDialog = this.showDialog; - } - if (this.refService.current) { - const dialogName = this.refService.current.dataset.dialogName; - if ((dialogName || '') !== (this.state.rxData.dialogName || '')) { - if (!this.state.rxData.dialogName) { - delete this.refService.current.dataset.dialogName; - } else { - this.refService.current.dataset.dialogName = this.state.rxData.dialogName; - } - } - } - } - - showDialog = (show: boolean) => { - this.setState({ dialogVisible: show }); - - // Auto-close - let timeout = this.state.rxData.autoclose; - if (timeout === true || timeout === 'true') { - timeout = 10000; - } - if (timeout === null || timeout === undefined || timeout === '') { - return; - } - timeout = parseInt(timeout as string, 10); - if (timeout < 60) { - // maybe this is seconds - timeout *= 1000; - } - timeout = timeout || 1000; - - if (timeout) { - if (show) { - this.hideTimeout = setTimeout(() => { - this.hideTimeout = null; - this.showDialog(false); - }, timeout); - } else if (this.hideTimeout) { - clearTimeout(this.hideTimeout); - this.hideTimeout = null; - } - } - }; - - onPasswordEnter() { - if (this.state.password === this.state.rxData.Password) { - this.setState({ showPassword: false }, () => this.onClick(true)); - } else { - window.alert(I18n.t('Wrong password')); - this.setState({ passwordError: true }, () => { - setTimeout(() => this.setState({ passwordError: false }), 3000); - }); - } - } - - renderPasswordDialog() { - if (!this.state.showPassword) { - return null; - } - return ( - this.setState({ showPassword: false })} - > - {I18n.t('Enter password')} - - this.setState({ password: e.target.value })} - onKeyUp={e => e.key === 'Enter' && this.onPasswordEnter()} - /> - - - - - - - ); - } - - async setObjectWithState(oid: string, value: ioBroker.State['val']) { - if (this.setObjectType === undefined) { - try { - const obj = await this.props.context.socket.getObject(oid); - this.setObjectType = obj?.common?.type || 'string'; - await this.setObjectWithState(oid, value); - } catch (error) { - console.warn(`Object ${oid} not found: ${error}`); - } - return; - } - if (this.setObjectType === 'boolean') { - value = - value === 'true' || value === true || value === '1' || value === 1 || value === 'on' || value === 'ON'; - } else if (this.setObjectType === 'number') { - value = parseFloat(value as string); - } else if (value !== null && value !== undefined) { - value = value.toString(); - } - - await this.props.context.setValue(oid, value); - } - - onCommand(command: VisWidgetCommand) { - super.onCommand(command); - if (command === 'openDialog') { - this.showDialog(true); - return true; - } - if (command === 'closeDialog') { - this.showDialog(false); - return true; - } - return false; - } - - onClick(passwordChecked?: boolean) { - if (this.state.dialogVisible) { - return; - } - - if (!passwordChecked && this.state.rxData.Password) { - if (this.props.editMode) { - window.alert(I18n.t('Ignored in edit mode')); - } else { - this.setState({ showPassword: true, password: '' }); - } - return; - } - - if (this.state.rxData.nav_view) { - this.props.context.changeView(this.state.rxData.nav_view); - } else if (!this.props.editMode && this.state.rxData.href) { - if ( - this.state.rxData.target || - (this.props.tpl === 'tplJquiButtonLinkBlank' && this.state.rxData.target === undefined) - ) { - window.open(this.state.rxData.href, this.state.rxData.target); - } else { - window.location.href = this.state.rxData.href; - } - } else if (this.state.rxData.url) { - this.props.context.socket - .getRawSocket() - .emit('httpGet', this.state.rxData.url, (data: any) => - console.log('httpGet', this.state.rxData.url, data), - ); - } - - if (this.state.rxData.html_dialog || this.state.rxData.contains_view) { - if (this.state.rxData.setId) { - this.setObjectWithState(this.state.rxData.setId, this.state.rxData.setValue).catch(error => - console.warn(`Cannot set state: ${error}`), - ); - } - // show dialog - this.showDialog(true); - } - } - - renderRxDialog(dialogStyle: CSSProperties, content: React.JSX.Element) { - console.log('test'); - if (this.state.rxData.modal) { - console.log('in'); - return ( - { - if (this.state.rxData.closeOnClick) { - this.showDialog(false); - } - }} - > -
- {this.state.rxData.title ? ( - - {this.state.rxData.title} - - ) : null} - {!this.state.rxData.hideCloseButton || !this.state.rxData.closeOnClick ? ( - this.showDialog(false)} - > - - - ) : null} - {content} -
-
- ); - } - - if (!this.state.dialogVisible) { - dialogStyle.display = 'none'; - } - - if (!dialogStyle.minWidth || (dialogStyle.minWidth as number) < 200) { - dialogStyle.minWidth = 200; - } - if (!dialogStyle.minHeight || (dialogStyle.minHeight as number) < 100) { - dialogStyle.minHeight = 100; - } - - dialogStyle.backgroundColor = 'blue'; - const paperStyle = { ...dialogStyle }; - delete paperStyle.top; - delete paperStyle.left; - paperStyle.padding = this.state.rxData.title ? '0 24px 24px 24px' : '26px 24px 24px 24px'; - - return ( - { - if (this.state.rxData.closeOnClick) { - this.showDialog(false); - } - }} - > - - {this.state.rxData.title ? ( - -
{this.state.rxData.title}
-
- ) : null} - this.showDialog(false)} - > - - - {content} -
-
- ); - } - - renderJQueryDialog(dialogStyle: CSSProperties, content: React.JSX.Element) { - return ( -
- {this.state.rxData.preload ? content : null} -
- ); - } - - renderDialog() { - if ( - this.props.editMode || - (!this.state.dialogVisible && !this.state.rxData.persistent && !this.state.rxData.externalDialog) || - (!this.state.rxData.html_dialog && !this.state.rxData.contains_view) - ) { - return null; - } - - // eslint-disable-next-line no-restricted-properties - // const top = isVarFinite(this.state.rxData.dialog_top) ? parseFloat(this.state.rxData.dialog_top) : this.state.rxData.dialog_top; - // eslint-disable-next-line no-restricted-properties - // const left = isVarFinite(this.state.rxData.dialog_left) ? parseFloat(this.state.rxData.dialog_left) : this.state.rxData.dialog_left; - // eslint-disable-next-line no-restricted-properties - const width = isVarFinite(this.state.rxData.dialog_width) - ? parseFloat(this.state.rxData.dialog_width) - : this.state.rxData.dialog_width; - // eslint-disable-next-line no-restricted-properties - const height = isVarFinite(this.state.rxData.dialog_height) - ? parseFloat(this.state.rxData.dialog_height) - : this.state.rxData.dialog_height; - - const dialogStyle: CSSProperties = { - minWidth: width || window.innerWidth - 50, - minHeight: height || window.innerHeight - 50, - // top: top || top === 0 ? top : undefined, - // left: left || left === 0 ? left : undefined, - overflowX: this.state.rxData.overflowX as any, - overflowY: this.state.rxData.overflowY as any, - }; - - let content; - if (this.state.rxData.contains_view) { - content = ( -
- {super.getWidgetView(this.state.rxData.contains_view, undefined)} -
- ); - } else { - content = ( -
- ); - } - - if (this.state.rxData.jquery_style) { - // return this.renderJQueryDialog(dialogStyle, content); - } - - return this.renderRxDialog(dialogStyle, content); - } - - renderWidgetBody(props: RxRenderWidgetProps) { - super.renderWidgetBody(props); - - const iconStyle: CSSProperties = { - marginRight: 4, - filter: this.state.rxData.invert_icon ? 'invert(1)' : undefined, - }; - - if (!this.state.rxData.jquery_style && this.state.rxData.src) { - if (this.state.width > this.state.height) { - iconStyle.height = `${this.state.rxData.imageHeight || 100}%`; - iconStyle.width = 'auto'; - } else { - iconStyle.width = `${this.state.rxData.imageHeight || 100}%`; - iconStyle.height = 'auto'; - } - } - let iconSrc = !this.state.rxData.jquery_style && (this.state.rxData.src || this.state.rxData.icon); - - if (iconSrc && iconSrc.startsWith('_PRJ_NAME/')) { - iconSrc = iconSrc.replace( - '_PRJ_NAME/', - `../${this.props.context.adapterName}.${this.props.context.instance}/${this.props.context.projectName}/`, - ); - } - const icon = iconSrc ? ( - - ) : null; - - const buttonStyle: CSSProperties = { textTransform: 'none' }; - // apply style from the element - Object.keys(this.state.rxStyle).forEach(attr => { - const value = (this.state.rxStyle as any)[attr]; - if (value !== null && value !== undefined && VisRxWidget.POSSIBLE_MUI_STYLES.includes(attr)) { - attr = attr.replace(/(-\w)/g, text => text[1].toUpperCase()); - (buttonStyle as any)[attr] = value; - } - }); - if (buttonStyle.borderWidth) { - buttonStyle.borderWidth = VisBaseWidget.correctStylePxValue(buttonStyle.borderWidth); - } - if (buttonStyle.fontSize) { - buttonStyle.fontSize = VisBaseWidget.correctStylePxValue(buttonStyle.fontSize); - } - - // the following widgets are resizable by default - let visResizable = this.state.data.visResizable; - if (visResizable === undefined || visResizable === null) { - if ( - this.props.tpl === 'tplJquiButtonNav' || - this.props.tpl === 'tplJquiNavPw' || - this.props.tpl === 'tplContainerDialog' || - this.props.tpl === 'tplContainerIconDialog' || - this.props.tpl === 'tplJquiDialog' || - this.props.tpl === 'tplJquiIconDialog' || - this.props.tpl === 'tplIconHttpGet' || - this.props.tpl === 'tplIconLink' || - this.props.tpl === 'tplJquiIconNav' - ) { - visResizable = true; - } - } - - // extra no rxData here, as it is not possible to set it with bindings - if (visResizable) { - buttonStyle.width = '100%'; - buttonStyle.height = '100%'; - } else { - buttonStyle.padding = this.state.rxData.padding; - } - buttonStyle.minWidth = 'unset'; - - let buttonText; - if (this.state.rxData.html) { - // ignore - } else if (this.state.rxData.nav_view && this.state.rxData.buttontext_view) { - buttonText = - this.props.context.views[this.state.rxData.nav_view]?.settings?.navigationTitle || - this.state.rxData.nav_view; - } else if (this.state.rxData.buttontext === undefined) { - buttonText = this.state.rxData.text || ''; // back compatibility - } else { - buttonText = this.state.rxData.buttontext; - } - - let content; - if (this.state.rxData.externalDialog) { - content = this.props.editMode ? ( -
- { - e.stopPropagation(); - e.preventDefault(); - const text = `setState('${this.props.context.adapterName}.${this.props.context.instance}.control.command', '${JSON.stringify({ command: 'dialog', instance: window.localStorage.getItem('visInstance'), data: this.state.rxData.dialogName || this.props.id })}')`; - window.alert(I18n.t('Copied %s', text)); - Utils.copyToClipboard(text); - }} - > - - -
{I18n.t('External dialog')}
-
{this.state.rxData.html_dialog ? 'HTML' : `View: ${this.state.rxData.contains_view}`}
-
{`Name: ${this.state.rxData.dialogName ? `${this.state.rxData.dialogName} (${this.props.id})` : this.props.id}`}
-
{I18n.t('You can open dialog with following script:')}
-
{`setState('${this.props.context.adapterName}.${this.props.context.instance}.control.command', '${JSON.stringify({ command: 'dialog', instance: window.localStorage.getItem('visInstance'), data: this.state.rxData.dialogName || this.props.id })}')`}
-
- ) : null; - } else { - content = [ - this.state.rxData.html_prepend ? ( - - ) : null, - this.state.rxData.html ? ( - - ) : this.state.rxData.no_style || this.state.rxData.jquery_style ? ( - - ) : ( - - ), - this.state.rxData.html_append ? ( - - ) : null, - ]; - } - - return ( -
this.onClick() : undefined} - > - {content} - {this.renderDialog()} - {this.renderPasswordDialog()} -
- ); - } -} - -export default JQuiButton; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 type { CSSProperties } from 'react'; +import React from 'react'; + +import { + Button, + Dialog, + DialogContent, + DialogTitle, + IconButton, + Popper, + Paper, + TextField, + DialogActions, +} from '@mui/material'; + +import { Close, Check } from '@mui/icons-material'; + +import { I18n, Icon, Utils, IconCopy } from '@iobroker/adapter-react-v5'; + +import VisBaseWidget from '@/Vis/visBaseWidget'; +import type { + RxRenderWidgetProps, + RxWidgetInfo, + RxWidgetInfoAttributesField, + RxWidgetInfoWriteable, + Writeable, + VisBaseWidgetProps, + VisWidgetCommand, +} from '@iobroker/types-vis-2'; +import { isVarFinite } from '../../../Utils/utils'; +import VisRxWidget, { type VisRxWidgetState } from '../../visRxWidget'; + +// eslint-disable-next-line no-use-before-define +export type JQuiButtonDataProps = { + buttontext: string; + html: string; + Password: string; + nav_view: string; + buttontext_view: boolean; + sub_view: string; + href: string; + url: string; + target: string; + no_style: boolean; + jquery_style: boolean; + padding: number; + variant: string; + color: string; + html_prepend: string; + html_append: string; + visResizable: boolean; + src: string; + icon: string; + invert_icon: boolean; + imageHeight: number; + html_dialog: string; + contains_view: string; + title: string; + modal: boolean; + dialog_width: string; + dialog_height: string; + dialog_class: string; + persistent: boolean; + preload: boolean; + closeOnClick: boolean; + hideCloseButton: boolean; + dialogBackgroundColor: string; + dialogTitleColor: string; + overflowX: string; + overflowY: string; + setId: string; + setValue: string; + dialogName: string; + externalDialog: boolean; + autoclose: number | string | boolean; + text: string; +}; + +export interface JQuiButtonState extends VisRxWidgetState { + width: number; + height: number; + dialogVisible: boolean; + showPassword: boolean; + password: string; + passwordError: boolean; +} + +class JQuiButton< + P extends JQuiButtonDataProps = JQuiButtonDataProps, + S extends JQuiButtonState = JQuiButtonState, +> extends VisRxWidget { + refButton: React.RefObject; + + refDialog: React.RefObject; + + hideTimeout: ReturnType; + + setObjectType: string; + + constructor(props: VisBaseWidgetProps) { + super(props); + (this.state as JQuiButtonState).width = 0; + (this.state as JQuiButtonState).height = 0; + (this.state as JQuiButtonState).dialogVisible = false; + (this.state as JQuiButtonState).showPassword = false; + (this.state as JQuiButtonState).password = ''; + this.refButton = React.createRef(); + this.refDialog = React.createRef(); + } + + static getWidgetInfo(): RxWidgetInfo { + return { + id: 'tplJquiButtonLink', + visSet: 'jqui', + visName: 'Button Link', + visSetLabel: 'jqui_set_label', + visWidgetLabel: 'jqui_button_link', + visPrev: 'widgets/jqui/img/Prev_ButtonLink.png', + visOrder: 1, + visAttrs: [ + { + name: 'common', + fields: [ + { + name: 'buttontext', + type: 'text', + default: 'URL', + hidden: (data: any) => + !!data.html || !!(data.buttontext_view && data.nav_view) || !!data.externalDialog, + }, + { + name: 'html', + type: 'html', + default: '', + tooltip: 'jqui_html_tooltip', + disabled: (data: any) => + !!data.buttontext || !!data.icon || !!data.src || !!data.externalDialog, + }, + { + name: 'Password', + type: 'password', + label: 'password', + tooltip: 'jqui_password_tooltip', + disabled: (data: any) => + !data.nav_view && !data.url && !data.href && !data.html_dialog && !data.contains_view, + }, + ], + }, + { + name: 'view', + label: 'jqui_view_group', + hidden: (data: any) => !!data.url || !!data.href || !!data.html_dialog || !!data.contains_view, + fields: [ + { + name: 'nav_view', + label: 'jqui_nav_view', + type: 'views', + }, + { + name: 'buttontext_view', + label: 'jqui_buttontext_view', + type: 'checkbox', + hidden: (data: any) => !data.nav_view, + }, + { + name: 'sub_view', + label: 'basic_sub_view', + type: 'text', + tooltip: 'sub_view_tooltip', + hidden: (data: any) => !data.nav_view, + }, + ], + }, + { + name: 'URL', + label: 'jqui_url_group', + hidden: (data: any) => !!data.html_dialog || !!data.contains_view || !!data.nav_view, + fields: [ + { + name: 'href', + label: 'jqui_url_in_browser', + type: 'url', + hidden: (data: any) => !!data.url, + tooltip: 'jqui_href_tooltip', + }, + { + name: 'url', + label: 'jqui_url_in_background', + type: 'url', + hidden: (data: any) => !!data.href, + tooltip: 'jqui_url_tooltip', + }, + { + name: 'target', + type: 'auto', + options: ['_blank', '_self', '_parent', '_top'], + hidden: (data: any) => !!data.url || !data.href, + }, + ], + }, + { + name: 'style', + label: 'Style', + hidden: (data: any) => !!data.externalDialog, + fields: [ + { name: 'no_style', type: 'checkbox', hidden: (data: any) => data.jquery_style }, + { + name: 'jquery_style', + label: 'jqui_jquery_style', + type: 'checkbox', + hidden: (data: any) => data.no_style, + }, + { + name: 'padding', + type: 'slider', + min: 0, + max: 100, + default: 5, + // hidden: (data: any) => !data.no_style && !data.jquery_style, + }, + { + name: 'variant', + label: 'jqui_variant', + type: 'select', + noTranslation: true, + options: ['contained', 'outlined', 'standard'], + default: 'contained', + hidden: (data: any) => data.jquery_style || data.no_style, + }, + { + name: 'color', + label: 'jqui_button_color', + type: 'select', + noTranslation: true, + options: ['', 'primary', 'secondary'], + default: '', + hidden: (data: any) => data.jquery_style || data.no_style, + }, + { name: 'html_prepend', type: 'html' }, + { name: 'html_append', type: 'html' }, + { + name: 'visResizable', // reserved name for resizable + label: 'visResizable', + type: 'checkbox', + default: false, + desiredSize: false, // If sizes should be deleted or set to specific value. `false` - delete sizes, or {width: 100, height: 100} + }, + ], + }, + { + name: 'icon', + hidden: (data: any) => !!data.externalDialog || data.jquery_style, + fields: [ + { + name: 'src', + label: 'jqui_image', + type: 'image', + hidden: (data: any) => data.icon, + }, + { + name: 'icon', + label: 'jqui_icon', + type: 'icon64', + hidden: (data: any) => data.src, + }, + { + name: 'invert_icon', + type: 'checkbox', + hidden: (data: any) => !data.icon && !data.src, + }, + { + name: 'imageHeight', + type: 'slider', + min: 0, + max: 200, + default: 100, + hidden: (data: any) => !data.src, + }, + ], + }, + { + name: 'dialog', + hidden: (data: any) => !!data.url || !!data.href || !!data.nav_view, + fields: [ + { + name: 'html_dialog', + type: 'html', + hidden: (data: any) => !!data.contains_view, + }, + { + name: 'contains_view', + type: 'views', + hidden: (data: any) => !!data.html_dialog, + }, + { + name: 'title', + type: 'text', + hidden: (data: any) => !data.html_dialog && !data.contains_view, + }, + { + name: 'autoclose', + type: 'slider', + min: 0, + max: 30000, + step: 100, + hidden: (data: any) => !data.html_dialog && !data.contains_view, + }, + { + name: 'modal', + type: 'checkbox', + default: true, + hidden: (data: any) => !data.html_dialog && !data.contains_view, + }, + { + name: 'dialog_width', + type: 'text', + hidden: (data: any) => !data.html_dialog && !data.contains_view, + }, + { + name: 'dialog_height', + type: 'text', + hidden: (data: any) => !data.html_dialog && !data.contains_view, + }, + { + name: 'dialog_class', + label: 'CSS Class', + type: 'text', + hidden: (data: any) => !data.html_dialog && !data.contains_view, + }, + { + name: 'persistent', + type: 'checkbox', + hidden: (data: any) => !data.html_dialog && !data.contains_view, + }, + { + name: 'preload', + type: 'checkbox', + hidden: (data: any) => !data.html_dialog && !data.contains_view, + }, + { + name: 'closeOnClick', + type: 'checkbox', + hidden: (data: any) => !data.html_dialog && !data.contains_view, + }, + { + name: 'hideCloseButton', + label: 'jqui_hide_close_button', + type: 'checkbox', + hidden: (data: any) => !data.html_dialog && !data.contains_view && !!data.closeOnClick, + }, + { + name: 'dialogBackgroundColor', + label: 'Background color', + type: 'color', + hidden: (data: any) => !data.html_dialog && !data.contains_view, + }, + { + name: 'dialogTitleColor', + type: 'color', + label: 'Text color', + hidden: (data: any) => !data.html_dialog && !data.contains_view, + }, + /* + { + name: 'dialog_top', + type: 'text', + hidden: (data: any) => !data.html_dialog && !data.contains_view, + }, + { + name: 'dialog_left', + type: 'text', + hidden: (data: any) => !data.html_dialog && !data.contains_view, + }, + */ + { + name: 'overflowX', + type: 'select', + noTranslation: true, + options: ['', 'auto', 'hidden', 'visible', 'scroll', 'initial', 'inherit'], + hidden: (data: any) => !data.html_dialog && !data.contains_view, + }, + { + name: 'overflowY', + type: 'select', + noTranslation: true, + options: ['', 'auto', 'hidden', 'visible', 'scroll', 'initial', 'inherit'], + hidden: (data: any) => !data.html_dialog && !data.contains_view, + }, + { + name: 'setId', + type: 'id', + tooltip: 'jqui_dialog_set_id_tooltip', + hidden: (data: any) => !data.html_dialog && !data.contains_view, + }, + { + name: 'setValue', + type: 'text', + hidden: (data: any) => !data.setId || (!data.html_dialog && !data.contains_view), + }, + { + name: 'dialogName', + label: 'jqui_dialog_name', + type: 'text', + hidden: (data: any) => !data.html_dialog && !data.contains_view, + }, + { + name: 'externalDialog', + label: 'jqui_external_dialog', + tooltip: 'jqui_external_dialog_tooltip', + type: 'checkbox', + hidden: (data: any) => !data.html_dialog && !data.contains_view, + }, + ], + }, + ], + } as const; + } + + static findField( + widgetInfo: RxWidgetInfo | RxWidgetInfoWriteable, + name: string, + ): Writeable | null { + return VisRxWidget.findField(widgetInfo as RxWidgetInfo, name) as unknown as Writeable; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo(): RxWidgetInfo { + return JQuiButton.getWidgetInfo(); + } + + async componentDidMount() { + super.componentDidMount(); + await this.componentDidUpdate(); + } + + async componentWillUnmount() { + this.hideTimeout && clearTimeout(this.hideTimeout); + this.hideTimeout = null; + super.componentWillUnmount(); + } + + async componentDidUpdate() { + if (this.refButton.current) { + if (this.state.rxData.jquery_style && !(this.refButton.current as any)._jQueryDone) { + (this.refButton.current as any)._jQueryDone = true; + (window.jQuery as any)(this.refButton.current).button(); + } + if ( + this.refButton.current.clientWidth !== this.state.width || + this.refButton.current.clientHeight !== this.state.height + ) { + this.setState({ + width: this.refButton.current.clientWidth, + height: this.refButton.current.clientHeight, + }); + } + } + // from base class + if ( + this.refService.current && + (this.state.rxData.html_dialog || this.state.rxData.contains_view) && + !(this.refService.current as any)._showDialog + ) { + (this.refService.current as any)._showDialog = this.showDialog; + } + if (this.refService.current) { + const dialogName = this.refService.current.dataset.dialogName; + if ((dialogName || '') !== (this.state.rxData.dialogName || '')) { + if (!this.state.rxData.dialogName) { + delete this.refService.current.dataset.dialogName; + } else { + this.refService.current.dataset.dialogName = this.state.rxData.dialogName; + } + } + } + } + + showDialog = (show: boolean) => { + this.setState({ dialogVisible: show }); + + // Auto-close + let timeout = this.state.rxData.autoclose; + if (timeout === true || timeout === 'true') { + timeout = 10000; + } + if (timeout === null || timeout === undefined || timeout === '') { + return; + } + timeout = parseInt(timeout as string, 10); + if (timeout < 60) { + // maybe this is seconds + timeout *= 1000; + } + timeout = timeout || 1000; + + if (timeout) { + if (show) { + this.hideTimeout = setTimeout(() => { + this.hideTimeout = null; + this.showDialog(false); + }, timeout); + } else if (this.hideTimeout) { + clearTimeout(this.hideTimeout); + this.hideTimeout = null; + } + } + }; + + onPasswordEnter() { + if (this.state.password === this.state.rxData.Password) { + this.setState({ showPassword: false }, () => this.onClick(true)); + } else { + window.alert(I18n.t('Wrong password')); + this.setState({ passwordError: true }, () => { + setTimeout(() => this.setState({ passwordError: false }), 3000); + }); + } + } + + renderPasswordDialog() { + if (!this.state.showPassword) { + return null; + } + return ( + this.setState({ showPassword: false })} + > + {I18n.t('Enter password')} + + this.setState({ password: e.target.value })} + onKeyUp={e => e.key === 'Enter' && this.onPasswordEnter()} + /> + + + + + + + ); + } + + async setObjectWithState(oid: string, value: ioBroker.State['val']) { + if (this.setObjectType === undefined) { + try { + const obj = await this.props.context.socket.getObject(oid); + this.setObjectType = obj?.common?.type || 'string'; + await this.setObjectWithState(oid, value); + } catch (error) { + console.warn(`Object ${oid} not found: ${error}`); + } + return; + } + if (this.setObjectType === 'boolean') { + value = + value === 'true' || value === true || value === '1' || value === 1 || value === 'on' || value === 'ON'; + } else if (this.setObjectType === 'number') { + value = parseFloat(value as string); + } else if (value !== null && value !== undefined) { + value = value.toString(); + } + + await this.props.context.setValue(oid, value); + } + + onCommand(command: VisWidgetCommand) { + super.onCommand(command); + if (command === 'openDialog') { + this.showDialog(true); + return true; + } + if (command === 'closeDialog') { + this.showDialog(false); + return true; + } + return false; + } + + onClick(passwordChecked?: boolean) { + if (this.state.dialogVisible) { + return; + } + + if (!passwordChecked && this.state.rxData.Password) { + if (this.props.editMode) { + window.alert(I18n.t('Ignored in edit mode')); + } else { + this.setState({ showPassword: true, password: '' }); + } + return; + } + + if (this.state.rxData.nav_view) { + this.props.context.changeView(this.state.rxData.nav_view); + } else if (!this.props.editMode && this.state.rxData.href) { + if ( + this.state.rxData.target || + (this.props.tpl === 'tplJquiButtonLinkBlank' && this.state.rxData.target === undefined) + ) { + window.open(this.state.rxData.href, this.state.rxData.target); + } else { + window.location.href = this.state.rxData.href; + } + } else if (this.state.rxData.url) { + this.props.context.socket + .getRawSocket() + .emit('httpGet', this.state.rxData.url, (data: any) => + console.log('httpGet', this.state.rxData.url, data), + ); + } + + if (this.state.rxData.html_dialog || this.state.rxData.contains_view) { + if (this.state.rxData.setId) { + this.setObjectWithState(this.state.rxData.setId, this.state.rxData.setValue).catch(error => + console.warn(`Cannot set state: ${error}`), + ); + } + // show dialog + this.showDialog(true); + } + } + + renderRxDialog(dialogStyle: CSSProperties, content: React.JSX.Element) { + console.log('test'); + if (this.state.rxData.modal) { + console.log('in'); + return ( + { + if (this.state.rxData.closeOnClick) { + this.showDialog(false); + } + }} + > +
+ {this.state.rxData.title ? ( + + {this.state.rxData.title} + + ) : null} + {!this.state.rxData.hideCloseButton || !this.state.rxData.closeOnClick ? ( + this.showDialog(false)} + > + + + ) : null} + {content} +
+
+ ); + } + + if (!this.state.dialogVisible) { + dialogStyle.display = 'none'; + } + + if (!dialogStyle.minWidth || (dialogStyle.minWidth as number) < 200) { + dialogStyle.minWidth = 200; + } + if (!dialogStyle.minHeight || (dialogStyle.minHeight as number) < 100) { + dialogStyle.minHeight = 100; + } + + dialogStyle.backgroundColor = 'blue'; + const paperStyle = { ...dialogStyle }; + delete paperStyle.top; + delete paperStyle.left; + paperStyle.padding = this.state.rxData.title ? '0 24px 24px 24px' : '26px 24px 24px 24px'; + + return ( + { + if (this.state.rxData.closeOnClick) { + this.showDialog(false); + } + }} + > + + {this.state.rxData.title ? ( + +
{this.state.rxData.title}
+
+ ) : null} + this.showDialog(false)} + > + + + {content} +
+
+ ); + } + + renderJQueryDialog(dialogStyle: CSSProperties, content: React.JSX.Element) { + return ( +
+ {this.state.rxData.preload ? content : null} +
+ ); + } + + renderDialog() { + if ( + this.props.editMode || + (!this.state.dialogVisible && !this.state.rxData.persistent && !this.state.rxData.externalDialog) || + (!this.state.rxData.html_dialog && !this.state.rxData.contains_view) + ) { + return null; + } + + // eslint-disable-next-line no-restricted-properties + // const top = isVarFinite(this.state.rxData.dialog_top) ? parseFloat(this.state.rxData.dialog_top) : this.state.rxData.dialog_top; + // eslint-disable-next-line no-restricted-properties + // const left = isVarFinite(this.state.rxData.dialog_left) ? parseFloat(this.state.rxData.dialog_left) : this.state.rxData.dialog_left; + // eslint-disable-next-line no-restricted-properties + const width = isVarFinite(this.state.rxData.dialog_width) + ? parseFloat(this.state.rxData.dialog_width) + : this.state.rxData.dialog_width; + // eslint-disable-next-line no-restricted-properties + const height = isVarFinite(this.state.rxData.dialog_height) + ? parseFloat(this.state.rxData.dialog_height) + : this.state.rxData.dialog_height; + + const dialogStyle: CSSProperties = { + minWidth: width || window.innerWidth - 50, + minHeight: height || window.innerHeight - 50, + // top: top || top === 0 ? top : undefined, + // left: left || left === 0 ? left : undefined, + overflowX: this.state.rxData.overflowX as any, + overflowY: this.state.rxData.overflowY as any, + }; + + let content; + if (this.state.rxData.contains_view) { + content = ( +
+ {super.getWidgetView(this.state.rxData.contains_view, undefined)} +
+ ); + } else { + content = ( +
+ ); + } + + if (this.state.rxData.jquery_style) { + // return this.renderJQueryDialog(dialogStyle, content); + } + + return this.renderRxDialog(dialogStyle, content); + } + + renderWidgetBody(props: RxRenderWidgetProps) { + super.renderWidgetBody(props); + + const iconStyle: CSSProperties = { + marginRight: 4, + filter: this.state.rxData.invert_icon ? 'invert(1)' : undefined, + }; + + if (!this.state.rxData.jquery_style && this.state.rxData.src) { + if (this.state.width > this.state.height) { + iconStyle.height = `${this.state.rxData.imageHeight || 100}%`; + iconStyle.width = 'auto'; + } else { + iconStyle.width = `${this.state.rxData.imageHeight || 100}%`; + iconStyle.height = 'auto'; + } + } + let iconSrc = !this.state.rxData.jquery_style && (this.state.rxData.src || this.state.rxData.icon); + + if (iconSrc && iconSrc.startsWith('_PRJ_NAME/')) { + iconSrc = iconSrc.replace( + '_PRJ_NAME/', + `../${this.props.context.adapterName}.${this.props.context.instance}/${this.props.context.projectName}/`, + ); + } + const icon = iconSrc ? ( + + ) : null; + + const buttonStyle: CSSProperties = { textTransform: 'none' }; + // apply style from the element + Object.keys(this.state.rxStyle).forEach(attr => { + const value = (this.state.rxStyle as any)[attr]; + if (value !== null && value !== undefined && VisRxWidget.POSSIBLE_MUI_STYLES.includes(attr)) { + attr = attr.replace(/(-\w)/g, text => text[1].toUpperCase()); + (buttonStyle as any)[attr] = value; + } + }); + if (buttonStyle.borderWidth) { + buttonStyle.borderWidth = VisBaseWidget.correctStylePxValue(buttonStyle.borderWidth); + } + if (buttonStyle.fontSize) { + buttonStyle.fontSize = VisBaseWidget.correctStylePxValue(buttonStyle.fontSize); + } + + // the following widgets are resizable by default + let visResizable = this.state.data.visResizable; + if (visResizable === undefined || visResizable === null) { + if ( + this.props.tpl === 'tplJquiButtonNav' || + this.props.tpl === 'tplJquiNavPw' || + this.props.tpl === 'tplContainerDialog' || + this.props.tpl === 'tplContainerIconDialog' || + this.props.tpl === 'tplJquiDialog' || + this.props.tpl === 'tplJquiIconDialog' || + this.props.tpl === 'tplIconHttpGet' || + this.props.tpl === 'tplIconLink' || + this.props.tpl === 'tplJquiIconNav' + ) { + visResizable = true; + } + } + + // extra no rxData here, as it is not possible to set it with bindings + if (visResizable) { + buttonStyle.width = '100%'; + buttonStyle.height = '100%'; + } else { + buttonStyle.padding = this.state.rxData.padding; + } + buttonStyle.minWidth = 'unset'; + + let buttonText; + if (this.state.rxData.html) { + // ignore + } else if (this.state.rxData.nav_view && this.state.rxData.buttontext_view) { + buttonText = + this.props.context.views[this.state.rxData.nav_view]?.settings?.navigationTitle || + this.state.rxData.nav_view; + } else if (this.state.rxData.buttontext === undefined) { + buttonText = this.state.rxData.text || ''; // back compatibility + } else { + buttonText = this.state.rxData.buttontext; + } + + let content; + if (this.state.rxData.externalDialog) { + content = this.props.editMode ? ( +
+ { + e.stopPropagation(); + e.preventDefault(); + const text = `setState('${this.props.context.adapterName}.${this.props.context.instance}.control.command', '${JSON.stringify({ command: 'dialog', instance: window.localStorage.getItem('visInstance'), data: this.state.rxData.dialogName || this.props.id })}')`; + window.alert(I18n.t('Copied %s', text)); + Utils.copyToClipboard(text); + }} + > + + +
{I18n.t('External dialog')}
+
{this.state.rxData.html_dialog ? 'HTML' : `View: ${this.state.rxData.contains_view}`}
+
{`Name: ${this.state.rxData.dialogName ? `${this.state.rxData.dialogName} (${this.props.id})` : this.props.id}`}
+
{I18n.t('You can open dialog with following script:')}
+
{`setState('${this.props.context.adapterName}.${this.props.context.instance}.control.command', '${JSON.stringify({ command: 'dialog', instance: window.localStorage.getItem('visInstance'), data: this.state.rxData.dialogName || this.props.id })}')`}
+
+ ) : null; + } else { + content = [ + this.state.rxData.html_prepend ? ( + + ) : null, + this.state.rxData.html ? ( + + ) : this.state.rxData.no_style || this.state.rxData.jquery_style ? ( + + ) : ( + + ), + this.state.rxData.html_append ? ( + + ) : null, + ]; + } + + return ( +
this.onClick() : undefined} + > + {content} + {this.renderDialog()} + {this.renderPasswordDialog()} +
+ ); + } +} + +export default JQuiButton; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiButtonBlank.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiButtonBlank.tsx index bce4ff34..8a330a7e 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiButtonBlank.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiButtonBlank.tsx @@ -1,62 +1,62 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 type { - RxWidgetInfo, - RxWidgetInfoAttributesFieldCheckbox, - RxWidgetInfoAttributesFieldSelect, - RxWidgetInfoAttributesFieldText, - RxWidgetInfoWriteable, -} from '@iobroker/types-vis-2'; -import JQuiButton from './JQuiButton'; - -class JQuiButtonBlank extends JQuiButton { - static getWidgetInfo(): RxWidgetInfo { - const widgetInfo = JQuiButton.getWidgetInfo() as unknown as RxWidgetInfoWriteable; - const newWidgetInfo = { - id: 'tplJquiButtonLinkBlank', - visSet: 'jqui', - visName: 'Button Link', - visWidgetLabel: 'jqui_button_link_blank', - visPrev: 'widgets/jqui/img/Prev_ButtonLinkBlank.png', - visOrder: 2, - visAttrs: widgetInfo.visAttrs, - }; - - // Add note - newWidgetInfo.visAttrs[0].fields.unshift({ - name: '_note', - type: 'help', - text: 'jqui_button_link_blank_note', - }); - const target = JQuiButton.findField(newWidgetInfo, 'target'); - target.default = '_blank'; - - const visResizable = JQuiButton.findField(newWidgetInfo, 'visResizable'); - visResizable.default = false; - - const text = JQuiButton.findField(newWidgetInfo, 'buttontext'); - text.default = 'URL Browser'; - - return newWidgetInfo as RxWidgetInfo; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo(): RxWidgetInfo { - return JQuiButtonBlank.getWidgetInfo(); - } -} - -export default JQuiButtonBlank; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 type { + RxWidgetInfo, + RxWidgetInfoAttributesFieldCheckbox, + RxWidgetInfoAttributesFieldSelect, + RxWidgetInfoAttributesFieldText, + RxWidgetInfoWriteable, +} from '@iobroker/types-vis-2'; +import JQuiButton from './JQuiButton'; + +class JQuiButtonBlank extends JQuiButton { + static getWidgetInfo(): RxWidgetInfo { + const widgetInfo = JQuiButton.getWidgetInfo() as unknown as RxWidgetInfoWriteable; + const newWidgetInfo = { + id: 'tplJquiButtonLinkBlank', + visSet: 'jqui', + visName: 'Button Link', + visWidgetLabel: 'jqui_button_link_blank', + visPrev: 'widgets/jqui/img/Prev_ButtonLinkBlank.png', + visOrder: 2, + visAttrs: widgetInfo.visAttrs, + }; + + // Add note + newWidgetInfo.visAttrs[0].fields.unshift({ + name: '_note', + type: 'help', + text: 'jqui_button_link_blank_note', + }); + const target = JQuiButton.findField(newWidgetInfo, 'target'); + target.default = '_blank'; + + const visResizable = JQuiButton.findField(newWidgetInfo, 'visResizable'); + visResizable.default = false; + + const text = JQuiButton.findField(newWidgetInfo, 'buttontext'); + text.default = 'URL Browser'; + + return newWidgetInfo as RxWidgetInfo; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo(): RxWidgetInfo { + return JQuiButtonBlank.getWidgetInfo(); + } +} + +export default JQuiButtonBlank; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiButtonDialogClose.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiButtonDialogClose.tsx index a12019e4..8e72f237 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiButtonDialogClose.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiButtonDialogClose.tsx @@ -1,428 +1,428 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 React, { type CSSProperties } from 'react'; - -import { Autocomplete, Button, Fab, TextField } from '@mui/material'; - -import { I18n, Icon } from '@iobroker/adapter-react-v5'; - -import VisBaseWidget, { type WidgetStyleState } from '@/Vis/visBaseWidget'; -import type { - AnyWidgetId, - RxRenderWidgetProps, - RxWidgetInfoAttributesField, - RxWidgetInfoCustomComponentProperties, - ViewCommand, - WidgetData, - VisBaseWidgetProps, - RxWidgetInfo, -} from '@iobroker/types-vis-2'; -import VisRxWidget, { type VisRxWidgetState } from '../../visRxWidget'; - -// eslint-disable-next-line no-use-before-define -type RxData = { - dlgName: string; - buttontext: string; - html: string; - no_style: boolean; - jquery_style: boolean; - padding: number; - variant: 'contained' | 'outlined' | 'standard'; - color: '' | 'primary' | 'secondary'; - html_prepend: string; - html_append: string; - visResizable: boolean; - src: string; - icon: string; - invert_icon: boolean; - imageHeight: number; -}; - -interface JQuiButtonDialogCloseState extends VisRxWidgetState { - width: number; - height: number; -} - -class JQuiButtonDialogClose extends VisRxWidget { - constructor(props: VisBaseWidgetProps) { - super(props); - (this.state as JQuiButtonDialogCloseState).width = 0; - (this.state as JQuiButtonDialogCloseState).height = 0; - } - - static getWidgetInfo(): RxWidgetInfo { - return { - id: 'tplJquiButtonDialogClose', - visSet: 'jqui', - visName: 'Button dialog close', - visWidgetLabel: 'jqui_button_dialog_close', - visPrev: 'widgets/jqui/img/Prev_ButtonDialogClose.png', - visOrder: 13, - visAttrs: [ - { - name: 'common', - fields: [ - { - label: 'jqui_dialog_name', - tooltip: 'jqui_dialog_name_tooltip', - type: 'custom', - component: ( - field: RxWidgetInfoAttributesField, - data: WidgetData, - onDataChange: (newData: WidgetData) => void, - options: RxWidgetInfoCustomComponentProperties, - ): React.JSX.Element | React.JSX.Element[] => { - // find all possible dialogs - const names: { label: string; value: string }[] = []; - Object.keys(options.context.views).forEach(id => { - const widgets = options.context.views[id].widgets; - widgets && - Object.keys(widgets).forEach((widget: AnyWidgetId) => { - if ( - widgets[widget].data?.html_dialog || - widgets[widget].data?.contains_view || - widgets[widget].data?.externalDialog - ) { - if (widgets[widget].data.dialogName) { - names.push({ - label: `${widgets[widget].data.dialogName} (${widget})`, - value: widgets[widget].data.dialogName, - }); - } else { - names.push({ label: widget, value: widget }); - } - } - }); - }); - return ( - - freeSolo - options={names} - // variant="standard" - value={data.dlgName || ''} - sx={{ width: '100%' }} - onInputChange={(e, inputValue) => { - if (typeof inputValue === 'object' && inputValue !== null) { - inputValue = (inputValue as { label: string; value: string }).value; - } - onDataChange({ dlgName: inputValue }); - }} - onChange={(e, inputValue) => { - if (typeof inputValue === 'object' && inputValue !== null) { - inputValue = inputValue.value; - } - onDataChange({ dlgName: inputValue }); - }} - getOptionLabel={option => { - if (typeof option === 'string') { - return option; - } - return option.label; - }} - renderInput={params => ( - - )} - /> - ); - }, - }, - { - name: 'buttontext', - type: 'text', - default: I18n.t('jqui_Close').replace('jqui_', ''), - hidden: (data: any) => !!data.html, - }, - { - name: 'html', - type: 'html', - default: '', - tooltip: 'jqui_html_tooltip', - disabled: (data: any) => !!data.buttontext || !!data.icon || !!data.src, - }, - ], - }, - { - name: 'style', - hidden: (data: any) => !!data.externalDialog, - fields: [ - { name: 'no_style', type: 'checkbox', hidden: (data: any) => data.jquery_style }, - { - name: 'jquery_style', - label: 'jqui_jquery_style', - type: 'checkbox', - hidden: (data: any) => data.no_style, - }, - { - name: 'padding', - type: 'slider', - min: 0, - max: 100, - default: 5, - // hidden: (data: any) => !data.no_style && !data.jquery_style, - }, - { - name: 'variant', - label: 'jqui_variant', - type: 'select', - noTranslation: true, - options: ['contained', 'outlined', 'standard'], - default: 'contained', - hidden: (data: any) => data.jquery_style || data.no_style, - }, - { - name: 'color', - label: 'jqui_button_color', - type: 'select', - noTranslation: true, - options: ['', 'primary', 'secondary'], - default: '', - hidden: (data: any) => data.jquery_style || data.no_style, - }, - { name: 'html_prepend', type: 'html' }, - { name: 'html_append', type: 'html' }, - { - name: 'visResizable', // reserved name for resizable - label: 'visResizable', - type: 'checkbox', - default: false, - desiredSize: false, // If sizes should be deleted or set to specific value. `false` - delete sizes, or {width: 100, height: 100} - }, - ], - }, - { - name: 'icon', - hidden: (data: any) => !!data.html, - fields: [ - { - name: 'src', - label: 'jqui_image', - type: 'image', - hidden: (data: any) => data.icon || data.jquery_style, - }, - { - name: 'icon', - label: 'jqui_icon', - type: 'icon64', - default: - 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSJjdXJyZW50Q29sb3IiIGQ9Ik0xOSA2LjQxTDE3LjU5IDVMMTIgMTAuNTlMNi40MSA1TDUgNi40MUwxMC41OSAxMkw1IDE3LjU5TDYuNDEgMTlMMTIgMTMuNDFMMTcuNTkgMTlMMTkgMTcuNTlMMTMuNDEgMTJ6Ii8+PC9zdmc+', - hidden: (data: any) => data.src || data.jquery_style, - }, - { - name: 'invert_icon', - type: 'checkbox', - hidden: (data: any) => (!data.icon || !data.image) && data.jquery_style, - }, - { - name: 'imageHeight', - type: 'slider', - min: 0, - max: 200, - default: 100, - hidden: (data: any) => !data.src || data.jquery_style, - }, - ], - }, - ], - } as const; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo(): RxWidgetInfo { - return JQuiButtonDialogClose.getWidgetInfo(); - } - - onClick(): void { - let dlgName: AnyWidgetId = this.state.rxData.dlgName as AnyWidgetId; - if (!dlgName) { - // go through all widgets and find the one with dialog content as this view - const views = Object.keys(this.props.context.views); - for (let i = 0; i < views.length; i++) { - const widgets = this.props.context.views[views[i]].widgets; - if (widgets) { - const wids: AnyWidgetId[] = Object.keys(widgets) as AnyWidgetId[]; - for (let j = 0; j < wids.length; j++) { - if (widgets[wids[j]].data?.contains_view === this.props.view) { - dlgName = wids[j]; - break; - } - } - } - if (dlgName) { - break; - } - } - } - if (dlgName) { - const el = - window.document.getElementById(dlgName) || - window.document.querySelector(`[data-dialog-name="${dlgName}"]`); - - const viewName = Object.keys(this.props.context.views).find( - view => this.props.context.views[view].widgets[dlgName], - ); - this.props.context.onCommand( - 'closeDialog', - viewName as ViewCommand, - dlgName as unknown as Record, - ); - - if ((el as any)?._showDialog) { - (el as any)._showDialog(false); - } else { - // noinspection JSJQueryEfficiency - (window.jQuery as any)(`#${dlgName}_dialog`).dialog('close'); - } - } else { - window.alert('Dialog not found'); - } - } - - renderWidgetBody(props: RxRenderWidgetProps): React.JSX.Element { - super.renderWidgetBody(props); - - const iconStyle: CSSProperties = { - filter: this.state.rxData.invert_icon ? 'invert(1)' : undefined, - }; - - if (!this.state.rxData.jquery_style && this.state.rxData.src) { - if (this.state.width > this.state.height) { - iconStyle.height = `${this.state.rxData.imageHeight || 100}%`; - iconStyle.width = 'auto'; - } else { - iconStyle.width = `${this.state.rxData.imageHeight || 100}%`; - iconStyle.height = 'auto'; - } - } - let iconSrc = !this.state.rxData.jquery_style && (this.state.rxData.src || this.state.rxData.icon); - - if (iconSrc && iconSrc.startsWith('_PRJ_NAME/')) { - iconSrc = iconSrc.replace( - '_PRJ_NAME/', - `../${this.props.context.adapterName}.${this.props.context.instance}/${this.props.context.projectName}/`, - ); - } - - const icon = iconSrc ? ( - - ) : null; - - const buttonStyle: CSSProperties = {}; - // apply style from the element - Object.keys(this.state.rxStyle).forEach((attr: keyof WidgetStyleState) => { - const value = this.state.rxStyle[attr]; - if (value !== null && value !== undefined && VisRxWidget.POSSIBLE_MUI_STYLES.includes(attr)) { - (attr as string) = attr.replace(/(-\w)/g, text => text[1].toUpperCase()); - (buttonStyle as any)[attr] = value; - } - }); - buttonStyle.minWidth = 'unset'; - if (buttonStyle.borderWidth) { - buttonStyle.borderWidth = VisBaseWidget.correctStylePxValue(buttonStyle.borderWidth); - } - if (buttonStyle.fontSize) { - buttonStyle.fontSize = VisBaseWidget.correctStylePxValue(buttonStyle.fontSize); - } - - // extra no rxData here, as it is not possible to set it with bindings - if (this.state.data.visResizable) { - buttonStyle.width = '100%'; - buttonStyle.height = '100%'; - } else { - buttonStyle.padding = this.state.rxData.padding; - } - - let buttonText; - if (this.state.rxData.html) { - // ignore - } else { - buttonText = this.state.rxData.buttontext; - } - - const content = [ - this.state.rxData.html_prepend ? ( - - ) : null, - this.state.rxData.html ? ( - - ) : this.state.rxData.no_style || this.state.rxData.jquery_style ? ( - - ) : !buttonText ? ( - this.onClick()} - size="small" - > - {icon} - - ) : ( - - ), - this.state.rxData.html_append ? ( - - ) : null, - ]; - - return ( -
this.onClick() : undefined} - > - {content} -
- ); - } -} - -export default JQuiButtonDialogClose; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 React, { type CSSProperties } from 'react'; + +import { Autocomplete, Button, Fab, TextField } from '@mui/material'; + +import { I18n, Icon } from '@iobroker/adapter-react-v5'; + +import VisBaseWidget, { type WidgetStyleState } from '@/Vis/visBaseWidget'; +import type { + AnyWidgetId, + RxRenderWidgetProps, + RxWidgetInfoAttributesField, + RxWidgetInfoCustomComponentProperties, + ViewCommand, + WidgetData, + VisBaseWidgetProps, + RxWidgetInfo, +} from '@iobroker/types-vis-2'; +import VisRxWidget, { type VisRxWidgetState } from '../../visRxWidget'; + +// eslint-disable-next-line no-use-before-define +type RxData = { + dlgName: string; + buttontext: string; + html: string; + no_style: boolean; + jquery_style: boolean; + padding: number; + variant: 'contained' | 'outlined' | 'standard'; + color: '' | 'primary' | 'secondary'; + html_prepend: string; + html_append: string; + visResizable: boolean; + src: string; + icon: string; + invert_icon: boolean; + imageHeight: number; +}; + +interface JQuiButtonDialogCloseState extends VisRxWidgetState { + width: number; + height: number; +} + +class JQuiButtonDialogClose extends VisRxWidget { + constructor(props: VisBaseWidgetProps) { + super(props); + (this.state as JQuiButtonDialogCloseState).width = 0; + (this.state as JQuiButtonDialogCloseState).height = 0; + } + + static getWidgetInfo(): RxWidgetInfo { + return { + id: 'tplJquiButtonDialogClose', + visSet: 'jqui', + visName: 'Button dialog close', + visWidgetLabel: 'jqui_button_dialog_close', + visPrev: 'widgets/jqui/img/Prev_ButtonDialogClose.png', + visOrder: 13, + visAttrs: [ + { + name: 'common', + fields: [ + { + label: 'jqui_dialog_name', + tooltip: 'jqui_dialog_name_tooltip', + type: 'custom', + component: ( + field: RxWidgetInfoAttributesField, + data: WidgetData, + onDataChange: (newData: WidgetData) => void, + options: RxWidgetInfoCustomComponentProperties, + ): React.JSX.Element | React.JSX.Element[] => { + // find all possible dialogs + const names: { label: string; value: string }[] = []; + Object.keys(options.context.views).forEach(id => { + const widgets = options.context.views[id].widgets; + widgets && + Object.keys(widgets).forEach((widget: AnyWidgetId) => { + if ( + widgets[widget].data?.html_dialog || + widgets[widget].data?.contains_view || + widgets[widget].data?.externalDialog + ) { + if (widgets[widget].data.dialogName) { + names.push({ + label: `${widgets[widget].data.dialogName} (${widget})`, + value: widgets[widget].data.dialogName, + }); + } else { + names.push({ label: widget, value: widget }); + } + } + }); + }); + return ( + + freeSolo + options={names} + // variant="standard" + value={data.dlgName || ''} + sx={{ width: '100%' }} + onInputChange={(e, inputValue) => { + if (typeof inputValue === 'object' && inputValue !== null) { + inputValue = (inputValue as { label: string; value: string }).value; + } + onDataChange({ dlgName: inputValue }); + }} + onChange={(e, inputValue) => { + if (typeof inputValue === 'object' && inputValue !== null) { + inputValue = inputValue.value; + } + onDataChange({ dlgName: inputValue }); + }} + getOptionLabel={option => { + if (typeof option === 'string') { + return option; + } + return option.label; + }} + renderInput={params => ( + + )} + /> + ); + }, + }, + { + name: 'buttontext', + type: 'text', + default: I18n.t('jqui_Close').replace('jqui_', ''), + hidden: (data: any) => !!data.html, + }, + { + name: 'html', + type: 'html', + default: '', + tooltip: 'jqui_html_tooltip', + disabled: (data: any) => !!data.buttontext || !!data.icon || !!data.src, + }, + ], + }, + { + name: 'style', + hidden: (data: any) => !!data.externalDialog, + fields: [ + { name: 'no_style', type: 'checkbox', hidden: (data: any) => data.jquery_style }, + { + name: 'jquery_style', + label: 'jqui_jquery_style', + type: 'checkbox', + hidden: (data: any) => data.no_style, + }, + { + name: 'padding', + type: 'slider', + min: 0, + max: 100, + default: 5, + // hidden: (data: any) => !data.no_style && !data.jquery_style, + }, + { + name: 'variant', + label: 'jqui_variant', + type: 'select', + noTranslation: true, + options: ['contained', 'outlined', 'standard'], + default: 'contained', + hidden: (data: any) => data.jquery_style || data.no_style, + }, + { + name: 'color', + label: 'jqui_button_color', + type: 'select', + noTranslation: true, + options: ['', 'primary', 'secondary'], + default: '', + hidden: (data: any) => data.jquery_style || data.no_style, + }, + { name: 'html_prepend', type: 'html' }, + { name: 'html_append', type: 'html' }, + { + name: 'visResizable', // reserved name for resizable + label: 'visResizable', + type: 'checkbox', + default: false, + desiredSize: false, // If sizes should be deleted or set to specific value. `false` - delete sizes, or {width: 100, height: 100} + }, + ], + }, + { + name: 'icon', + hidden: (data: any) => !!data.html, + fields: [ + { + name: 'src', + label: 'jqui_image', + type: 'image', + hidden: (data: any) => data.icon || data.jquery_style, + }, + { + name: 'icon', + label: 'jqui_icon', + type: 'icon64', + default: + 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSJjdXJyZW50Q29sb3IiIGQ9Ik0xOSA2LjQxTDE3LjU5IDVMMTIgMTAuNTlMNi40MSA1TDUgNi40MUwxMC41OSAxMkw1IDE3LjU5TDYuNDEgMTlMMTIgMTMuNDFMMTcuNTkgMTlMMTkgMTcuNTlMMTMuNDEgMTJ6Ii8+PC9zdmc+', + hidden: (data: any) => data.src || data.jquery_style, + }, + { + name: 'invert_icon', + type: 'checkbox', + hidden: (data: any) => (!data.icon || !data.image) && data.jquery_style, + }, + { + name: 'imageHeight', + type: 'slider', + min: 0, + max: 200, + default: 100, + hidden: (data: any) => !data.src || data.jquery_style, + }, + ], + }, + ], + } as const; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo(): RxWidgetInfo { + return JQuiButtonDialogClose.getWidgetInfo(); + } + + onClick(): void { + let dlgName: AnyWidgetId = this.state.rxData.dlgName as AnyWidgetId; + if (!dlgName) { + // go through all widgets and find the one with dialog content as this view + const views = Object.keys(this.props.context.views); + for (let i = 0; i < views.length; i++) { + const widgets = this.props.context.views[views[i]].widgets; + if (widgets) { + const wids: AnyWidgetId[] = Object.keys(widgets) as AnyWidgetId[]; + for (let j = 0; j < wids.length; j++) { + if (widgets[wids[j]].data?.contains_view === this.props.view) { + dlgName = wids[j]; + break; + } + } + } + if (dlgName) { + break; + } + } + } + if (dlgName) { + const el = + window.document.getElementById(dlgName) || + window.document.querySelector(`[data-dialog-name="${dlgName}"]`); + + const viewName = Object.keys(this.props.context.views).find( + view => this.props.context.views[view].widgets[dlgName], + ); + this.props.context.onCommand( + 'closeDialog', + viewName as ViewCommand, + dlgName as unknown as Record, + ); + + if ((el as any)?._showDialog) { + (el as any)._showDialog(false); + } else { + // noinspection JSJQueryEfficiency + (window.jQuery as any)(`#${dlgName}_dialog`).dialog('close'); + } + } else { + window.alert('Dialog not found'); + } + } + + renderWidgetBody(props: RxRenderWidgetProps): React.JSX.Element { + super.renderWidgetBody(props); + + const iconStyle: CSSProperties = { + filter: this.state.rxData.invert_icon ? 'invert(1)' : undefined, + }; + + if (!this.state.rxData.jquery_style && this.state.rxData.src) { + if (this.state.width > this.state.height) { + iconStyle.height = `${this.state.rxData.imageHeight || 100}%`; + iconStyle.width = 'auto'; + } else { + iconStyle.width = `${this.state.rxData.imageHeight || 100}%`; + iconStyle.height = 'auto'; + } + } + let iconSrc = !this.state.rxData.jquery_style && (this.state.rxData.src || this.state.rxData.icon); + + if (iconSrc && iconSrc.startsWith('_PRJ_NAME/')) { + iconSrc = iconSrc.replace( + '_PRJ_NAME/', + `../${this.props.context.adapterName}.${this.props.context.instance}/${this.props.context.projectName}/`, + ); + } + + const icon = iconSrc ? ( + + ) : null; + + const buttonStyle: CSSProperties = {}; + // apply style from the element + Object.keys(this.state.rxStyle).forEach((attr: keyof WidgetStyleState) => { + const value = this.state.rxStyle[attr]; + if (value !== null && value !== undefined && VisRxWidget.POSSIBLE_MUI_STYLES.includes(attr)) { + (attr as string) = attr.replace(/(-\w)/g, text => text[1].toUpperCase()); + (buttonStyle as any)[attr] = value; + } + }); + buttonStyle.minWidth = 'unset'; + if (buttonStyle.borderWidth) { + buttonStyle.borderWidth = VisBaseWidget.correctStylePxValue(buttonStyle.borderWidth); + } + if (buttonStyle.fontSize) { + buttonStyle.fontSize = VisBaseWidget.correctStylePxValue(buttonStyle.fontSize); + } + + // extra no rxData here, as it is not possible to set it with bindings + if (this.state.data.visResizable) { + buttonStyle.width = '100%'; + buttonStyle.height = '100%'; + } else { + buttonStyle.padding = this.state.rxData.padding; + } + + let buttonText; + if (this.state.rxData.html) { + // ignore + } else { + buttonText = this.state.rxData.buttontext; + } + + const content = [ + this.state.rxData.html_prepend ? ( + + ) : null, + this.state.rxData.html ? ( + + ) : this.state.rxData.no_style || this.state.rxData.jquery_style ? ( + + ) : !buttonText ? ( + this.onClick()} + size="small" + > + {icon} + + ) : ( + + ), + this.state.rxData.html_append ? ( + + ) : null, + ]; + + return ( +
this.onClick() : undefined} + > + {content} +
+ ); + } +} + +export default JQuiButtonDialogClose; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiButtonNavigation.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiButtonNavigation.tsx index 5780c5fe..e82b2c3c 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiButtonNavigation.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiButtonNavigation.tsx @@ -1,68 +1,68 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 type { - RxWidgetInfo, - RxWidgetInfoAttributesFieldCheckbox, - RxWidgetInfoAttributesFieldDefault, - RxWidgetInfoAttributesFieldText, - RxWidgetInfoWriteable, -} from '@iobroker/types-vis-2'; -import JQuiButton from './JQuiButton'; - -class JQuiButtonNavigation extends JQuiButton { - static getWidgetInfo(): RxWidgetInfo { - const widgetInfo = JQuiButton.getWidgetInfo() as unknown as RxWidgetInfoWriteable; - - const newWidgetInfo = { - id: 'tplJquiButtonNav', - visSet: 'jqui', - visName: 'Navigation Button', - visWidgetLabel: 'jqui_navigation_button', - visPrev: 'widgets/jqui/img/Prev_ButtonNav.png', - visOrder: 8, - visAttrs: widgetInfo.visAttrs, - }; - - // Add note - newWidgetInfo.visAttrs[0].fields.unshift({ - name: '_note', - type: 'help', - text: 'jqui_button_nav_blank_note', - }); - - const modal = JQuiButton.findField(newWidgetInfo, 'modal'); - delete modal.default; - - const navView = JQuiButton.findField(newWidgetInfo, 'nav_view'); - navView.default = ''; - - const text = JQuiButton.findField(newWidgetInfo, 'buttontext'); - text.default = 'View'; - - // set resizable to true - const visResizable = JQuiButton.findField(newWidgetInfo, 'visResizable'); - visResizable.default = true; - - return newWidgetInfo as RxWidgetInfo; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo() { - return JQuiButtonNavigation.getWidgetInfo(); - } -} - -export default JQuiButtonNavigation; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 type { + RxWidgetInfo, + RxWidgetInfoAttributesFieldCheckbox, + RxWidgetInfoAttributesFieldDefault, + RxWidgetInfoAttributesFieldText, + RxWidgetInfoWriteable, +} from '@iobroker/types-vis-2'; +import JQuiButton from './JQuiButton'; + +class JQuiButtonNavigation extends JQuiButton { + static getWidgetInfo(): RxWidgetInfo { + const widgetInfo = JQuiButton.getWidgetInfo() as unknown as RxWidgetInfoWriteable; + + const newWidgetInfo = { + id: 'tplJquiButtonNav', + visSet: 'jqui', + visName: 'Navigation Button', + visWidgetLabel: 'jqui_navigation_button', + visPrev: 'widgets/jqui/img/Prev_ButtonNav.png', + visOrder: 8, + visAttrs: widgetInfo.visAttrs, + }; + + // Add note + newWidgetInfo.visAttrs[0].fields.unshift({ + name: '_note', + type: 'help', + text: 'jqui_button_nav_blank_note', + }); + + const modal = JQuiButton.findField(newWidgetInfo, 'modal'); + delete modal.default; + + const navView = JQuiButton.findField(newWidgetInfo, 'nav_view'); + navView.default = ''; + + const text = JQuiButton.findField(newWidgetInfo, 'buttontext'); + text.default = 'View'; + + // set resizable to true + const visResizable = JQuiButton.findField(newWidgetInfo, 'visResizable'); + visResizable.default = true; + + return newWidgetInfo as RxWidgetInfo; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo() { + return JQuiButtonNavigation.getWidgetInfo(); + } +} + +export default JQuiButtonNavigation; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiButtonPasswordNavigation.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiButtonPasswordNavigation.tsx index 3a36d871..f4eda3e8 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiButtonPasswordNavigation.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiButtonPasswordNavigation.tsx @@ -1,71 +1,71 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 type { - RxWidgetInfo, - RxWidgetInfoAttributesFieldCheckbox, - RxWidgetInfoAttributesFieldDefault, - RxWidgetInfoAttributesFieldText, - RxWidgetInfoWriteable, -} from '@iobroker/types-vis-2'; -import JQuiButton from './JQuiButton'; - -class JQuiButtonPasswordNavigation extends JQuiButton { - static getWidgetInfo(): RxWidgetInfo { - const widgetInfo = JQuiButton.getWidgetInfo() as unknown as RxWidgetInfoWriteable; - - const newWidgetInfo = { - id: 'tplJquiNavPw', - visSet: 'jqui', - visName: 'Navigation with password', - visWidgetLabel: 'jqui_navigation_password', - visPrev: 'widgets/jqui/img/Prev_ButtonNavPw.png', - visOrder: 10, - visAttrs: widgetInfo.visAttrs, - }; - - // Add note - newWidgetInfo.visAttrs[0].fields.unshift({ - name: '_note', - type: 'help', - text: 'jqui_button_nav_blank_note', - }); - - const modal = JQuiButton.findField(newWidgetInfo, 'modal'); - delete modal.default; - - const text = JQuiButton.findField(newWidgetInfo, 'buttontext'); - text.default = 'Password'; - - const navView = JQuiButton.findField(newWidgetInfo, 'nav_view'); - navView.default = ''; - - const password = JQuiButton.findField(newWidgetInfo, 'Password'); - password.default = ''; - - // set resizable to true - const visResizable = JQuiButton.findField(newWidgetInfo, 'visResizable'); - visResizable.default = true; - - return newWidgetInfo as RxWidgetInfo; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo() { - return JQuiButtonPasswordNavigation.getWidgetInfo(); - } -} - -export default JQuiButtonPasswordNavigation; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 type { + RxWidgetInfo, + RxWidgetInfoAttributesFieldCheckbox, + RxWidgetInfoAttributesFieldDefault, + RxWidgetInfoAttributesFieldText, + RxWidgetInfoWriteable, +} from '@iobroker/types-vis-2'; +import JQuiButton from './JQuiButton'; + +class JQuiButtonPasswordNavigation extends JQuiButton { + static getWidgetInfo(): RxWidgetInfo { + const widgetInfo = JQuiButton.getWidgetInfo() as unknown as RxWidgetInfoWriteable; + + const newWidgetInfo = { + id: 'tplJquiNavPw', + visSet: 'jqui', + visName: 'Navigation with password', + visWidgetLabel: 'jqui_navigation_password', + visPrev: 'widgets/jqui/img/Prev_ButtonNavPw.png', + visOrder: 10, + visAttrs: widgetInfo.visAttrs, + }; + + // Add note + newWidgetInfo.visAttrs[0].fields.unshift({ + name: '_note', + type: 'help', + text: 'jqui_button_nav_blank_note', + }); + + const modal = JQuiButton.findField(newWidgetInfo, 'modal'); + delete modal.default; + + const text = JQuiButton.findField(newWidgetInfo, 'buttontext'); + text.default = 'Password'; + + const navView = JQuiButton.findField(newWidgetInfo, 'nav_view'); + navView.default = ''; + + const password = JQuiButton.findField(newWidgetInfo, 'Password'); + password.default = ''; + + // set resizable to true + const visResizable = JQuiButton.findField(newWidgetInfo, 'visResizable'); + visResizable.default = true; + + return newWidgetInfo as RxWidgetInfo; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo() { + return JQuiButtonPasswordNavigation.getWidgetInfo(); + } +} + +export default JQuiButtonPasswordNavigation; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiContainerButtonDialog.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiContainerButtonDialog.tsx index 73af171f..05b97d49 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiContainerButtonDialog.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiContainerButtonDialog.tsx @@ -1,63 +1,63 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 type { - RxWidgetInfo, - RxWidgetInfoAttributesFieldSimple, - RxWidgetInfoAttributesFieldText, - RxWidgetInfoWriteable, -} from '@iobroker/types-vis-2'; -import JQuiButton from './JQuiButton'; - -class JQuiContainerButtonDialog extends JQuiButton { - static getWidgetInfo(): RxWidgetInfo { - const widgetInfo = JQuiButton.getWidgetInfo() as unknown as RxWidgetInfoWriteable; - - const newWidgetInfo = { - id: 'tplContainerButtonDialog', - visSet: 'jqui', - visName: 'container - Button - view in jqui Dialog', - visWidgetLabel: 'jqui_container_button_dialog', - visPrev: 'widgets/jqui/img/Prev_ContainerButtonDialog.png', - visOrder: 11, - visAttrs: widgetInfo.visAttrs, - }; - - // Add note - newWidgetInfo.visAttrs[0].fields.unshift({ - name: '_note', - type: 'help', - text: 'jqui_button_link_blank_note', - }); - - const buttonText = JQuiButton.findField(newWidgetInfo, 'buttontext'); - buttonText.default = 'Container Dialog'; - - const containsView = JQuiButton.findField( - newWidgetInfo, - 'contains_view', - ); - containsView.default = ''; - - return newWidgetInfo as RxWidgetInfo; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo() { - return JQuiContainerButtonDialog.getWidgetInfo(); - } -} - -export default JQuiContainerButtonDialog; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 type { + RxWidgetInfo, + RxWidgetInfoAttributesFieldSimple, + RxWidgetInfoAttributesFieldText, + RxWidgetInfoWriteable, +} from '@iobroker/types-vis-2'; +import JQuiButton from './JQuiButton'; + +class JQuiContainerButtonDialog extends JQuiButton { + static getWidgetInfo(): RxWidgetInfo { + const widgetInfo = JQuiButton.getWidgetInfo() as unknown as RxWidgetInfoWriteable; + + const newWidgetInfo = { + id: 'tplContainerButtonDialog', + visSet: 'jqui', + visName: 'container - Button - view in jqui Dialog', + visWidgetLabel: 'jqui_container_button_dialog', + visPrev: 'widgets/jqui/img/Prev_ContainerButtonDialog.png', + visOrder: 11, + visAttrs: widgetInfo.visAttrs, + }; + + // Add note + newWidgetInfo.visAttrs[0].fields.unshift({ + name: '_note', + type: 'help', + text: 'jqui_button_link_blank_note', + }); + + const buttonText = JQuiButton.findField(newWidgetInfo, 'buttontext'); + buttonText.default = 'Container Dialog'; + + const containsView = JQuiButton.findField( + newWidgetInfo, + 'contains_view', + ); + containsView.default = ''; + + return newWidgetInfo as RxWidgetInfo; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo() { + return JQuiContainerButtonDialog.getWidgetInfo(); + } +} + +export default JQuiContainerButtonDialog; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiContainerDialog.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiContainerDialog.tsx index 430f436f..7978dfd3 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiContainerDialog.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiContainerDialog.tsx @@ -1,64 +1,64 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 type { - RxWidgetInfo, - RxWidgetInfoAttributesFieldCheckbox, - RxWidgetInfoAttributesFieldHTML, - RxWidgetInfoAttributesFieldText, - RxWidgetInfoWriteable, -} from '@iobroker/types-vis-2'; -import JQuiButton from './JQuiButton'; - -class JQuiContainerDialog extends JQuiButton { - static getWidgetInfo(): RxWidgetInfo { - const widgetInfo = JQuiButton.getWidgetInfo() as unknown as RxWidgetInfoWriteable; - - const newWidgetInfo = { - id: 'tplContainerDialog', - visSet: 'jqui', - visName: 'container - HTML - view in jqui Dialog', - visWidgetLabel: 'jqui_container_dialog', - visPrev: 'widgets/jqui/img/Prev_ContainerDialog.png', - visOrder: 7, - visAttrs: widgetInfo.visAttrs, - }; - - // Add note - newWidgetInfo.visAttrs[0].fields.unshift({ - name: '_note', - type: 'help', - text: 'jqui_button_link_blank_note', - }); - - // set resizable to true - const visResizable = JQuiButton.findField(newWidgetInfo, 'visResizable'); - visResizable.default = true; - - const buttonText = JQuiButton.findField(newWidgetInfo, 'buttontext'); - buttonText.default = 'Container Dialog'; - - const htmlDialog = JQuiButton.findField(newWidgetInfo, 'html_dialog'); - htmlDialog.default = '
HTML Dialog
'; - - return newWidgetInfo as RxWidgetInfo; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo() { - return JQuiContainerDialog.getWidgetInfo(); - } -} - -export default JQuiContainerDialog; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 type { + RxWidgetInfo, + RxWidgetInfoAttributesFieldCheckbox, + RxWidgetInfoAttributesFieldHTML, + RxWidgetInfoAttributesFieldText, + RxWidgetInfoWriteable, +} from '@iobroker/types-vis-2'; +import JQuiButton from './JQuiButton'; + +class JQuiContainerDialog extends JQuiButton { + static getWidgetInfo(): RxWidgetInfo { + const widgetInfo = JQuiButton.getWidgetInfo() as unknown as RxWidgetInfoWriteable; + + const newWidgetInfo = { + id: 'tplContainerDialog', + visSet: 'jqui', + visName: 'container - HTML - view in jqui Dialog', + visWidgetLabel: 'jqui_container_dialog', + visPrev: 'widgets/jqui/img/Prev_ContainerDialog.png', + visOrder: 7, + visAttrs: widgetInfo.visAttrs, + }; + + // Add note + newWidgetInfo.visAttrs[0].fields.unshift({ + name: '_note', + type: 'help', + text: 'jqui_button_link_blank_note', + }); + + // set resizable to true + const visResizable = JQuiButton.findField(newWidgetInfo, 'visResizable'); + visResizable.default = true; + + const buttonText = JQuiButton.findField(newWidgetInfo, 'buttontext'); + buttonText.default = 'Container Dialog'; + + const htmlDialog = JQuiButton.findField(newWidgetInfo, 'html_dialog'); + htmlDialog.default = '
HTML Dialog
'; + + return newWidgetInfo as RxWidgetInfo; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo() { + return JQuiContainerDialog.getWidgetInfo(); + } +} + +export default JQuiContainerDialog; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiContainerIconDialog.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiContainerIconDialog.tsx index 8193948a..e4e8b8d7 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiContainerIconDialog.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiContainerIconDialog.tsx @@ -1,68 +1,68 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 JQuiButton from './JQuiButton'; -import type { - RxWidgetInfo, - RxWidgetInfoAttributesField, - RxWidgetInfoAttributesFieldCheckbox, - RxWidgetInfoAttributesFieldSimple, - Writeable, -} from '@iobroker/types-vis-2'; - -class JQuiContainerIconDialog extends JQuiButton { - static getWidgetInfo(): RxWidgetInfo { - const widgetInfo = JQuiButton.getWidgetInfo(); - - const newWidgetInfo: RxWidgetInfo = { - id: 'tplContainerIconDialog', - visSet: 'jqui', - visName: 'container - Icon - view in jqui Dialog', - visWidgetLabel: 'jqui_container_icon_dialog', - visPrev: 'widgets/jqui/img/Prev_ContainerIconDialog.png', - visOrder: 12, - visAttrs: widgetInfo.visAttrs, - }; - - // Add note - (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ - name: '_note', - type: 'help', - text: 'jqui_button_link_blank_note', - }); - - // set resizable to true - const visResizable = JQuiButton.findField(newWidgetInfo, 'visResizable'); - visResizable.default = true; - - const icon = JQuiButton.findField(newWidgetInfo, 'icon'); - icon.default = - 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSJjdXJyZW50Q29sb3IiIGQ9Ik0xOSAxOUg1VjVoN1YzSDVhMiAyIDAgMCAwLTIgMnYxNGEyIDIgMCAwIDAgMiAyaDE0YzEuMSAwIDItLjkgMi0ydi03aC0ydjd6TTE0IDN2MmgzLjU5bC05LjgzIDkuODNsMS40MSAxLjQxTDE5IDYuNDFWMTBoMlYzaC03eiIvPjwvc3ZnPg=='; - - const buttonText = JQuiButton.findField(newWidgetInfo, 'buttontext'); - delete buttonText.default; - - const containsView = JQuiButton.findField(newWidgetInfo, 'contains_view'); - containsView.default = ''; - - return newWidgetInfo; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo(): RxWidgetInfo { - return JQuiContainerIconDialog.getWidgetInfo(); - } -} - -export default JQuiContainerIconDialog; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 JQuiButton from './JQuiButton'; +import type { + RxWidgetInfo, + RxWidgetInfoAttributesField, + RxWidgetInfoAttributesFieldCheckbox, + RxWidgetInfoAttributesFieldSimple, + Writeable, +} from '@iobroker/types-vis-2'; + +class JQuiContainerIconDialog extends JQuiButton { + static getWidgetInfo(): RxWidgetInfo { + const widgetInfo = JQuiButton.getWidgetInfo(); + + const newWidgetInfo: RxWidgetInfo = { + id: 'tplContainerIconDialog', + visSet: 'jqui', + visName: 'container - Icon - view in jqui Dialog', + visWidgetLabel: 'jqui_container_icon_dialog', + visPrev: 'widgets/jqui/img/Prev_ContainerIconDialog.png', + visOrder: 12, + visAttrs: widgetInfo.visAttrs, + }; + + // Add note + (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ + name: '_note', + type: 'help', + text: 'jqui_button_link_blank_note', + }); + + // set resizable to true + const visResizable = JQuiButton.findField(newWidgetInfo, 'visResizable'); + visResizable.default = true; + + const icon = JQuiButton.findField(newWidgetInfo, 'icon'); + icon.default = + 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSJjdXJyZW50Q29sb3IiIGQ9Ik0xOSAxOUg1VjVoN1YzSDVhMiAyIDAgMCAwLTIgMnYxNGEyIDIgMCAwIDAgMiAyaDE0YzEuMSAwIDItLjkgMi0ydi03aC0ydjd6TTE0IDN2MmgzLjU5bC05LjgzIDkuODNsMS40MSAxLjQxTDE5IDYuNDFWMTBoMlYzaC03eiIvPjwvc3ZnPg=='; + + const buttonText = JQuiButton.findField(newWidgetInfo, 'buttontext'); + delete buttonText.default; + + const containsView = JQuiButton.findField(newWidgetInfo, 'contains_view'); + containsView.default = ''; + + return newWidgetInfo; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo(): RxWidgetInfo { + return JQuiContainerIconDialog.getWidgetInfo(); + } +} + +export default JQuiContainerIconDialog; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiDialog.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiDialog.tsx index b13caf94..d4cc1838 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiDialog.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiDialog.tsx @@ -1,77 +1,77 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 JQuiButton from './JQuiButton'; -import type { - RxWidgetInfoAttributesField, - RxWidgetInfoAttributesFieldCheckbox, - RxWidgetInfoAttributesFieldSimple, - WidgetStyle, - Writeable, - RxWidgetInfo, -} from '@iobroker/types-vis-2'; - -class JQuiDialog extends JQuiButton { - static getWidgetInfo(): RxWidgetInfo { - const widgetInfo = JQuiButton.getWidgetInfo(); - - const newWidgetInfo: RxWidgetInfo = { - id: 'tplJquiDialog', - visSet: 'jqui', - visName: 'HTML - Dialog', - visWidgetLabel: 'jqui_html_dialog', - visPrev: 'widgets/jqui/img/Prev_JquiDialog.png', - visOrder: 5, - visAttrs: widgetInfo.visAttrs, - }; - - // Add note - (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ - name: '_note', - type: 'help', - text: 'jqui_button_link_blank_note', - }); - - // set resizable to true - const visResizable = JQuiButton.findField(newWidgetInfo, 'visResizable'); - visResizable.default = true; - - const html = JQuiButton.findField(newWidgetInfo, 'html'); - html.default = '
HTML
'; - - const buttonText = JQuiButton.findField(newWidgetInfo, 'buttontext'); - delete buttonText.default; - - const htmlDialog = JQuiButton.findField(newWidgetInfo, 'html_dialog'); - htmlDialog.default = '
HTML Dialog
'; - - (newWidgetInfo.visDefaultStyle as Writeable) = { - 'border-width': '1px', - 'border-style': 'solid', - 'border-color': '#000', - width: '200px', - height: '130px', - cursor: 'pointer', - }; - - return newWidgetInfo; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo(): RxWidgetInfo { - return JQuiDialog.getWidgetInfo(); - } -} - -export default JQuiDialog; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 JQuiButton from './JQuiButton'; +import type { + RxWidgetInfoAttributesField, + RxWidgetInfoAttributesFieldCheckbox, + RxWidgetInfoAttributesFieldSimple, + WidgetStyle, + Writeable, + RxWidgetInfo, +} from '@iobroker/types-vis-2'; + +class JQuiDialog extends JQuiButton { + static getWidgetInfo(): RxWidgetInfo { + const widgetInfo = JQuiButton.getWidgetInfo(); + + const newWidgetInfo: RxWidgetInfo = { + id: 'tplJquiDialog', + visSet: 'jqui', + visName: 'HTML - Dialog', + visWidgetLabel: 'jqui_html_dialog', + visPrev: 'widgets/jqui/img/Prev_JquiDialog.png', + visOrder: 5, + visAttrs: widgetInfo.visAttrs, + }; + + // Add note + (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ + name: '_note', + type: 'help', + text: 'jqui_button_link_blank_note', + }); + + // set resizable to true + const visResizable = JQuiButton.findField(newWidgetInfo, 'visResizable'); + visResizable.default = true; + + const html = JQuiButton.findField(newWidgetInfo, 'html'); + html.default = '
HTML
'; + + const buttonText = JQuiButton.findField(newWidgetInfo, 'buttontext'); + delete buttonText.default; + + const htmlDialog = JQuiButton.findField(newWidgetInfo, 'html_dialog'); + htmlDialog.default = '
HTML Dialog
'; + + (newWidgetInfo.visDefaultStyle as Writeable) = { + 'border-width': '1px', + 'border-style': 'solid', + 'border-color': '#000', + width: '200px', + height: '130px', + cursor: 'pointer', + }; + + return newWidgetInfo; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo(): RxWidgetInfo { + return JQuiDialog.getWidgetInfo(); + } +} + +export default JQuiDialog; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiDialogExternal.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiDialogExternal.tsx index 15c03162..c9a8df1e 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiDialogExternal.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiDialogExternal.tsx @@ -1,63 +1,63 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 JQuiButton from './JQuiButton'; -import type { - RxWidgetInfo, - RxWidgetInfoAttributesField, - RxWidgetInfoAttributesFieldCheckbox, - RxWidgetInfoAttributesFieldSimple, - Writeable, -} from '@iobroker/types-vis-2'; - -class JQuiDialogExternal extends JQuiButton { - static getWidgetInfo(): RxWidgetInfo { - const widgetInfo: RxWidgetInfo = JQuiButton.getWidgetInfo(); - - const newWidgetInfo = { - id: 'tplContainerDialogExternal', - visSet: 'jqui', - visName: 'External Dialog', - visWidgetLabel: 'jqui_html_external_dialog', - visPrev: 'widgets/jqui/img/Prev_JquiExternalDialog.png', - visOrder: 11, - visAttrs: widgetInfo.visAttrs, - }; - - // Add note - (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ - name: '_note', - type: 'help', - text: 'jqui_button_link_blank_note', - }); - - const externalDialog = JQuiButton.findField( - newWidgetInfo, - 'externalDialog', - ); - externalDialog.default = true; - - const htmlDialog = JQuiButton.findField(newWidgetInfo, 'html_dialog'); - htmlDialog.default = '
HTML Dialog
'; - - return newWidgetInfo; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo(): RxWidgetInfo { - return JQuiDialogExternal.getWidgetInfo(); - } -} - -export default JQuiDialogExternal; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 JQuiButton from './JQuiButton'; +import type { + RxWidgetInfo, + RxWidgetInfoAttributesField, + RxWidgetInfoAttributesFieldCheckbox, + RxWidgetInfoAttributesFieldSimple, + Writeable, +} from '@iobroker/types-vis-2'; + +class JQuiDialogExternal extends JQuiButton { + static getWidgetInfo(): RxWidgetInfo { + const widgetInfo: RxWidgetInfo = JQuiButton.getWidgetInfo(); + + const newWidgetInfo = { + id: 'tplContainerDialogExternal', + visSet: 'jqui', + visName: 'External Dialog', + visWidgetLabel: 'jqui_html_external_dialog', + visPrev: 'widgets/jqui/img/Prev_JquiExternalDialog.png', + visOrder: 11, + visAttrs: widgetInfo.visAttrs, + }; + + // Add note + (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ + name: '_note', + type: 'help', + text: 'jqui_button_link_blank_note', + }); + + const externalDialog = JQuiButton.findField( + newWidgetInfo, + 'externalDialog', + ); + externalDialog.default = true; + + const htmlDialog = JQuiButton.findField(newWidgetInfo, 'html_dialog'); + htmlDialog.default = '
HTML Dialog
'; + + return newWidgetInfo; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo(): RxWidgetInfo { + return JQuiDialogExternal.getWidgetInfo(); + } +} + +export default JQuiDialogExternal; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiIconDialog.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiIconDialog.tsx index 6838be2c..a9468b40 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiIconDialog.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiIconDialog.tsx @@ -1,65 +1,65 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 JQuiButton from './JQuiButton'; -import type { - RxWidgetInfo, - RxWidgetInfoAttributesField, - RxWidgetInfoAttributesFieldCheckbox, - RxWidgetInfoAttributesFieldSimple, - Writeable, -} from '@iobroker/types-vis-2'; - -class JQuiIconDialog extends JQuiButton { - static getWidgetInfo(): RxWidgetInfo { - const widgetInfo: RxWidgetInfo = JQuiButton.getWidgetInfo(); - - const newWidgetInfo = { - id: 'tplJquiIconDialog', - visSet: 'jqui', - visName: 'Icon - Dialog', - visWidgetLabel: 'jqui_icon_dialog', - visPrev: 'widgets/jqui/img/Prev_JquiIconDialog.png', - visOrder: 6, - visAttrs: widgetInfo.visAttrs, - }; - - // Add note - (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ - name: '_note', - type: 'help', - text: 'jqui_button_link_blank_note', - }); - - // set resizable to true - const visResizable = JQuiButton.findField(newWidgetInfo, 'visResizable'); - visResizable.default = true; - - const buttonText = JQuiButton.findField(newWidgetInfo, 'buttontext'); - buttonText.default = 'Icon Dialog'; - - const htmlDialog = JQuiButton.findField(newWidgetInfo, 'html_dialog'); - htmlDialog.default = '
HTML Dialog
'; - - return newWidgetInfo; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo(): RxWidgetInfo { - return JQuiIconDialog.getWidgetInfo(); - } -} - -export default JQuiIconDialog; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 JQuiButton from './JQuiButton'; +import type { + RxWidgetInfo, + RxWidgetInfoAttributesField, + RxWidgetInfoAttributesFieldCheckbox, + RxWidgetInfoAttributesFieldSimple, + Writeable, +} from '@iobroker/types-vis-2'; + +class JQuiIconDialog extends JQuiButton { + static getWidgetInfo(): RxWidgetInfo { + const widgetInfo: RxWidgetInfo = JQuiButton.getWidgetInfo(); + + const newWidgetInfo = { + id: 'tplJquiIconDialog', + visSet: 'jqui', + visName: 'Icon - Dialog', + visWidgetLabel: 'jqui_icon_dialog', + visPrev: 'widgets/jqui/img/Prev_JquiIconDialog.png', + visOrder: 6, + visAttrs: widgetInfo.visAttrs, + }; + + // Add note + (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ + name: '_note', + type: 'help', + text: 'jqui_button_link_blank_note', + }); + + // set resizable to true + const visResizable = JQuiButton.findField(newWidgetInfo, 'visResizable'); + visResizable.default = true; + + const buttonText = JQuiButton.findField(newWidgetInfo, 'buttontext'); + buttonText.default = 'Icon Dialog'; + + const htmlDialog = JQuiButton.findField(newWidgetInfo, 'html_dialog'); + htmlDialog.default = '
HTML Dialog
'; + + return newWidgetInfo; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo(): RxWidgetInfo { + return JQuiIconDialog.getWidgetInfo(); + } +} + +export default JQuiIconDialog; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiIconHttpGet.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiIconHttpGet.tsx index cb02efaf..cfaebe51 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiIconHttpGet.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiIconHttpGet.tsx @@ -1,65 +1,65 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 JQuiButton from './JQuiButton'; -import type { - RxWidgetInfo, - RxWidgetInfoAttributesField, - RxWidgetInfoAttributesFieldCheckbox, - RxWidgetInfoAttributesFieldSimple, - Writeable, -} from '@iobroker/types-vis-2'; - -class JQuiIconHttpGet extends JQuiButton { - static getWidgetInfo(): RxWidgetInfo { - const widgetInfo = JQuiButton.getWidgetInfo(); - - const newWidgetInfo = { - id: 'tplIconHttpGet', - visSet: 'jqui', - visName: 'Icon HTTP GET', - visWidgetLabel: 'jqui_icon_http_get', - visPrev: 'widgets/jqui/img/Prev_IconHttpGet.png', - visOrder: 4, - visAttrs: widgetInfo.visAttrs, - }; - - // Add note - (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ - name: '_note', - type: 'help', - text: 'jqui_button_link_blank_note', - }); - - const url = JQuiButton.findField(newWidgetInfo, 'url'); - url.default = 'http://'; - - const text = JQuiButton.findField(newWidgetInfo, 'buttontext'); - text.default = 'URL Backend'; - - // set resizable to true - const visResizable = JQuiButton.findField(newWidgetInfo, 'visResizable'); - visResizable.default = true; - - return newWidgetInfo; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo(): RxWidgetInfo { - return JQuiIconHttpGet.getWidgetInfo(); - } -} - -export default JQuiIconHttpGet; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 JQuiButton from './JQuiButton'; +import type { + RxWidgetInfo, + RxWidgetInfoAttributesField, + RxWidgetInfoAttributesFieldCheckbox, + RxWidgetInfoAttributesFieldSimple, + Writeable, +} from '@iobroker/types-vis-2'; + +class JQuiIconHttpGet extends JQuiButton { + static getWidgetInfo(): RxWidgetInfo { + const widgetInfo = JQuiButton.getWidgetInfo(); + + const newWidgetInfo = { + id: 'tplIconHttpGet', + visSet: 'jqui', + visName: 'Icon HTTP GET', + visWidgetLabel: 'jqui_icon_http_get', + visPrev: 'widgets/jqui/img/Prev_IconHttpGet.png', + visOrder: 4, + visAttrs: widgetInfo.visAttrs, + }; + + // Add note + (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ + name: '_note', + type: 'help', + text: 'jqui_button_link_blank_note', + }); + + const url = JQuiButton.findField(newWidgetInfo, 'url'); + url.default = 'http://'; + + const text = JQuiButton.findField(newWidgetInfo, 'buttontext'); + text.default = 'URL Backend'; + + // set resizable to true + const visResizable = JQuiButton.findField(newWidgetInfo, 'visResizable'); + visResizable.default = true; + + return newWidgetInfo; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo(): RxWidgetInfo { + return JQuiIconHttpGet.getWidgetInfo(); + } +} + +export default JQuiIconHttpGet; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiIconInc.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiIconInc.tsx index 6c3adbb9..9bb36ff8 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiIconInc.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiIconInc.tsx @@ -1,57 +1,57 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 JQuiWriteState from './JQuiWriteState'; -import type { - RxWidgetInfo, - RxWidgetInfoAttributesFieldSelect, - RxWidgetInfoAttributesField, - Writeable, -} from '@iobroker/types-vis-2'; - -class JQuiIconInc extends JQuiWriteState { - static getWidgetInfo(): RxWidgetInfo { - const widgetInfo: RxWidgetInfo = JQuiWriteState.getWidgetInfo(); - - const newWidgetInfo: RxWidgetInfo = { - id: 'tplIconInc', - visSet: 'jqui', - visName: 'Icon Increment', - visWidgetLabel: 'jqui_icon_increment', - visPrev: 'widgets/jqui/img/Prev_IconInc.png', - visOrder: 27, - visAttrs: widgetInfo.visAttrs, - }; - - // Add note - (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ - name: '_note', - type: 'help', - text: 'jqui_write_state_note', - }); - - const target = JQuiWriteState.findField(newWidgetInfo, 'type'); - target.default = 'change'; - - return newWidgetInfo; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo(): RxWidgetInfo { - return JQuiIconInc.getWidgetInfo(); - } -} - -export default JQuiIconInc; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 JQuiWriteState from './JQuiWriteState'; +import type { + RxWidgetInfo, + RxWidgetInfoAttributesFieldSelect, + RxWidgetInfoAttributesField, + Writeable, +} from '@iobroker/types-vis-2'; + +class JQuiIconInc extends JQuiWriteState { + static getWidgetInfo(): RxWidgetInfo { + const widgetInfo: RxWidgetInfo = JQuiWriteState.getWidgetInfo(); + + const newWidgetInfo: RxWidgetInfo = { + id: 'tplIconInc', + visSet: 'jqui', + visName: 'Icon Increment', + visWidgetLabel: 'jqui_icon_increment', + visPrev: 'widgets/jqui/img/Prev_IconInc.png', + visOrder: 27, + visAttrs: widgetInfo.visAttrs, + }; + + // Add note + (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ + name: '_note', + type: 'help', + text: 'jqui_write_state_note', + }); + + const target = JQuiWriteState.findField(newWidgetInfo, 'type'); + target.default = 'change'; + + return newWidgetInfo; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo(): RxWidgetInfo { + return JQuiIconInc.getWidgetInfo(); + } +} + +export default JQuiIconInc; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiIconLink.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiIconLink.tsx index 6cff80d9..e69ba80c 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiIconLink.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiIconLink.tsx @@ -1,61 +1,61 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 JQuiButton from './JQuiButton'; -import type { - RxWidgetInfo, - RxWidgetInfoAttributesField, - RxWidgetInfoAttributesFieldCheckbox, - RxWidgetInfoAttributesFieldSimple, - Writeable, -} from '@iobroker/types-vis-2'; - -class JQuiIconLink extends JQuiButton { - static getWidgetInfo(): RxWidgetInfo { - const widgetInfo = JQuiButton.getWidgetInfo(); - - const newWidgetInfo = { - id: 'tplIconLink', - visSet: 'jqui', - visName: 'Button Link', - visWidgetLabel: 'jqui_icon_link', - visPrev: 'widgets/jqui/img/Prev_IconLink.png', - visOrder: 3, - visAttrs: widgetInfo.visAttrs, - }; - - // Add note - (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ - name: '_note', - type: 'help', - text: 'jqui_button_link_blank_note', - }); - - const target = JQuiButton.findField(newWidgetInfo, 'target'); - target.default = '_blank'; - - // set resizable to true - const visResizable = JQuiButton.findField(newWidgetInfo, 'visResizable'); - visResizable.default = true; - - return newWidgetInfo; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo(): RxWidgetInfo { - return JQuiIconLink.getWidgetInfo(); - } -} - -export default JQuiIconLink; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 JQuiButton from './JQuiButton'; +import type { + RxWidgetInfo, + RxWidgetInfoAttributesField, + RxWidgetInfoAttributesFieldCheckbox, + RxWidgetInfoAttributesFieldSimple, + Writeable, +} from '@iobroker/types-vis-2'; + +class JQuiIconLink extends JQuiButton { + static getWidgetInfo(): RxWidgetInfo { + const widgetInfo = JQuiButton.getWidgetInfo(); + + const newWidgetInfo = { + id: 'tplIconLink', + visSet: 'jqui', + visName: 'Button Link', + visWidgetLabel: 'jqui_icon_link', + visPrev: 'widgets/jqui/img/Prev_IconLink.png', + visOrder: 3, + visAttrs: widgetInfo.visAttrs, + }; + + // Add note + (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ + name: '_note', + type: 'help', + text: 'jqui_button_link_blank_note', + }); + + const target = JQuiButton.findField(newWidgetInfo, 'target'); + target.default = '_blank'; + + // set resizable to true + const visResizable = JQuiButton.findField(newWidgetInfo, 'visResizable'); + visResizable.default = true; + + return newWidgetInfo; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo(): RxWidgetInfo { + return JQuiIconLink.getWidgetInfo(); + } +} + +export default JQuiIconLink; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiIconNavigation.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiIconNavigation.tsx index 4b0935e0..9672a69a 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiIconNavigation.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiIconNavigation.tsx @@ -1,71 +1,71 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 JQuiButton from './JQuiButton'; -import type { - RxWidgetInfo, - RxWidgetInfoAttributesField, - RxWidgetInfoAttributesFieldCheckbox, - RxWidgetInfoAttributesFieldSimple, - Writeable, -} from '@iobroker/types-vis-2'; - -class JQuiIconNavigation extends JQuiButton { - static getWidgetInfo(): RxWidgetInfo { - const widgetInfo = JQuiButton.getWidgetInfo(); - - const newWidgetInfo: RxWidgetInfo = { - id: 'tplJquiIconNav', - visSet: 'jqui', - visName: 'Navigation Icon', - visWidgetLabel: 'jqui_navigation_icon', - visPrev: 'widgets/jqui/img/Prev_IconNav.png', - visOrder: 9, - visAttrs: widgetInfo.visAttrs, - }; - - // Add note - (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ - name: '_note', - type: 'help', - text: 'jqui_button_nav_blank_note', - }); - - const buttonText = JQuiButton.findField(newWidgetInfo, 'buttontext'); - delete buttonText.default; - - const modal = JQuiButton.findField(newWidgetInfo, 'modal'); - delete modal.default; - - const navView = JQuiButton.findField(newWidgetInfo, 'nav_view'); - navView.default = ''; - - const icon = JQuiButton.findField(newWidgetInfo, 'icon'); - icon.default = - 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSJjdXJyZW50Q29sb3IiIGQ9Ik0yMSAzTDMgMTAuNTN2Ljk4bDYuODQgMi42NUwxMi40OCAyMWguOThMMjEgM3oiLz48L3N2Zz4='; - - // set resizable to true - const visResizable = JQuiButton.findField(newWidgetInfo, 'visResizable'); - visResizable.default = true; - - return newWidgetInfo; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo(): RxWidgetInfo { - return JQuiIconNavigation.getWidgetInfo(); - } -} - -export default JQuiIconNavigation; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 JQuiButton from './JQuiButton'; +import type { + RxWidgetInfo, + RxWidgetInfoAttributesField, + RxWidgetInfoAttributesFieldCheckbox, + RxWidgetInfoAttributesFieldSimple, + Writeable, +} from '@iobroker/types-vis-2'; + +class JQuiIconNavigation extends JQuiButton { + static getWidgetInfo(): RxWidgetInfo { + const widgetInfo = JQuiButton.getWidgetInfo(); + + const newWidgetInfo: RxWidgetInfo = { + id: 'tplJquiIconNav', + visSet: 'jqui', + visName: 'Navigation Icon', + visWidgetLabel: 'jqui_navigation_icon', + visPrev: 'widgets/jqui/img/Prev_IconNav.png', + visOrder: 9, + visAttrs: widgetInfo.visAttrs, + }; + + // Add note + (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ + name: '_note', + type: 'help', + text: 'jqui_button_nav_blank_note', + }); + + const buttonText = JQuiButton.findField(newWidgetInfo, 'buttontext'); + delete buttonText.default; + + const modal = JQuiButton.findField(newWidgetInfo, 'modal'); + delete modal.default; + + const navView = JQuiButton.findField(newWidgetInfo, 'nav_view'); + navView.default = ''; + + const icon = JQuiButton.findField(newWidgetInfo, 'icon'); + icon.default = + 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSJjdXJyZW50Q29sb3IiIGQ9Ik0yMSAzTDMgMTAuNTN2Ljk4bDYuODQgMi42NUwxMi40OCAyMWguOThMMjEgM3oiLz48L3N2Zz4='; + + // set resizable to true + const visResizable = JQuiButton.findField(newWidgetInfo, 'visResizable'); + visResizable.default = true; + + return newWidgetInfo; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo(): RxWidgetInfo { + return JQuiIconNavigation.getWidgetInfo(); + } +} + +export default JQuiIconNavigation; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiIconStateBool.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiIconStateBool.tsx index cc22591c..863e137e 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiIconStateBool.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiIconStateBool.tsx @@ -1,63 +1,63 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 JQuiBinaryState from './JQuiBinaryState'; -import type { - RxWidgetInfo, - RxWidgetInfoAttributesField, - RxWidgetInfoAttributesFieldSimple, - Writeable, -} from '@iobroker/types-vis-2'; - -class JQuiIconStateBool extends JQuiBinaryState { - static getWidgetInfo(): RxWidgetInfo { - const widgetInfo = JQuiBinaryState.getWidgetInfo(); - const newWidgetInfo = { - id: 'tplIconStateBool', - visSet: 'jqui', - visName: 'Binary Icon State', - visWidgetLabel: 'jqui_icon_state_bool', - visPrev: 'widgets/jqui/img/Prev_IconStateBool.png', - visOrder: 15, - visAttrs: widgetInfo.visAttrs, - visDefaultStyle: { - width: 70, - height: 30, - }, - }; - - const iconFalse = JQuiBinaryState.findField(newWidgetInfo, 'icon_false'); - if (iconFalse) { - iconFalse.default = - 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSJjdXJyZW50Q29sb3IiIGQ9Ik05IDIxYzAgLjUuNCAxIDEgMWg0Yy42IDAgMS0uNSAxLTF2LTFIOXYxem0zLTE5QzguMSAyIDUgNS4xIDUgOWMwIDIuNCAxLjIgNC41IDMgNS43VjE3YzAgLjUuNCAxIDEgMWg2Yy42IDAgMS0uNSAxLTF2LTIuM2MxLjgtMS4zIDMtMy40IDMtNS43YzAtMy45LTMuMS03LTctN3oiLz48L3N2Zz4='; - } - - // Add note - (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ - name: '_note', - type: 'help', - text: 'jqui_button_binary_control_note', - }); - - return newWidgetInfo; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo(): RxWidgetInfo { - return JQuiIconStateBool.getWidgetInfo(); - } -} - -export default JQuiIconStateBool; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 JQuiBinaryState from './JQuiBinaryState'; +import type { + RxWidgetInfo, + RxWidgetInfoAttributesField, + RxWidgetInfoAttributesFieldSimple, + Writeable, +} from '@iobroker/types-vis-2'; + +class JQuiIconStateBool extends JQuiBinaryState { + static getWidgetInfo(): RxWidgetInfo { + const widgetInfo = JQuiBinaryState.getWidgetInfo(); + const newWidgetInfo = { + id: 'tplIconStateBool', + visSet: 'jqui', + visName: 'Binary Icon State', + visWidgetLabel: 'jqui_icon_state_bool', + visPrev: 'widgets/jqui/img/Prev_IconStateBool.png', + visOrder: 15, + visAttrs: widgetInfo.visAttrs, + visDefaultStyle: { + width: 70, + height: 30, + }, + }; + + const iconFalse = JQuiBinaryState.findField(newWidgetInfo, 'icon_false'); + if (iconFalse) { + iconFalse.default = + 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSJjdXJyZW50Q29sb3IiIGQ9Ik05IDIxYzAgLjUuNCAxIDEgMWg0Yy42IDAgMS0uNSAxLTF2LTFIOXYxem0zLTE5QzguMSAyIDUgNS4xIDUgOWMwIDIuNCAxLjIgNC41IDMgNS43VjE3YzAgLjUuNCAxIDEgMWg2Yy42IDAgMS0uNSAxLTF2LTIuM2MxLjgtMS4zIDMtMy40IDMtNS43YzAtMy45LTMuMS03LTctN3oiLz48L3N2Zz4='; + } + + // Add note + (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ + name: '_note', + type: 'help', + text: 'jqui_button_binary_control_note', + }); + + return newWidgetInfo; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo(): RxWidgetInfo { + return JQuiIconStateBool.getWidgetInfo(); + } +} + +export default JQuiIconStateBool; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiIconStatePushButton.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiIconStatePushButton.tsx index 9c65947d..8f79f751 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiIconStatePushButton.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiIconStatePushButton.tsx @@ -1,62 +1,62 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 JQuiBinaryState from './JQuiBinaryState'; -import type { - RxWidgetInfo, - RxWidgetInfoAttributesField, - RxWidgetInfoAttributesFieldCheckbox, - Writeable, -} from '@iobroker/types-vis-2'; - -class JQuiIconStatePushButton extends JQuiBinaryState { - static getWidgetInfo(): RxWidgetInfo { - const widgetInfo: RxWidgetInfo = JQuiBinaryState.getWidgetInfo(); - const newWidgetInfo = { - id: 'tplIconStatePushButton', - visSet: 'jqui', - visName: 'Binary Icon Push Button', - visWidgetLabel: 'jqui_icon_state_push_button', - visPrev: 'widgets/jqui/img/Prev_IconPushButton.png', - visOrder: 15, - visAttrs: widgetInfo.visAttrs, - visDefaultStyle: { - width: 70, - height: 30, - }, - }; - - const pushMode = JQuiBinaryState.findField(newWidgetInfo, 'pushMode'); - if (pushMode) { - pushMode.default = true; - } - - // Add note - (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ - name: '_note', - type: 'help', - text: 'jqui_button_binary_control_note', - }); - - return newWidgetInfo; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo(): RxWidgetInfo { - return JQuiIconStatePushButton.getWidgetInfo(); - } -} - -export default JQuiIconStatePushButton; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 JQuiBinaryState from './JQuiBinaryState'; +import type { + RxWidgetInfo, + RxWidgetInfoAttributesField, + RxWidgetInfoAttributesFieldCheckbox, + Writeable, +} from '@iobroker/types-vis-2'; + +class JQuiIconStatePushButton extends JQuiBinaryState { + static getWidgetInfo(): RxWidgetInfo { + const widgetInfo: RxWidgetInfo = JQuiBinaryState.getWidgetInfo(); + const newWidgetInfo = { + id: 'tplIconStatePushButton', + visSet: 'jqui', + visName: 'Binary Icon Push Button', + visWidgetLabel: 'jqui_icon_state_push_button', + visPrev: 'widgets/jqui/img/Prev_IconPushButton.png', + visOrder: 15, + visAttrs: widgetInfo.visAttrs, + visDefaultStyle: { + width: 70, + height: 30, + }, + }; + + const pushMode = JQuiBinaryState.findField(newWidgetInfo, 'pushMode'); + if (pushMode) { + pushMode.default = true; + } + + // Add note + (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ + name: '_note', + type: 'help', + text: 'jqui_button_binary_control_note', + }); + + return newWidgetInfo; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo(): RxWidgetInfo { + return JQuiIconStatePushButton.getWidgetInfo(); + } +} + +export default JQuiIconStatePushButton; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiInput.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiInput.tsx index 081d3ddf..30015ffe 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiInput.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiInput.tsx @@ -1,390 +1,390 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 React from 'react'; - -import { IconButton, TextField, InputAdornment, Button } from '@mui/material'; - -import { KeyboardReturn } from '@mui/icons-material'; - -import { I18n, type LegacyConnection } from '@iobroker/adapter-react-v5'; - -import VisRxWidget, { type VisRxWidgetState } from '../../visRxWidget'; -import type { - RxRenderWidgetProps, - RxWidgetInfo, - RxWidgetInfoAttributesField, - VisBaseWidgetProps, - Writeable, -} from '@iobroker/types-vis-2'; - -type RxData = { - label: string; - oid: string; - asString: boolean; - autoFocus: boolean; - readOnly: boolean; - withEnter: boolean; - buttontext: string; - selectAllOnFocus: boolean; - unit: string; - no_style: boolean; - jquery_style: boolean; - variant: 'filled' | 'outlined' | 'standard'; - size: number; -}; - -interface JQuiInputState extends VisRxWidgetState { - input: string; -} - -class JQuiInput

extends VisRxWidget { - private focused: boolean = false; - private readonly inputRef: React.RefObject; - private jQueryDone: boolean = false; - private object: ioBroker.StateObject | null = null; - - constructor(props: VisBaseWidgetProps) { - super(props); - Object.assign(this.state, { - input: '', - }); - this.inputRef = React.createRef(); - } - - static getWidgetInfo(): RxWidgetInfo { - return { - id: 'tplJquiInput', - visSet: 'jqui', - visName: 'Input', - visWidgetLabel: 'jqui_input', - visPrev: 'widgets/jqui/img/Prev_Input.png', - visOrder: 13, - visAttrs: [ - { - name: 'common', - fields: [ - { - name: 'label', - type: 'text', - default: I18n.t('jqui_input').replace('jqui_', ''), - }, - { - name: 'oid', - type: 'id', - onChange: async ( - _field: RxWidgetInfoAttributesField, - data: RxData, - changeData: (newData: RxData) => void, - socket: LegacyConnection, - ): Promise => { - if (data.oid && data.oid !== 'nothing_selected') { - const obj = await socket.getObject(data.oid); - let changed = false; - if (obj?.common?.unit) { - if (data.unit !== obj.common.unit) { - data.unit = obj.common.unit; - changed = true; - } - } - if (obj?.common?.type !== 'number') { - if (!data.asString) { - data.asString = true; - changed = true; - } - } else if (obj?.common?.type === 'number') { - if (data.asString) { - data.asString = false; - changed = true; - } - } - changed && changeData(data); - } - }, - }, - { - name: 'asString', - type: 'checkbox', - label: 'jqui_as_string', - }, - // { - // name: 'digits', - // type: 'slider', - // min: 0, - // max: 5, - // hidden: data => !!data.asString, - // }, - { - name: 'autoFocus', - type: 'checkbox', - }, - { - name: 'readOnly', - type: 'checkbox', - label: 'jqui_read_only', - }, - { - name: 'withEnter', - type: 'checkbox', - label: 'jqui_with_enter_button', - hidden: '!!data.readOnly', - }, - { - name: 'buttontext', - type: 'text', - label: 'jqui_button_text', - hidden: '!data.withEnter || !!data.readOnly', - }, - { - name: 'selectAllOnFocus', - type: 'checkbox', - label: 'jqui_select_all_on_focus', - tooltip: 'jqui_select_all_on_focus_tooltip', - }, - { - name: 'unit', - type: 'text', - label: 'jqui_unit', - }, - ], - }, - { - name: 'style', - fields: [ - { name: 'no_style', type: 'checkbox', hidden: (data: RxData): boolean => data.jquery_style }, - { - name: 'jquery_style', - label: 'jqui_jquery_style', - type: 'checkbox', - hidden: (data: RxData): boolean => data.no_style, - }, - { - name: 'variant', - label: 'jqui_variant', - type: 'select', - noTranslation: true, - options: ['filled', 'outlined', 'standard'], - default: 'standard', - hidden: (data: RxData): boolean => data.jquery_style || data.no_style, - }, - { - name: 'size', - type: 'slider', - min: 4, - max: 100, - default: 10, - hidden: (data: RxData): boolean => !data.no_style, - }, - ], - }, - ], - }; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo(): RxWidgetInfo { - return JQuiInput.getWidgetInfo(); - } - - static findField( - widgetInfo: RxWidgetInfo, - name: string, - ): Writeable | null { - return VisRxWidget.findField(widgetInfo, name) as unknown as Writeable; - } - - async componentDidMount(): Promise { - super.componentDidMount(); - - try { - const input = await this.props.context.socket.getState(this.state.rxData.oid); - if (input && input.val !== undefined && input.val !== null) { - input.val = input.val.toString(); - this.setState({ input: input.val }); - } - } catch (error) { - console.error(`Cannot get state ${this.state.rxData.oid}: ${error}`); - } - - if ( - this.inputRef.current && - this.state.rxData.autoFocus && - !this.props.editMode && - (this.state.rxData.jquery_style || this.state.rxData.no_style) - ) { - setTimeout(() => this.inputRef.current.focus(), 100); - } - } - - onStateUpdated(id: string, state: ioBroker.State | null | undefined): void { - super.onStateUpdated(id, state); - if (state?.val || state?.val === 0) { - if (id === this.state.rxData.oid && !this.focused) { - if (state.val.toString() !== this.state.input.toString()) { - this.setState({ input: state.val as string }); - } - } - } - } - - componentDidUpdate(): void { - if (this.inputRef.current) { - if (this.state.rxData.jquery_style && !this.jQueryDone) { - this.jQueryDone = true; - (window as any).jQuery(this.inputRef.current).button().addClass('ui-state-default'); - } - } - } - - async setValue(value: string): Promise { - if (this.object?._id !== this.state.rxData.oid) { - this.object = (await this.props.context.socket.getObject(this.state.rxData.oid)) as - | ioBroker.StateObject - | null - | undefined; - if (!this.object) { - return; - } - } - if (this.object?.common?.type === 'number') { - let fValue = parseFloat(value.replace(',', '.')); - if (Number.isNaN(value)) { - fValue = 0; - } - this.props.context.setValue(this.state.rxData.oid, fValue); - } else { - this.props.context.setValue(this.state.rxData.oid, value); - } - } - - onChange(value: string): void { - this.setState({ input: value }); - if (!this.state.rxData.withEnter) { - void this.setValue(value); - } - } - - renderWidgetBody(props: RxRenderWidgetProps): React.JSX.Element { - super.renderWidgetBody(props); - - props.style.overflow = 'visible'; - - let content; - const label = this.state.rxData.label; // title for back compatibility with tplJquiInputSet - if (!this.state.rxData.jquery_style && !this.state.rxData.no_style) { - content = ( - { - this.focused = true; - if (this.state.rxData.selectAllOnFocus) { - e.target.select(); - } - }} - onBlur={() => (this.focused = false)} - autoFocus={!this.props.editMode && this.state.rxData.autoFocus} - variant={this.state.rxData.variant === undefined ? 'standard' : this.state.rxData.variant} - slotProps={{ - htmlInput: { - readOnly: this.state.rxData.readOnly, - }, - input: { - endAdornment: - this.state.rxData.withEnter && !this.state.rxData.readOnly ? ( - - {this.state.rxData.buttontext ? ( - - ) : ( - this.setValue(this.state.input)} - edge="end" - > - - - )} - - ) : undefined, - startAdornment: this.state.rxData.unit ? ( - {this.state.rxData.unit} - ) : undefined, - }, - }} - label={label} - onChange={e => this.onChange(e.target.value)} - /> - ); - } else { - content = [ -

- {label} -
, - { - this.focused = true; - if (this.state.rxData.selectAllOnFocus) { - e.target.select(); - } - }} - onBlur={() => (this.focused = false)} - onChange={e => this.onChange(e.target.value)} - />, - this.state.rxData.withEnter && !this.state.rxData.readOnly ? ( - this.setValue(this.state.input)} - edge="end" - > - - - ) : undefined, - ]; - } - - return ( -
- {content} -
- ); - } -} - -export default JQuiInput; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 React from 'react'; + +import { IconButton, TextField, InputAdornment, Button } from '@mui/material'; + +import { KeyboardReturn } from '@mui/icons-material'; + +import { I18n, type LegacyConnection } from '@iobroker/adapter-react-v5'; + +import VisRxWidget, { type VisRxWidgetState } from '../../visRxWidget'; +import type { + RxRenderWidgetProps, + RxWidgetInfo, + RxWidgetInfoAttributesField, + VisBaseWidgetProps, + Writeable, +} from '@iobroker/types-vis-2'; + +type RxData = { + label: string; + oid: string; + asString: boolean; + autoFocus: boolean; + readOnly: boolean; + withEnter: boolean; + buttontext: string; + selectAllOnFocus: boolean; + unit: string; + no_style: boolean; + jquery_style: boolean; + variant: 'filled' | 'outlined' | 'standard'; + size: number; +}; + +interface JQuiInputState extends VisRxWidgetState { + input: string; +} + +class JQuiInput

extends VisRxWidget { + private focused: boolean = false; + private readonly inputRef: React.RefObject; + private jQueryDone: boolean = false; + private object: ioBroker.StateObject | null = null; + + constructor(props: VisBaseWidgetProps) { + super(props); + Object.assign(this.state, { + input: '', + }); + this.inputRef = React.createRef(); + } + + static getWidgetInfo(): RxWidgetInfo { + return { + id: 'tplJquiInput', + visSet: 'jqui', + visName: 'Input', + visWidgetLabel: 'jqui_input', + visPrev: 'widgets/jqui/img/Prev_Input.png', + visOrder: 13, + visAttrs: [ + { + name: 'common', + fields: [ + { + name: 'label', + type: 'text', + default: I18n.t('jqui_input').replace('jqui_', ''), + }, + { + name: 'oid', + type: 'id', + onChange: async ( + _field: RxWidgetInfoAttributesField, + data: RxData, + changeData: (newData: RxData) => void, + socket: LegacyConnection, + ): Promise => { + if (data.oid && data.oid !== 'nothing_selected') { + const obj = await socket.getObject(data.oid); + let changed = false; + if (obj?.common?.unit) { + if (data.unit !== obj.common.unit) { + data.unit = obj.common.unit; + changed = true; + } + } + if (obj?.common?.type !== 'number') { + if (!data.asString) { + data.asString = true; + changed = true; + } + } else if (obj?.common?.type === 'number') { + if (data.asString) { + data.asString = false; + changed = true; + } + } + changed && changeData(data); + } + }, + }, + { + name: 'asString', + type: 'checkbox', + label: 'jqui_as_string', + }, + // { + // name: 'digits', + // type: 'slider', + // min: 0, + // max: 5, + // hidden: data => !!data.asString, + // }, + { + name: 'autoFocus', + type: 'checkbox', + }, + { + name: 'readOnly', + type: 'checkbox', + label: 'jqui_read_only', + }, + { + name: 'withEnter', + type: 'checkbox', + label: 'jqui_with_enter_button', + hidden: '!!data.readOnly', + }, + { + name: 'buttontext', + type: 'text', + label: 'jqui_button_text', + hidden: '!data.withEnter || !!data.readOnly', + }, + { + name: 'selectAllOnFocus', + type: 'checkbox', + label: 'jqui_select_all_on_focus', + tooltip: 'jqui_select_all_on_focus_tooltip', + }, + { + name: 'unit', + type: 'text', + label: 'jqui_unit', + }, + ], + }, + { + name: 'style', + fields: [ + { name: 'no_style', type: 'checkbox', hidden: (data: RxData): boolean => data.jquery_style }, + { + name: 'jquery_style', + label: 'jqui_jquery_style', + type: 'checkbox', + hidden: (data: RxData): boolean => data.no_style, + }, + { + name: 'variant', + label: 'jqui_variant', + type: 'select', + noTranslation: true, + options: ['filled', 'outlined', 'standard'], + default: 'standard', + hidden: (data: RxData): boolean => data.jquery_style || data.no_style, + }, + { + name: 'size', + type: 'slider', + min: 4, + max: 100, + default: 10, + hidden: (data: RxData): boolean => !data.no_style, + }, + ], + }, + ], + }; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo(): RxWidgetInfo { + return JQuiInput.getWidgetInfo(); + } + + static findField( + widgetInfo: RxWidgetInfo, + name: string, + ): Writeable | null { + return VisRxWidget.findField(widgetInfo, name) as unknown as Writeable; + } + + async componentDidMount(): Promise { + super.componentDidMount(); + + try { + const input = await this.props.context.socket.getState(this.state.rxData.oid); + if (input && input.val !== undefined && input.val !== null) { + input.val = input.val.toString(); + this.setState({ input: input.val }); + } + } catch (error) { + console.error(`Cannot get state ${this.state.rxData.oid}: ${error}`); + } + + if ( + this.inputRef.current && + this.state.rxData.autoFocus && + !this.props.editMode && + (this.state.rxData.jquery_style || this.state.rxData.no_style) + ) { + setTimeout(() => this.inputRef.current.focus(), 100); + } + } + + onStateUpdated(id: string, state: ioBroker.State | null | undefined): void { + super.onStateUpdated(id, state); + if (state?.val || state?.val === 0) { + if (id === this.state.rxData.oid && !this.focused) { + if (state.val.toString() !== this.state.input.toString()) { + this.setState({ input: state.val as string }); + } + } + } + } + + componentDidUpdate(): void { + if (this.inputRef.current) { + if (this.state.rxData.jquery_style && !this.jQueryDone) { + this.jQueryDone = true; + (window as any).jQuery(this.inputRef.current).button().addClass('ui-state-default'); + } + } + } + + async setValue(value: string): Promise { + if (this.object?._id !== this.state.rxData.oid) { + this.object = (await this.props.context.socket.getObject(this.state.rxData.oid)) as + | ioBroker.StateObject + | null + | undefined; + if (!this.object) { + return; + } + } + if (this.object?.common?.type === 'number') { + let fValue = parseFloat(value.replace(',', '.')); + if (Number.isNaN(value)) { + fValue = 0; + } + this.props.context.setValue(this.state.rxData.oid, fValue); + } else { + this.props.context.setValue(this.state.rxData.oid, value); + } + } + + onChange(value: string): void { + this.setState({ input: value }); + if (!this.state.rxData.withEnter) { + void this.setValue(value); + } + } + + renderWidgetBody(props: RxRenderWidgetProps): React.JSX.Element { + super.renderWidgetBody(props); + + props.style.overflow = 'visible'; + + let content; + const label = this.state.rxData.label; // title for back compatibility with tplJquiInputSet + if (!this.state.rxData.jquery_style && !this.state.rxData.no_style) { + content = ( + { + this.focused = true; + if (this.state.rxData.selectAllOnFocus) { + e.target.select(); + } + }} + onBlur={() => (this.focused = false)} + autoFocus={!this.props.editMode && this.state.rxData.autoFocus} + variant={this.state.rxData.variant === undefined ? 'standard' : this.state.rxData.variant} + slotProps={{ + htmlInput: { + readOnly: this.state.rxData.readOnly, + }, + input: { + endAdornment: + this.state.rxData.withEnter && !this.state.rxData.readOnly ? ( + + {this.state.rxData.buttontext ? ( + + ) : ( + this.setValue(this.state.input)} + edge="end" + > + + + )} + + ) : undefined, + startAdornment: this.state.rxData.unit ? ( + {this.state.rxData.unit} + ) : undefined, + }, + }} + label={label} + onChange={e => this.onChange(e.target.value)} + /> + ); + } else { + content = [ +

+ {label} +
, + { + this.focused = true; + if (this.state.rxData.selectAllOnFocus) { + e.target.select(); + } + }} + onBlur={() => (this.focused = false)} + onChange={e => this.onChange(e.target.value)} + />, + this.state.rxData.withEnter && !this.state.rxData.readOnly ? ( + this.setValue(this.state.input)} + edge="end" + > + + + ) : undefined, + ]; + } + + return ( +
+ {content} +
+ ); + } +} + +export default JQuiInput; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiInputDate.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiInputDate.tsx index 9ccb7e5b..2adce23c 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiInputDate.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiInputDate.tsx @@ -1,201 +1,201 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 React from 'react'; -import dayjs from 'dayjs'; - -import { DatePicker, LocalizationProvider } from '@mui/x-date-pickers'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import 'dayjs/locale/de'; -import 'dayjs/locale/en'; -import 'dayjs/locale/ru'; -import 'dayjs/locale/zh-cn'; -import 'dayjs/locale/uk'; -import 'dayjs/locale/it'; -import 'dayjs/locale/fr'; -import 'dayjs/locale/es'; -import 'dayjs/locale/pl'; -import 'dayjs/locale/pt'; -import 'dayjs/locale/nl'; - -import type { TextFieldVariants } from '@mui/material'; - -import type { RxRenderWidgetProps, RxWidgetInfo } from '@iobroker/types-vis-2'; -import VisRxWidget from '../../visRxWidget'; - -const styles: { textRoot: { [key: string]: React.CSSProperties } } = { - textRoot: { - '& .MuiInputBase-root': { - width: '100%', - height: '100%', - }, - }, -}; - -type RxData = { - oid: string; - variant: TextFieldVariants; - autoFocus: boolean; - clearable: boolean; - widgetTitle: string; - disableFuture: boolean; - disablePast: boolean; - asFullDate: boolean; - displayWeekNumber: boolean; - wideFormat: boolean; -}; - -class JQuiInputDate extends VisRxWidget { - /** If a user does not want to use full date */ - private readonly EASY_DATE_FORMAT = 'DD.MM.YYYY'; - - static getWidgetInfo(): RxWidgetInfo { - return { - id: 'tplJquiInputDate', - visSet: 'jqui', - visName: 'Input Date', - visWidgetLabel: 'jqui_input_date', - visPrev: 'widgets/jqui/img/Prev_InputDate.png', - visOrder: 30, - visAttrs: [ - { - name: 'common', - fields: [ - { - name: 'oid', - type: 'id', - }, - { - name: 'variant', - label: 'jqui_variant', - type: 'select', - options: [ - { label: 'standard', value: 'standard' }, - { label: 'outlined', value: 'outlined' }, - { label: 'filled', value: 'filled' }, - ], - default: 'standard', - }, - { - name: 'autoFocus', - type: 'checkbox', - }, - { - name: 'clearable', - label: 'jqui_clearable', - type: 'checkbox', - }, - { - name: 'widgetTitle', - label: 'jqui_widgetTitle', - type: 'text', - }, - { - name: 'disableFuture', - label: 'jqui_disableFuture', - type: 'checkbox', - hidden: '!!data.disablePast', - }, - { - name: 'disablePast', - label: 'jqui_disablePast', - type: 'checkbox', - hidden: '!!data.disableFuture', - }, - { - name: 'asFullDate', - label: 'jqui_asFullDate', - type: 'checkbox', - }, - { - name: 'displayWeekNumber', - label: 'jqui_displayWeekNumber', - type: 'checkbox', - }, - { - name: 'wideFormat', - label: 'jqui_wideFormat', - type: 'checkbox', - }, - ], - }, - ], - visDefaultStyle: { - width: 250, - height: 56, - }, - } as const; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo(): RxWidgetInfo { - return JQuiInputDate.getWidgetInfo(); - } - - renderWidgetBody(props: RxRenderWidgetProps): React.JSX.Element { - super.renderWidgetBody(props); - props.style.overflow = 'visible'; - - return ( -
- - { - if (!newValue) { - return; - } - - const val = this.state.rxData.asFullDate - ? newValue.toDate().toString() - : newValue.format(this.EASY_DATE_FORMAT); - this.props.context.setValue(this.state.rxData.oid, val); - }} - formatDensity={this.state.rxData.wideFormat ? 'spacious' : 'dense'} - slotProps={{ - textField: { - variant: this.state.rxData.variant || 'standard', - style: { - width: '100%', - height: '100%', - }, - sx: styles.textRoot, - }, - field: { - clearable: this.state.rxData.clearable, - onClear: () => this.props.context.setValue(this.state.rxData.oid, ''), - }, - }} - disableFuture={this.state.rxData.disableFuture || false} - disablePast={this.state.rxData.disablePast || false} - displayWeekNumber={this.state.rxData.displayWeekNumber || false} - /> - -
- ); - } -} - -export default JQuiInputDate; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 React from 'react'; +import dayjs from 'dayjs'; + +import { DatePicker, LocalizationProvider } from '@mui/x-date-pickers'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import 'dayjs/locale/de'; +import 'dayjs/locale/en'; +import 'dayjs/locale/ru'; +import 'dayjs/locale/zh-cn'; +import 'dayjs/locale/uk'; +import 'dayjs/locale/it'; +import 'dayjs/locale/fr'; +import 'dayjs/locale/es'; +import 'dayjs/locale/pl'; +import 'dayjs/locale/pt'; +import 'dayjs/locale/nl'; + +import type { TextFieldVariants } from '@mui/material'; + +import type { RxRenderWidgetProps, RxWidgetInfo } from '@iobroker/types-vis-2'; +import VisRxWidget from '../../visRxWidget'; + +const styles: { textRoot: { [key: string]: React.CSSProperties } } = { + textRoot: { + '& .MuiInputBase-root': { + width: '100%', + height: '100%', + }, + }, +}; + +type RxData = { + oid: string; + variant: TextFieldVariants; + autoFocus: boolean; + clearable: boolean; + widgetTitle: string; + disableFuture: boolean; + disablePast: boolean; + asFullDate: boolean; + displayWeekNumber: boolean; + wideFormat: boolean; +}; + +class JQuiInputDate extends VisRxWidget { + /** If a user does not want to use full date */ + private readonly EASY_DATE_FORMAT = 'DD.MM.YYYY'; + + static getWidgetInfo(): RxWidgetInfo { + return { + id: 'tplJquiInputDate', + visSet: 'jqui', + visName: 'Input Date', + visWidgetLabel: 'jqui_input_date', + visPrev: 'widgets/jqui/img/Prev_InputDate.png', + visOrder: 30, + visAttrs: [ + { + name: 'common', + fields: [ + { + name: 'oid', + type: 'id', + }, + { + name: 'variant', + label: 'jqui_variant', + type: 'select', + options: [ + { label: 'standard', value: 'standard' }, + { label: 'outlined', value: 'outlined' }, + { label: 'filled', value: 'filled' }, + ], + default: 'standard', + }, + { + name: 'autoFocus', + type: 'checkbox', + }, + { + name: 'clearable', + label: 'jqui_clearable', + type: 'checkbox', + }, + { + name: 'widgetTitle', + label: 'jqui_widgetTitle', + type: 'text', + }, + { + name: 'disableFuture', + label: 'jqui_disableFuture', + type: 'checkbox', + hidden: '!!data.disablePast', + }, + { + name: 'disablePast', + label: 'jqui_disablePast', + type: 'checkbox', + hidden: '!!data.disableFuture', + }, + { + name: 'asFullDate', + label: 'jqui_asFullDate', + type: 'checkbox', + }, + { + name: 'displayWeekNumber', + label: 'jqui_displayWeekNumber', + type: 'checkbox', + }, + { + name: 'wideFormat', + label: 'jqui_wideFormat', + type: 'checkbox', + }, + ], + }, + ], + visDefaultStyle: { + width: 250, + height: 56, + }, + } as const; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo(): RxWidgetInfo { + return JQuiInputDate.getWidgetInfo(); + } + + renderWidgetBody(props: RxRenderWidgetProps): React.JSX.Element { + super.renderWidgetBody(props); + props.style.overflow = 'visible'; + + return ( +
+ + { + if (!newValue) { + return; + } + + const val = this.state.rxData.asFullDate + ? newValue.toDate().toString() + : newValue.format(this.EASY_DATE_FORMAT); + this.props.context.setValue(this.state.rxData.oid, val); + }} + formatDensity={this.state.rxData.wideFormat ? 'spacious' : 'dense'} + slotProps={{ + textField: { + variant: this.state.rxData.variant || 'standard', + style: { + width: '100%', + height: '100%', + }, + sx: styles.textRoot, + }, + field: { + clearable: this.state.rxData.clearable, + onClear: () => this.props.context.setValue(this.state.rxData.oid, ''), + }, + }} + disableFuture={this.state.rxData.disableFuture || false} + disablePast={this.state.rxData.disablePast || false} + displayWeekNumber={this.state.rxData.displayWeekNumber || false} + /> + +
+ ); + } +} + +export default JQuiInputDate; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiInputDateTime.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiInputDateTime.tsx index 6db590c7..2f2d89f7 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiInputDateTime.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiInputDateTime.tsx @@ -1,195 +1,195 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 React from 'react'; - -import { TimePicker, LocalizationProvider } from '@mui/x-date-pickers'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import dayjs from 'dayjs'; -import 'dayjs/locale/de'; -import 'dayjs/locale/en'; -import 'dayjs/locale/ru'; -import 'dayjs/locale/zh-cn'; -import 'dayjs/locale/uk'; -import 'dayjs/locale/it'; -import 'dayjs/locale/fr'; -import 'dayjs/locale/es'; -import 'dayjs/locale/pl'; -import 'dayjs/locale/pt'; -import 'dayjs/locale/nl'; - -import type { TextFieldVariants } from '@mui/material'; - -import type { RxRenderWidgetProps, RxWidgetInfo } from '@iobroker/types-vis-2'; -import VisRxWidget from '../../visRxWidget'; - -const styles: { textRoot: { [key: string]: React.CSSProperties } } = { - textRoot: { - '& .MuiInputBase-root': { - width: '100%', - height: '100%', - }, - }, -}; - -type RxData = { - oid: string; - variant: TextFieldVariants; - autoFocus: boolean; - clearable: boolean; - widgetTitle: string; - ampm: boolean; - asDate: boolean; - stepMinute: number; -}; - -class JQuiInputDateTime extends VisRxWidget { - static getWidgetInfo(): RxWidgetInfo { - return { - id: 'tplJquiInputDatetime', - visSet: 'jqui', - visName: 'Input Time', - visWidgetLabel: 'jqui_input_time', - visPrev: 'widgets/jqui/img/Prev_InputDateTime.png', - visOrder: 31, - visAttrs: [ - { - name: 'common', - fields: [ - { - name: 'oid', - type: 'id', - }, - { - name: 'variant', - label: 'jqui_variant', - type: 'select', - options: [ - { label: 'standard', value: 'standard' }, - { label: 'outlined', value: 'outlined' }, - { label: 'filled', value: 'filled' }, - ], - default: 'standard', - }, - { - name: 'autoFocus', - type: 'checkbox', - }, - { - name: 'clearable', - label: 'jqui_clearable', - type: 'checkbox', - }, - { - name: 'widgetTitle', - label: 'jqui_widgetTitle', - type: 'text', - }, - { - name: 'ampm', - label: 'jqui_ampm', - type: 'checkbox', - }, - { - name: 'asDate', - label: 'jqui_asDate', - type: 'checkbox', - }, - { - name: 'stepMinute', - label: 'jqui_stepMinute', - type: 'select', - options: [ - { label: '1 minute', value: 1 }, - { label: '2 minutes', value: 2 }, - { label: '3 minutes', value: 3 }, - { label: '4 minutes', value: 4 }, - { label: '5 minutes', value: 5 }, - { label: '10 minutes', value: 10 }, - { label: '15 minutes', value: 15 }, - { label: '20 minutes', value: 20 }, - { label: '30 minutes', value: 30 }, - { label: '60 minutes', value: 60 }, - ], - }, - ], - }, - ], - visDefaultStyle: { - width: 250, - height: 56, - }, - } as const; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo(): RxWidgetInfo { - return JQuiInputDateTime.getWidgetInfo(); - } - - renderWidgetBody(props: RxRenderWidgetProps): React.JSX.Element { - super.renderWidgetBody(props); - - const val = this.state.values[`${this.state.rxData.oid}.val`]; - const asDate = this.state.rxData.asDate; - props.style.overflow = 'visible'; - - return ( -
- - { - if (!value) { - return; - } - - const res = !asDate - ? value.format('HH:mm') - : value.second(0).millisecond(0).toDate().toString(); - - this.props.context.setValue(this.state.rxData.oid, res); - }} - slotProps={{ - textField: { - variant: this.state.rxData.variant || 'standard', - style: { - width: '100%', - height: '100%', - }, - sx: styles.textRoot, - }, - field: { - clearable: this.state.rxData.clearable, - onClear: () => this.props.context.setValue(this.state.rxData.oid, ''), - }, - }} - /> - -
- ); - } -} - -export default JQuiInputDateTime; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 React from 'react'; + +import { TimePicker, LocalizationProvider } from '@mui/x-date-pickers'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import dayjs from 'dayjs'; +import 'dayjs/locale/de'; +import 'dayjs/locale/en'; +import 'dayjs/locale/ru'; +import 'dayjs/locale/zh-cn'; +import 'dayjs/locale/uk'; +import 'dayjs/locale/it'; +import 'dayjs/locale/fr'; +import 'dayjs/locale/es'; +import 'dayjs/locale/pl'; +import 'dayjs/locale/pt'; +import 'dayjs/locale/nl'; + +import type { TextFieldVariants } from '@mui/material'; + +import type { RxRenderWidgetProps, RxWidgetInfo } from '@iobroker/types-vis-2'; +import VisRxWidget from '../../visRxWidget'; + +const styles: { textRoot: { [key: string]: React.CSSProperties } } = { + textRoot: { + '& .MuiInputBase-root': { + width: '100%', + height: '100%', + }, + }, +}; + +type RxData = { + oid: string; + variant: TextFieldVariants; + autoFocus: boolean; + clearable: boolean; + widgetTitle: string; + ampm: boolean; + asDate: boolean; + stepMinute: number; +}; + +class JQuiInputDateTime extends VisRxWidget { + static getWidgetInfo(): RxWidgetInfo { + return { + id: 'tplJquiInputDatetime', + visSet: 'jqui', + visName: 'Input Time', + visWidgetLabel: 'jqui_input_time', + visPrev: 'widgets/jqui/img/Prev_InputDateTime.png', + visOrder: 31, + visAttrs: [ + { + name: 'common', + fields: [ + { + name: 'oid', + type: 'id', + }, + { + name: 'variant', + label: 'jqui_variant', + type: 'select', + options: [ + { label: 'standard', value: 'standard' }, + { label: 'outlined', value: 'outlined' }, + { label: 'filled', value: 'filled' }, + ], + default: 'standard', + }, + { + name: 'autoFocus', + type: 'checkbox', + }, + { + name: 'clearable', + label: 'jqui_clearable', + type: 'checkbox', + }, + { + name: 'widgetTitle', + label: 'jqui_widgetTitle', + type: 'text', + }, + { + name: 'ampm', + label: 'jqui_ampm', + type: 'checkbox', + }, + { + name: 'asDate', + label: 'jqui_asDate', + type: 'checkbox', + }, + { + name: 'stepMinute', + label: 'jqui_stepMinute', + type: 'select', + options: [ + { label: '1 minute', value: 1 }, + { label: '2 minutes', value: 2 }, + { label: '3 minutes', value: 3 }, + { label: '4 minutes', value: 4 }, + { label: '5 minutes', value: 5 }, + { label: '10 minutes', value: 10 }, + { label: '15 minutes', value: 15 }, + { label: '20 minutes', value: 20 }, + { label: '30 minutes', value: 30 }, + { label: '60 minutes', value: 60 }, + ], + }, + ], + }, + ], + visDefaultStyle: { + width: 250, + height: 56, + }, + } as const; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo(): RxWidgetInfo { + return JQuiInputDateTime.getWidgetInfo(); + } + + renderWidgetBody(props: RxRenderWidgetProps): React.JSX.Element { + super.renderWidgetBody(props); + + const val = this.state.values[`${this.state.rxData.oid}.val`]; + const asDate = this.state.rxData.asDate; + props.style.overflow = 'visible'; + + return ( +
+ + { + if (!value) { + return; + } + + const res = !asDate + ? value.format('HH:mm') + : value.second(0).millisecond(0).toDate().toString(); + + this.props.context.setValue(this.state.rxData.oid, res); + }} + slotProps={{ + textField: { + variant: this.state.rxData.variant || 'standard', + style: { + width: '100%', + height: '100%', + }, + sx: styles.textRoot, + }, + field: { + clearable: this.state.rxData.clearable, + onClear: () => this.props.context.setValue(this.state.rxData.oid, ''), + }, + }} + /> + +
+ ); + } +} + +export default JQuiInputDateTime; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiInputSet.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiInputSet.tsx index 1112087f..787b2e0a 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiInputSet.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiInputSet.tsx @@ -1,68 +1,68 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 JQuiInput from './JQuiInput'; -import type { - RxWidgetInfo, - RxWidgetInfoAttributesField, - RxWidgetInfoAttributesFieldCheckbox, - RxWidgetInfoAttributesFieldSimple, - Writeable, -} from '@iobroker/types-vis-2'; - -class JQuiInputSet extends JQuiInput { - static getWidgetInfo(): RxWidgetInfo { - const widgetInfo = JQuiInput.getWidgetInfo(); - const newWidgetInfo: RxWidgetInfo = { - id: 'tplJquiInputSet', - visSet: 'jqui', - visName: 'Input + Button', - visWidgetLabel: 'jqui_input_with_button', - visPrev: 'widgets/jqui/img/Prev_InputWithButton.png', - visOrder: 14, - visAttrs: widgetInfo.visAttrs, - visDefaultStyle: { - width: 150, - height: 45, - }, - }; - - // Add note - (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ - name: '_note', - type: 'help', - text: 'jqui_button_input_note', - }); - - const withEnter = JQuiInput.findField(newWidgetInfo, 'withEnter'); - if (withEnter) { - withEnter.default = true; - } - - const buttonText = JQuiInput.findField(newWidgetInfo, 'buttontext'); - if (buttonText) { - buttonText.default = 'OK'; - } - - return newWidgetInfo; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo(): RxWidgetInfo { - return JQuiInputSet.getWidgetInfo(); - } -} - -export default JQuiInputSet; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 JQuiInput from './JQuiInput'; +import type { + RxWidgetInfo, + RxWidgetInfoAttributesField, + RxWidgetInfoAttributesFieldCheckbox, + RxWidgetInfoAttributesFieldSimple, + Writeable, +} from '@iobroker/types-vis-2'; + +class JQuiInputSet extends JQuiInput { + static getWidgetInfo(): RxWidgetInfo { + const widgetInfo = JQuiInput.getWidgetInfo(); + const newWidgetInfo: RxWidgetInfo = { + id: 'tplJquiInputSet', + visSet: 'jqui', + visName: 'Input + Button', + visWidgetLabel: 'jqui_input_with_button', + visPrev: 'widgets/jqui/img/Prev_InputWithButton.png', + visOrder: 14, + visAttrs: widgetInfo.visAttrs, + visDefaultStyle: { + width: 150, + height: 45, + }, + }; + + // Add note + (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ + name: '_note', + type: 'help', + text: 'jqui_button_input_note', + }); + + const withEnter = JQuiInput.findField(newWidgetInfo, 'withEnter'); + if (withEnter) { + withEnter.default = true; + } + + const buttonText = JQuiInput.findField(newWidgetInfo, 'buttontext'); + if (buttonText) { + buttonText.default = 'OK'; + } + + return newWidgetInfo; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo(): RxWidgetInfo { + return JQuiInputSet.getWidgetInfo(); + } +} + +export default JQuiInputSet; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiRadio.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiRadio.tsx index f6ea7434..a30c4103 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiRadio.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiRadio.tsx @@ -1,62 +1,62 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 JQuiBinaryState from './JQuiBinaryState'; -import type { - RxWidgetInfo, - RxWidgetInfoAttributesFieldSelect, - Writeable, - RxWidgetInfoAttributesField, -} from '@iobroker/types-vis-2'; - -class JQuiRadio extends JQuiBinaryState { - static getWidgetInfo(): RxWidgetInfo { - const widgetInfo: RxWidgetInfo = JQuiBinaryState.getWidgetInfo(); - const newWidgetInfo: RxWidgetInfo = { - id: 'tplJquiRadio', - visSet: 'jqui', - visName: 'Radiobuttons on/off', - visWidgetLabel: 'jqui_radio_buttons_on_off', - visPrev: 'widgets/jqui/img/Prev_RadioButtonsOnOff.png', - visOrder: 15, - visAttrs: widgetInfo.visAttrs, - visDefaultStyle: { - width: 150, - height: 45, - }, - }; - - // Add note - (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ - name: '_note', - type: 'help', - text: 'jqui_button_binary_control_note', - }); - - const type = JQuiBinaryState.findField(newWidgetInfo, 'type'); - if (type) { - type.default = 'radio'; - } - - return newWidgetInfo; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo(): RxWidgetInfo { - return JQuiRadio.getWidgetInfo(); - } -} - -export default JQuiRadio; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 JQuiBinaryState from './JQuiBinaryState'; +import type { + RxWidgetInfo, + RxWidgetInfoAttributesFieldSelect, + Writeable, + RxWidgetInfoAttributesField, +} from '@iobroker/types-vis-2'; + +class JQuiRadio extends JQuiBinaryState { + static getWidgetInfo(): RxWidgetInfo { + const widgetInfo: RxWidgetInfo = JQuiBinaryState.getWidgetInfo(); + const newWidgetInfo: RxWidgetInfo = { + id: 'tplJquiRadio', + visSet: 'jqui', + visName: 'Radiobuttons on/off', + visWidgetLabel: 'jqui_radio_buttons_on_off', + visPrev: 'widgets/jqui/img/Prev_RadioButtonsOnOff.png', + visOrder: 15, + visAttrs: widgetInfo.visAttrs, + visDefaultStyle: { + width: 150, + height: 45, + }, + }; + + // Add note + (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ + name: '_note', + type: 'help', + text: 'jqui_button_binary_control_note', + }); + + const type = JQuiBinaryState.findField(newWidgetInfo, 'type'); + if (type) { + type.default = 'radio'; + } + + return newWidgetInfo; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo(): RxWidgetInfo { + return JQuiRadio.getWidgetInfo(); + } +} + +export default JQuiRadio; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiRadioList.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiRadioList.tsx index 42af0bb0..44ea8cf5 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiRadioList.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiRadioList.tsx @@ -1,60 +1,60 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 JQuiState from './JQuiState'; -import type { - RxWidgetInfo, - RxWidgetInfoAttributesFieldSelect, - Writeable, - RxWidgetInfoAttributesField, -} from '@iobroker/types-vis-2'; - -class JQuiRadioList extends JQuiState { - static getWidgetInfo(): RxWidgetInfo { - const widgetInfo: RxWidgetInfo = JQuiState.getWidgetInfo(); - const newWidgetInfo: RxWidgetInfo = { - id: 'tplJquiRadioList', - visSet: 'jqui', - visName: 'Radiobuttons ValueList', - visWidgetLabel: 'jqui_radio_list', - visPrev: 'widgets/jqui/img/Prev_RadioList.png', - visOrder: 15, - visAttrs: widgetInfo.visAttrs, - visDefaultStyle: { - width: 250, - height: 45, - }, - }; - - const type = JQuiState.findField(newWidgetInfo, 'type'); - type.default = 'radio'; - - // Add note - (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ - name: '_note', - type: 'help', - text: 'jqui_state_note', - }); - - return newWidgetInfo; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo(): RxWidgetInfo { - return JQuiRadioList.getWidgetInfo(); - } -} - -export default JQuiRadioList; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 JQuiState from './JQuiState'; +import type { + RxWidgetInfo, + RxWidgetInfoAttributesFieldSelect, + Writeable, + RxWidgetInfoAttributesField, +} from '@iobroker/types-vis-2'; + +class JQuiRadioList extends JQuiState { + static getWidgetInfo(): RxWidgetInfo { + const widgetInfo: RxWidgetInfo = JQuiState.getWidgetInfo(); + const newWidgetInfo: RxWidgetInfo = { + id: 'tplJquiRadioList', + visSet: 'jqui', + visName: 'Radiobuttons ValueList', + visWidgetLabel: 'jqui_radio_list', + visPrev: 'widgets/jqui/img/Prev_RadioList.png', + visOrder: 15, + visAttrs: widgetInfo.visAttrs, + visDefaultStyle: { + width: 250, + height: 45, + }, + }; + + const type = JQuiState.findField(newWidgetInfo, 'type'); + type.default = 'radio'; + + // Add note + (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ + name: '_note', + type: 'help', + text: 'jqui_state_note', + }); + + return newWidgetInfo; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo(): RxWidgetInfo { + return JQuiRadioList.getWidgetInfo(); + } +} + +export default JQuiRadioList; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiRadioSteps.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiRadioSteps.tsx index 54d42625..94c01c9f 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiRadioSteps.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiRadioSteps.tsx @@ -1,52 +1,52 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 JQuiState from './JQuiState'; -import type { RxWidgetInfo, Writeable, RxWidgetInfoAttributesField } from '@iobroker/types-vis-2'; - -class JQuiRadioSteps extends JQuiState { - static getWidgetInfo(): RxWidgetInfo { - const widgetInfo: RxWidgetInfo = JQuiState.getWidgetInfo(); - const newWidgetInfo: RxWidgetInfo = { - id: 'tplJquiRadioSteps', - visSet: 'jqui', - visName: 'Radiobuttons 25%', - visWidgetLabel: 'jqui_radio_steps', - visPrev: 'widgets/jqui/img/Prev_RadioSteps.png', - visOrder: 25, - visAttrs: widgetInfo.visAttrs, - visDefaultStyle: { - width: 350, - height: 35, - }, - }; - - // Add note - (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ - name: '_note', - type: 'help', - text: 'jqui_state_note', - }); - - return newWidgetInfo; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo(): RxWidgetInfo { - return JQuiRadioSteps.getWidgetInfo(); - } -} - -export default JQuiRadioSteps; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 JQuiState from './JQuiState'; +import type { RxWidgetInfo, Writeable, RxWidgetInfoAttributesField } from '@iobroker/types-vis-2'; + +class JQuiRadioSteps extends JQuiState { + static getWidgetInfo(): RxWidgetInfo { + const widgetInfo: RxWidgetInfo = JQuiState.getWidgetInfo(); + const newWidgetInfo: RxWidgetInfo = { + id: 'tplJquiRadioSteps', + visSet: 'jqui', + visName: 'Radiobuttons 25%', + visWidgetLabel: 'jqui_radio_steps', + visPrev: 'widgets/jqui/img/Prev_RadioSteps.png', + visOrder: 25, + visAttrs: widgetInfo.visAttrs, + visDefaultStyle: { + width: 350, + height: 35, + }, + }; + + // Add note + (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ + name: '_note', + type: 'help', + text: 'jqui_state_note', + }); + + return newWidgetInfo; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo(): RxWidgetInfo { + return JQuiRadioSteps.getWidgetInfo(); + } +} + +export default JQuiRadioSteps; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiSelectList.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiSelectList.tsx index 654d9eb9..c81804ef 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiSelectList.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiSelectList.tsx @@ -1,61 +1,61 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 JQuiState from './JQuiState'; -import type { - RxWidgetInfo, - RxWidgetInfoAttributesField, - RxWidgetInfoAttributesFieldSelect, - Writeable, -} from '@iobroker/types-vis-2'; - -class JQuiSelectList extends JQuiState { - static getWidgetInfo(): RxWidgetInfo { - const widgetInfo = JQuiState.getWidgetInfo(); - const newWidgetInfo = { - id: 'tplJquiSelectList', - visSet: 'jqui', - visName: 'Select ValueList', - visWidgetLabel: 'jqui_select_list', - visPrev: 'widgets/jqui/img/Prev_SelectList.png', - visOrder: 16, - visAttrs: widgetInfo.visAttrs, - visDefaultStyle: { - width: 250, - height: 45, - }, - }; - - const type: Writeable = - JQuiState.findField(newWidgetInfo, 'type'); - type.default = 'select'; - - // Add note - (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ - name: '_note', - type: 'help', - text: 'jqui_state_note', - }); - - return newWidgetInfo; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo(): RxWidgetInfo { - return JQuiSelectList.getWidgetInfo(); - } -} - -export default JQuiSelectList; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 JQuiState from './JQuiState'; +import type { + RxWidgetInfo, + RxWidgetInfoAttributesField, + RxWidgetInfoAttributesFieldSelect, + Writeable, +} from '@iobroker/types-vis-2'; + +class JQuiSelectList extends JQuiState { + static getWidgetInfo(): RxWidgetInfo { + const widgetInfo = JQuiState.getWidgetInfo(); + const newWidgetInfo = { + id: 'tplJquiSelectList', + visSet: 'jqui', + visName: 'Select ValueList', + visWidgetLabel: 'jqui_select_list', + visPrev: 'widgets/jqui/img/Prev_SelectList.png', + visOrder: 16, + visAttrs: widgetInfo.visAttrs, + visDefaultStyle: { + width: 250, + height: 45, + }, + }; + + const type: Writeable = + JQuiState.findField(newWidgetInfo, 'type'); + type.default = 'select'; + + // Add note + (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ + name: '_note', + type: 'help', + text: 'jqui_state_note', + }); + + return newWidgetInfo; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo(): RxWidgetInfo { + return JQuiSelectList.getWidgetInfo(); + } +} + +export default JQuiSelectList; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiSlider.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiSlider.tsx index 2449f3b9..e1df3d18 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiSlider.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiSlider.tsx @@ -2,7 +2,7 @@ * ioBroker.vis-2 * https://github.com/ioBroker/ioBroker.vis-2 * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, * Creative Common Attribution-NonCommercial (CC BY-NC) * * http://creativecommons.org/licenses/by-nc/4.0/ diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiSliderVertical.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiSliderVertical.tsx index 7879a894..3b4f5db6 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiSliderVertical.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiSliderVertical.tsx @@ -1,58 +1,58 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 JQuiSlider from './JQuiSlider'; -import type { - RxWidgetInfo, - RxWidgetInfoAttributesFieldSelect, - Writeable, - RxWidgetInfoAttributesField, -} from '@iobroker/types-vis-2'; - -class JQuiSliderVertical extends JQuiSlider { - static getWidgetInfo(): RxWidgetInfo { - const widgetInfo: RxWidgetInfo = JQuiSlider.getWidgetInfo(); - const newWidgetInfo: RxWidgetInfo = { - id: 'tplJquiSliderVertical', - visSet: 'jqui', - visName: 'Vertical slider ', - visWidgetLabel: 'jqui_slider_vertical', - visPrev: 'widgets/jqui/img/Prev_SliderVertical.png', - visOrder: 25, - visAttrs: widgetInfo.visAttrs, - visDefaultStyle: { - width: 40, - height: 300, - }, - }; - (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ - name: '_note', - type: 'help', - text: 'jqui_slider_note', - }); - - const orientation = JQuiSlider.findField(newWidgetInfo, 'orientation'); - orientation.default = 'vertical'; - - return newWidgetInfo; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo(): RxWidgetInfo { - return JQuiSliderVertical.getWidgetInfo(); - } -} - -export default JQuiSliderVertical; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 JQuiSlider from './JQuiSlider'; +import type { + RxWidgetInfo, + RxWidgetInfoAttributesFieldSelect, + Writeable, + RxWidgetInfoAttributesField, +} from '@iobroker/types-vis-2'; + +class JQuiSliderVertical extends JQuiSlider { + static getWidgetInfo(): RxWidgetInfo { + const widgetInfo: RxWidgetInfo = JQuiSlider.getWidgetInfo(); + const newWidgetInfo: RxWidgetInfo = { + id: 'tplJquiSliderVertical', + visSet: 'jqui', + visName: 'Vertical slider ', + visWidgetLabel: 'jqui_slider_vertical', + visPrev: 'widgets/jqui/img/Prev_SliderVertical.png', + visOrder: 25, + visAttrs: widgetInfo.visAttrs, + visDefaultStyle: { + width: 40, + height: 300, + }, + }; + (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ + name: '_note', + type: 'help', + text: 'jqui_slider_note', + }); + + const orientation = JQuiSlider.findField(newWidgetInfo, 'orientation'); + orientation.default = 'vertical'; + + return newWidgetInfo; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo(): RxWidgetInfo { + return JQuiSliderVertical.getWidgetInfo(); + } +} + +export default JQuiSliderVertical; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiState.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiState.tsx index 647c6cc0..9d03ac6b 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiState.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiState.tsx @@ -1,907 +1,907 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 React from 'react'; - -import { - Button, - Tooltip, - ButtonGroup, - Radio, - RadioGroup, - FormControlLabel, - MenuItem, - Select, - FormControl, - InputLabel, - FormLabel, - Slider, - List, -} from '@mui/material'; - -import { I18n, Icon, type LegacyConnection } from '@iobroker/adapter-react-v5'; - -import VisBaseWidget from '@/Vis/visBaseWidget'; -import { deepClone } from '@/Utils/utils'; - -import VisRxWidget, { type VisRxWidgetState } from '../../visRxWidget'; -import BulkEditor from './BulkEditor'; -import type { - RxRenderWidgetProps, - RxWidgetInfo, - RxWidgetInfoAttributesField, - RxWidgetInfoAttributesFieldCheckbox, - RxWidgetInfoCustomComponentProperties, - VisBaseWidgetProps, - WidgetData, - Writeable, -} from '@iobroker/types-vis-2'; - -interface BulkEditorData { - variant?: 'outlined' | 'contained'; - type: 'select' | 'radio'; - oid: string; - count: number; - [colors: `color${number}`]: string; - [values: `value${number}`]: string | number; - [values: `text${number}`]: string; - [values: `icon${number}`]: string | null; - [values: `g_states-${number}`]: boolean; - [values: `image${number}`]: string; - [values: `activeColor${number}`]: string; - [values: `tooltip${number}`]: string; -} - -type RxData = { - oid: string; - count: number; - - type: 'button' | 'select' | 'radio' | 'slider'; - readOnly: boolean; - click_id: string; - variant: 'contained' | 'outlined' | 'text' | 'standard'; - orientation: 'horizontal' | 'vertical'; - widgetTitle: string; - timeout: number; - open: boolean; - - [key: `value${number}`]: string | number; - [key: `color${number}`]: string; - [key: `text${number}`]: string; - [key: `icon${number}`]: string | null; - [key: `g_states-${number}`]: boolean; - [key: `image${number}`]: string; - [key: `activeColor${number}`]: string; - [key: `tooltip${number}`]: string; - - [key: `onlyIcon${number}`]: boolean; - [key: `test${number}`]: boolean; -}; - -interface JQuiStateState extends VisRxWidgetState { - value: string | number | boolean; - object: ioBroker.StateObject | null | false; -} - -class JQuiState

extends VisRxWidget { - private controlTimeout: ReturnType | null = null; - - constructor(props: VisBaseWidgetProps) { - super(props); - Object.assign(this.state, { - value: '', - object: null, - }); - } - - static getWidgetInfo(): RxWidgetInfo { - return { - id: 'tplJquiButtonState', - visSet: 'jqui', - visName: 'States control', - visWidgetLabel: 'jqui_states_control', - visPrev: 'widgets/jqui/img/Prev_ButtonState.png', - visOrder: 14, - visAttrs: [ - { - name: 'common', - fields: [ - { - name: 'type', - label: 'jqui_type', - type: 'select', - noTranslation: true, - default: 'button', - options: ['button', 'select', 'radio', 'slider'], - }, - { - name: 'oid', - type: 'id', - onChange: async ( - _field: RxWidgetInfoAttributesField, - data: RxData, - changeData: (newData: RxData) => void, - socket: LegacyConnection, - ): Promise => { - if (data.oid) { - // unknown bug by compilation - if (await (BulkEditor.generateFields as any)(data, socket)) { - changeData(data); - } - } - }, - }, - { - name: 'readOnly', - type: 'checkbox', - }, - { - name: 'click_id', - type: 'id', - noSubscribe: true, - hidden: (data: Record): boolean => !!data.readOnly, - }, - { - name: 'count', - type: 'slider', - min: 0, - default: 1, - max: 10, - hidden: (data: Record): boolean => !!data.percents, - }, - { - type: 'custom', - component: ( - _field: RxWidgetInfoAttributesField, - data: WidgetData, - onDataChange: (newData: WidgetData) => void, - props: RxWidgetInfoCustomComponentProperties, - ) => ( - - ), - }, - { - name: 'variant', - label: 'jqui_variant', - type: 'select', - noTranslation: true, - options: ['contained', 'outlined', 'text', 'standard'], - default: 'contained', - hidden: (data: Record): boolean => - data.type !== 'button' && data.type !== 'select', - }, - { - name: 'orientation', - label: 'orientation', - type: 'select', - options: ['horizontal', 'vertical'], - default: 'horizontal', - hidden: (data: Record): boolean => - data.type !== 'button' && data.type !== 'slider', - }, - { - name: 'widgetTitle', - label: 'jqui_name', - type: 'text', - }, - { - name: 'timeout', - label: 'jqui_set_timeout', - type: 'number', - hidden: (data: Record): boolean => data.type !== 'slider', - }, - { - name: 'open', - label: 'jqui_open', - type: 'checkbox', - hidden: (data: Record): boolean => data.type !== 'select', - }, - ], - }, - { - name: 'states', - label: 'jqui_group_value', - indexFrom: 1, - indexTo: 'count', - hidden: (data: Record): boolean => !!data.percents, - fields: [ - { - name: 'value', - type: 'text', - label: 'jqui_value', - default: '0', - }, - { - name: 'test', - type: 'checkbox', - label: 'jqui_test', - onChange: ( - field: RxWidgetInfoAttributesField, - data: Record, - changeData: (newData: Record) => void, - _socket: LegacyConnection, - index?: number, - ): Promise => { - if (data[(field as RxWidgetInfoAttributesFieldCheckbox).name]) { - let changed = false; - // deactivate all other tests - for (let i = 1; i <= data.count; i++) { - if (i !== index) { - if (data[`test${i}`]) { - changed = true; - data[`test${i}`] = false; - } - } - } - changed && changeData(data); - } - return Promise.resolve(); - }, - hidden: (data, index) => - data.type === 'slider' || - data[`value${index}`] === '' || - data[`value${index}`] === null || - data[`value${index}`] === undefined, - }, - { - name: 'onlyIcon', - type: 'checkbox', - label: 'jqui_only_icon', - }, - { - name: 'text', - default: I18n.t('Value'), - type: 'text', - label: 'jqui_text', - hidden: (data, index) => - !!data[`onlyIcon${index}`] || - data[`value${index}`] === '' || - data[`value${index}`] === null || - data[`value${index}`] === undefined, - }, - { - name: 'color', - type: 'color', - label: 'color', - hidden: (data, index) => - data.type === 'slider' || - data[`value${index}`] === '' || - data[`value${index}`] === null || - data[`value${index}`] === undefined, - }, - { - name: 'activeColor', - type: 'color', - label: 'jqui_active_color', - hidden: (data, index) => - data.type === 'slider' || - data[`value${index}`] === '' || - data[`value${index}`] === null || - data[`value${index}`] === undefined, - }, - { - name: 'image', - label: 'jqui_image', - type: 'image', - hidden: (data, index) => - data.type === 'slider' || - !!data.icon || - data[`value${index}`] === '' || - data[`value${index}`] === null || - data[`value${index}`] === undefined, - }, - { - name: 'icon', - label: 'jqui_icon', - type: 'icon64', - hidden: (data, index) => - data.type === 'slider' || - !!data.image || - data[`value${index}`] === '' || - data[`value${index}`] === null || - data[`value${index}`] === undefined, - }, - { - name: 'tooltip', - label: 'jqui_tooltip', - type: 'text', - hidden: (data, index) => - data.type === 'slider' || - data[`value${index}`] === '' || - data[`value${index}`] === null || - data[`value${index}`] === undefined, - }, - ], - }, - ], - visDefaultStyle: { - width: 300, - height: 45, - }, - }; - } - - async componentDidMount(): Promise { - super.componentDidMount(); - - // convert old tplJquiRadioSteps data to JquiState data - if ( - this.props.tpl === 'tplJquiRadioSteps' && - this.state.data && - this.props.context.onWidgetsChanged && - this.state.data.count === undefined - ) { - const data = deepClone(this.state.data); - - data.count = 5; - const min = parseFloat(data.min || 0); - const max = parseFloat(data.max || 100); - - data.value1 = min; - data.text1 = I18n.t('jqui_off'); - data['g_states-1'] = true; - - data.value5 = max; - data.text5 = '100%'; - data['g_states-5'] = true; - - data.value2 = (max - min) * 0.25 + min; - data.text2 = '25%'; - data['g_states-2'] = true; - - data.value3 = (max - min) * 0.5 + min; - data.text3 = '50%'; - data['g_states-3'] = true; - - data.value4 = (max - min) * 0.75 + min; - data.text4 = '75%'; - data['g_states-4'] = true; - - data.min = null; - data.max = null; - - setTimeout( - () => - this.props.context.onWidgetsChanged([ - { - wid: this.props.id, - view: this.props.view, - data, - }, - ]), - 100, - ); - } - - // convert old tplJquiRadioList data to JquiState data - if ( - (this.props.tpl === 'tplJquiRadioList' || this.props.tpl === 'tplJquiSelectList') && - this.state.data && - this.state.data.values && - this.state.data.texts && - this.props.context.onWidgetsChanged - ) { - // convert - const values = this.state.data.values.split(';'); - const texts = this.state.data.texts.split(';'); - const data = deepClone(this.state.data); - data.values = null; - data.texts = null; - data.count = values.length; - for (let i = 1; i <= values.length; i++) { - data[`value${i}`] = values[i - 1]; - data[`text${i}`] = texts[i - 1]; - data[`g_states-${i}`] = true; - } - data.type = this.props.tpl === 'tplJquiRadioList' ? 'radio' : 'select'; - if (this.props.context.onWidgetsChanged) { - setTimeout( - () => - this.props.context.onWidgetsChanged([ - { - wid: this.props.id, - view: this.props.view, - data, - }, - ]), - 100, - ); - } - } - - if (this.state.rxData.oid && this.state.rxData.oid !== 'nothing_selected') { - try { - const state = await this.props.context.socket.getState(this.state.rxData.oid); - this.onStateUpdated(this.state.rxData.oid, state); - } catch (error) { - console.error(`Cannot get state ${this.state.rxData.oid}: ${error}`); - } - } - } - - componentWillUnmount(): void { - super.componentWillUnmount(); - if (this.controlTimeout) { - clearTimeout(this.controlTimeout); - this.controlTimeout = null; - } - } - - static findField( - widgetInfo: RxWidgetInfo, - name: string, - ): Writeable | null { - return VisRxWidget.findField(widgetInfo, name) as unknown as Writeable; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo(): RxWidgetInfo { - return JQuiState.getWidgetInfo(); - } - - onStateUpdated(id: string, state: ioBroker.State): void { - if (id === this.state.rxData.oid && state) { - const value = state.val === null || state.val === undefined ? '' : state.val; - - if (this.state.value !== value.toString()) { - this.setState({ value: value.toString() }); - } - } - } - - getControlOid(): string { - if (this.state.rxData.click_id && this.state.rxData.click_id !== 'nothing_selected') { - return this.state.rxData.click_id; - } - if (this.state.rxData.oid && this.state.rxData.oid !== 'nothing_selected') { - return this.state.rxData.oid; - } - return ''; - } - - onClick(indexOrValue: string | number, immediately?: boolean): void { - if (this.state.rxData.readOnly || this.props.editMode) { - return; - } - - if (this.state.rxData.type === 'slider') { - if (this.controlTimeout) { - clearTimeout(this.controlTimeout); - } - this.controlTimeout = setTimeout( - () => { - this.controlTimeout = null; - const oid = this.getControlOid(); - if (oid) { - this.props.context.setValue(oid, parseFloat(indexOrValue as string)); - } - }, - immediately ? 0 : parseInt(this.state.rxData.timeout as unknown as string, 10) || 300, - ); - this.setState({ value: indexOrValue }); - } else { - const oid = this.getControlOid(); - if (oid) { - if (typeof this.state.object === 'object' && this.state.object?.common.type === 'number') { - this.props.context.setValue( - oid, - parseFloat(this.state.rxData[`value${indexOrValue as number}`] as string), - ); - } else { - this.props.context.setValue(oid, this.state.rxData[`value${indexOrValue as number}`]); - } - } - this.setState({ value: this.state.rxData[`value${indexOrValue as number}`] }); - } - } - - getSelectedIndex(value?: string | number | boolean): number { - if (value === undefined) { - value = this.state.value; - } - - if (this.props.editMode) { - for (let i = 1; i <= this.state.rxData.count; i++) { - if ((this.state.rxData as unknown as Record)[`test${i}`]) { - return i; - } - } - } - for (let i = 1; i <= this.state.rxData.count; i++) { - if ((this.state.rxData as unknown as Record)[`value${i}`] === value) { - return i; - } - } - return 0; - } - - renderIcon(i: number, selectedIndex: number): React.JSX.Element | null { - let color: string; - const rxData = this.state.rxData as unknown as Record; - let icon: string = rxData[`icon${i}`] || rxData[`image${i}`]; - if (icon && rxData[`color${i}`]) { - color = rxData[`color${i}`]; - if (i === selectedIndex && rxData[`activeColor${i}`]) { - color = rxData[`activeColor${i}`]; - } - } - - if (icon) { - if (icon.startsWith('_PRJ_NAME/')) { - icon = icon.replace( - '_PRJ_NAME/', - `../${this.props.context.adapterName}.${this.props.context.instance}/${this.props.context.projectName}/`, - ); - } - const style: React.CSSProperties = { color }; - style.width = 'auto'; - style.height = 24; - - return ( - - ); - } - return null; - } - - renderText(i: number, selectedIndex: number): React.JSX.Element | null { - const rxData = this.state.rxData as unknown as Record; - if (rxData[`onlyIcon${i}`]) { - return null; - } - let text = rxData[`text${i}`]; - let color = rxData[`color${i}`]; - if (i === selectedIndex && rxData[`activeColor${i}`]) { - color = rxData[`activeColor${i}`]; - } - - text = text || rxData[`value${i}`]; - - return {text}; - } - - renderButton(i: number, selectedIndex: number, buttonStyle?: React.CSSProperties): React.JSX.Element | null { - const rxData = this.state.rxData as unknown as Record; - const icon = this.renderIcon(i, selectedIndex); - const text = this.renderText(i, selectedIndex); - - // Button - const button = ( - - ); - - if (rxData[`tooltip${i}`]) { - return ( - - {button} - - ); - } - - return button; - } - - renderRadio(i: number, selectedIndex: number, buttonStyle?: React.CSSProperties): React.JSX.Element { - const rxData = this.state.rxData as unknown as Record; - const icon = this.renderIcon(i, selectedIndex); - let text = this.renderText(i, selectedIndex); - - if (icon && text) { - text = ( -

- {icon} - {text} -
- ); - } - - // Button - const button = ( - this.onClick(i)} - checked={selectedIndex === i} - /> - } - labelPlacement="end" - label={text || icon} - /> - ); - - if (rxData[`tooltip${i}`]) { - return ( - - {button} - - ); - } - return button; - } - - renderMenuItem(i: number, selectedIndex: number, buttonStyle?: React.CSSProperties): React.JSX.Element { - const rxData = this.state.rxData as unknown as Record; - const icon = this.renderIcon(i, selectedIndex); - let text = this.renderText(i, selectedIndex); - - if (icon && text) { - text = ( -
- {icon} - {text} -
- ); - } - - // Button - return ( - this.onClick(i) : undefined} - > - {text || icon} - - ); - } - - renderWidgetBody(props: RxRenderWidgetProps): React.JSX.Element { - super.renderWidgetBody(props); - const selectedIndex = this.getSelectedIndex(); - const rxData = this.state.rxData as unknown as Record; - - if ((this.state.object as ioBroker.StateObject)?._id !== this.state.rxData.oid && this.state.object !== false) { - Object.assign(this.state, { object: false }); - setTimeout(async () => { - if (this.state.rxData.oid && this.state.rxData.oid !== 'nothing_selected') { - const obj = await this.props.context.socket.getObject(this.state.rxData.oid); - if (obj?.common?.type) { - this.setState({ - object: { - _id: obj._id, - common: { type: obj.common.type } as ioBroker.StateCommon, - type: 'state', - native: {}, - }, - }); - return; - } - } - this.setState({ - object: { - _id: this.state.rxData.oid, - common: { type: 'string' } as ioBroker.StateCommon, - type: 'state', - native: {}, - }, - }); - }, 0); - } - - const buttonStyle: React.CSSProperties = {}; - // apply style from the element - Object.keys(this.state.rxStyle).forEach(attr => { - const value = rxData[attr]; - if (value !== null && value !== undefined && VisRxWidget.POSSIBLE_MUI_STYLES.includes(attr)) { - attr = attr.replace(/(-\w)/g, text => text[1].toUpperCase()); - (buttonStyle as unknown as Record)[attr] = value; - } - }); - buttonStyle.minWidth = 'unset'; - if (buttonStyle.borderWidth) { - buttonStyle.borderWidth = VisBaseWidget.correctStylePxValue(buttonStyle.borderWidth); - } - if (buttonStyle.fontSize) { - buttonStyle.fontSize = VisBaseWidget.correctStylePxValue(buttonStyle.fontSize); - } - - let content; - if ( - (!this.state.rxData.count || - (this.state.rxData.count === 1 && !rxData.text0 && !rxData.icon0 && !rxData.image0)) && - (!this.state.rxData.oid || this.state.rxData.oid === 'nothing_selected') - ) { - content = ( - - ); - } else if (!this.state.rxData.count) { - content = ( - - ); - } else if (this.state.rxData.type === 'radio') { - const buttons = []; - for (let i = 1; i <= this.state.rxData.count; i++) { - buttons.push(this.renderRadio(i, selectedIndex, buttonStyle)); - } - - content = {buttons}; - } else if (this.state.rxData.type === 'select') { - const buttons = []; - for (let i = 1; i <= this.state.rxData.count; i++) { - buttons.push(this.renderMenuItem(i, selectedIndex, buttonStyle)); - } - - let variant: 'standard' | 'filled' | 'outlined' = 'standard'; - if (this.state.rxData.variant === 'contained') { - variant = 'filled'; - } else if (this.state.rxData.variant === 'outlined') { - variant = 'outlined'; - } - - if (this.state.rxData.open) { - content = {buttons}; - } else { - content = ( - - ); - } - } else if (this.state.rxData.type === 'slider') { - props.style.overflow = 'visible'; - const marks = []; - for (let i = 1; i <= this.state.rxData.count; i++) { - marks.push({ - value: parseFloat(rxData[`value${i}`]) || 0, - label: rxData[`text${i}`] || 0, - }); - } - - content = ( - this.onClick(value, true)} - onChange={(_e, value: number) => this.onClick(value)} - /> - ); - } else { - const buttons = []; - for (let i = 1; i <= this.state.rxData.count; i++) { - buttons.push(this.renderButton(i, selectedIndex, buttonStyle)); - } - - content = ( - - {buttons} - - ); - } - - if (this.state.rxData.widgetTitle) { - content = ( - - {this.state.rxData.type === 'select' ? ( - {this.state.rxData.widgetTitle} - ) : ( - - {this.state.rxData.widgetTitle} - - )} - {content} - - ); - } - - return
{content}
; - } -} - -export default JQuiState; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 React from 'react'; + +import { + Button, + Tooltip, + ButtonGroup, + Radio, + RadioGroup, + FormControlLabel, + MenuItem, + Select, + FormControl, + InputLabel, + FormLabel, + Slider, + List, +} from '@mui/material'; + +import { I18n, Icon, type LegacyConnection } from '@iobroker/adapter-react-v5'; + +import VisBaseWidget from '@/Vis/visBaseWidget'; +import { deepClone } from '@/Utils/utils'; + +import VisRxWidget, { type VisRxWidgetState } from '../../visRxWidget'; +import BulkEditor from './BulkEditor'; +import type { + RxRenderWidgetProps, + RxWidgetInfo, + RxWidgetInfoAttributesField, + RxWidgetInfoAttributesFieldCheckbox, + RxWidgetInfoCustomComponentProperties, + VisBaseWidgetProps, + WidgetData, + Writeable, +} from '@iobroker/types-vis-2'; + +interface BulkEditorData { + variant?: 'outlined' | 'contained'; + type: 'select' | 'radio'; + oid: string; + count: number; + [colors: `color${number}`]: string; + [values: `value${number}`]: string | number; + [values: `text${number}`]: string; + [values: `icon${number}`]: string | null; + [values: `g_states-${number}`]: boolean; + [values: `image${number}`]: string; + [values: `activeColor${number}`]: string; + [values: `tooltip${number}`]: string; +} + +type RxData = { + oid: string; + count: number; + + type: 'button' | 'select' | 'radio' | 'slider'; + readOnly: boolean; + click_id: string; + variant: 'contained' | 'outlined' | 'text' | 'standard'; + orientation: 'horizontal' | 'vertical'; + widgetTitle: string; + timeout: number; + open: boolean; + + [key: `value${number}`]: string | number; + [key: `color${number}`]: string; + [key: `text${number}`]: string; + [key: `icon${number}`]: string | null; + [key: `g_states-${number}`]: boolean; + [key: `image${number}`]: string; + [key: `activeColor${number}`]: string; + [key: `tooltip${number}`]: string; + + [key: `onlyIcon${number}`]: boolean; + [key: `test${number}`]: boolean; +}; + +interface JQuiStateState extends VisRxWidgetState { + value: string | number | boolean; + object: ioBroker.StateObject | null | false; +} + +class JQuiState

extends VisRxWidget { + private controlTimeout: ReturnType | null = null; + + constructor(props: VisBaseWidgetProps) { + super(props); + Object.assign(this.state, { + value: '', + object: null, + }); + } + + static getWidgetInfo(): RxWidgetInfo { + return { + id: 'tplJquiButtonState', + visSet: 'jqui', + visName: 'States control', + visWidgetLabel: 'jqui_states_control', + visPrev: 'widgets/jqui/img/Prev_ButtonState.png', + visOrder: 14, + visAttrs: [ + { + name: 'common', + fields: [ + { + name: 'type', + label: 'jqui_type', + type: 'select', + noTranslation: true, + default: 'button', + options: ['button', 'select', 'radio', 'slider'], + }, + { + name: 'oid', + type: 'id', + onChange: async ( + _field: RxWidgetInfoAttributesField, + data: RxData, + changeData: (newData: RxData) => void, + socket: LegacyConnection, + ): Promise => { + if (data.oid) { + // unknown bug by compilation + if (await (BulkEditor.generateFields as any)(data, socket)) { + changeData(data); + } + } + }, + }, + { + name: 'readOnly', + type: 'checkbox', + }, + { + name: 'click_id', + type: 'id', + noSubscribe: true, + hidden: (data: Record): boolean => !!data.readOnly, + }, + { + name: 'count', + type: 'slider', + min: 0, + default: 1, + max: 10, + hidden: (data: Record): boolean => !!data.percents, + }, + { + type: 'custom', + component: ( + _field: RxWidgetInfoAttributesField, + data: WidgetData, + onDataChange: (newData: WidgetData) => void, + props: RxWidgetInfoCustomComponentProperties, + ) => ( + + ), + }, + { + name: 'variant', + label: 'jqui_variant', + type: 'select', + noTranslation: true, + options: ['contained', 'outlined', 'text', 'standard'], + default: 'contained', + hidden: (data: Record): boolean => + data.type !== 'button' && data.type !== 'select', + }, + { + name: 'orientation', + label: 'orientation', + type: 'select', + options: ['horizontal', 'vertical'], + default: 'horizontal', + hidden: (data: Record): boolean => + data.type !== 'button' && data.type !== 'slider', + }, + { + name: 'widgetTitle', + label: 'jqui_name', + type: 'text', + }, + { + name: 'timeout', + label: 'jqui_set_timeout', + type: 'number', + hidden: (data: Record): boolean => data.type !== 'slider', + }, + { + name: 'open', + label: 'jqui_open', + type: 'checkbox', + hidden: (data: Record): boolean => data.type !== 'select', + }, + ], + }, + { + name: 'states', + label: 'jqui_group_value', + indexFrom: 1, + indexTo: 'count', + hidden: (data: Record): boolean => !!data.percents, + fields: [ + { + name: 'value', + type: 'text', + label: 'jqui_value', + default: '0', + }, + { + name: 'test', + type: 'checkbox', + label: 'jqui_test', + onChange: ( + field: RxWidgetInfoAttributesField, + data: Record, + changeData: (newData: Record) => void, + _socket: LegacyConnection, + index?: number, + ): Promise => { + if (data[(field as RxWidgetInfoAttributesFieldCheckbox).name]) { + let changed = false; + // deactivate all other tests + for (let i = 1; i <= data.count; i++) { + if (i !== index) { + if (data[`test${i}`]) { + changed = true; + data[`test${i}`] = false; + } + } + } + changed && changeData(data); + } + return Promise.resolve(); + }, + hidden: (data, index) => + data.type === 'slider' || + data[`value${index}`] === '' || + data[`value${index}`] === null || + data[`value${index}`] === undefined, + }, + { + name: 'onlyIcon', + type: 'checkbox', + label: 'jqui_only_icon', + }, + { + name: 'text', + default: I18n.t('Value'), + type: 'text', + label: 'jqui_text', + hidden: (data, index) => + !!data[`onlyIcon${index}`] || + data[`value${index}`] === '' || + data[`value${index}`] === null || + data[`value${index}`] === undefined, + }, + { + name: 'color', + type: 'color', + label: 'color', + hidden: (data, index) => + data.type === 'slider' || + data[`value${index}`] === '' || + data[`value${index}`] === null || + data[`value${index}`] === undefined, + }, + { + name: 'activeColor', + type: 'color', + label: 'jqui_active_color', + hidden: (data, index) => + data.type === 'slider' || + data[`value${index}`] === '' || + data[`value${index}`] === null || + data[`value${index}`] === undefined, + }, + { + name: 'image', + label: 'jqui_image', + type: 'image', + hidden: (data, index) => + data.type === 'slider' || + !!data.icon || + data[`value${index}`] === '' || + data[`value${index}`] === null || + data[`value${index}`] === undefined, + }, + { + name: 'icon', + label: 'jqui_icon', + type: 'icon64', + hidden: (data, index) => + data.type === 'slider' || + !!data.image || + data[`value${index}`] === '' || + data[`value${index}`] === null || + data[`value${index}`] === undefined, + }, + { + name: 'tooltip', + label: 'jqui_tooltip', + type: 'text', + hidden: (data, index) => + data.type === 'slider' || + data[`value${index}`] === '' || + data[`value${index}`] === null || + data[`value${index}`] === undefined, + }, + ], + }, + ], + visDefaultStyle: { + width: 300, + height: 45, + }, + }; + } + + async componentDidMount(): Promise { + super.componentDidMount(); + + // convert old tplJquiRadioSteps data to JquiState data + if ( + this.props.tpl === 'tplJquiRadioSteps' && + this.state.data && + this.props.context.onWidgetsChanged && + this.state.data.count === undefined + ) { + const data = deepClone(this.state.data); + + data.count = 5; + const min = parseFloat(data.min || 0); + const max = parseFloat(data.max || 100); + + data.value1 = min; + data.text1 = I18n.t('jqui_off'); + data['g_states-1'] = true; + + data.value5 = max; + data.text5 = '100%'; + data['g_states-5'] = true; + + data.value2 = (max - min) * 0.25 + min; + data.text2 = '25%'; + data['g_states-2'] = true; + + data.value3 = (max - min) * 0.5 + min; + data.text3 = '50%'; + data['g_states-3'] = true; + + data.value4 = (max - min) * 0.75 + min; + data.text4 = '75%'; + data['g_states-4'] = true; + + data.min = null; + data.max = null; + + setTimeout( + () => + this.props.context.onWidgetsChanged([ + { + wid: this.props.id, + view: this.props.view, + data, + }, + ]), + 100, + ); + } + + // convert old tplJquiRadioList data to JquiState data + if ( + (this.props.tpl === 'tplJquiRadioList' || this.props.tpl === 'tplJquiSelectList') && + this.state.data && + this.state.data.values && + this.state.data.texts && + this.props.context.onWidgetsChanged + ) { + // convert + const values = this.state.data.values.split(';'); + const texts = this.state.data.texts.split(';'); + const data = deepClone(this.state.data); + data.values = null; + data.texts = null; + data.count = values.length; + for (let i = 1; i <= values.length; i++) { + data[`value${i}`] = values[i - 1]; + data[`text${i}`] = texts[i - 1]; + data[`g_states-${i}`] = true; + } + data.type = this.props.tpl === 'tplJquiRadioList' ? 'radio' : 'select'; + if (this.props.context.onWidgetsChanged) { + setTimeout( + () => + this.props.context.onWidgetsChanged([ + { + wid: this.props.id, + view: this.props.view, + data, + }, + ]), + 100, + ); + } + } + + if (this.state.rxData.oid && this.state.rxData.oid !== 'nothing_selected') { + try { + const state = await this.props.context.socket.getState(this.state.rxData.oid); + this.onStateUpdated(this.state.rxData.oid, state); + } catch (error) { + console.error(`Cannot get state ${this.state.rxData.oid}: ${error}`); + } + } + } + + componentWillUnmount(): void { + super.componentWillUnmount(); + if (this.controlTimeout) { + clearTimeout(this.controlTimeout); + this.controlTimeout = null; + } + } + + static findField( + widgetInfo: RxWidgetInfo, + name: string, + ): Writeable | null { + return VisRxWidget.findField(widgetInfo, name) as unknown as Writeable; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo(): RxWidgetInfo { + return JQuiState.getWidgetInfo(); + } + + onStateUpdated(id: string, state: ioBroker.State): void { + if (id === this.state.rxData.oid && state) { + const value = state.val === null || state.val === undefined ? '' : state.val; + + if (this.state.value !== value.toString()) { + this.setState({ value: value.toString() }); + } + } + } + + getControlOid(): string { + if (this.state.rxData.click_id && this.state.rxData.click_id !== 'nothing_selected') { + return this.state.rxData.click_id; + } + if (this.state.rxData.oid && this.state.rxData.oid !== 'nothing_selected') { + return this.state.rxData.oid; + } + return ''; + } + + onClick(indexOrValue: string | number, immediately?: boolean): void { + if (this.state.rxData.readOnly || this.props.editMode) { + return; + } + + if (this.state.rxData.type === 'slider') { + if (this.controlTimeout) { + clearTimeout(this.controlTimeout); + } + this.controlTimeout = setTimeout( + () => { + this.controlTimeout = null; + const oid = this.getControlOid(); + if (oid) { + this.props.context.setValue(oid, parseFloat(indexOrValue as string)); + } + }, + immediately ? 0 : parseInt(this.state.rxData.timeout as unknown as string, 10) || 300, + ); + this.setState({ value: indexOrValue }); + } else { + const oid = this.getControlOid(); + if (oid) { + if (typeof this.state.object === 'object' && this.state.object?.common.type === 'number') { + this.props.context.setValue( + oid, + parseFloat(this.state.rxData[`value${indexOrValue as number}`] as string), + ); + } else { + this.props.context.setValue(oid, this.state.rxData[`value${indexOrValue as number}`]); + } + } + this.setState({ value: this.state.rxData[`value${indexOrValue as number}`] }); + } + } + + getSelectedIndex(value?: string | number | boolean): number { + if (value === undefined) { + value = this.state.value; + } + + if (this.props.editMode) { + for (let i = 1; i <= this.state.rxData.count; i++) { + if ((this.state.rxData as unknown as Record)[`test${i}`]) { + return i; + } + } + } + for (let i = 1; i <= this.state.rxData.count; i++) { + if ((this.state.rxData as unknown as Record)[`value${i}`] === value) { + return i; + } + } + return 0; + } + + renderIcon(i: number, selectedIndex: number): React.JSX.Element | null { + let color: string; + const rxData = this.state.rxData as unknown as Record; + let icon: string = rxData[`icon${i}`] || rxData[`image${i}`]; + if (icon && rxData[`color${i}`]) { + color = rxData[`color${i}`]; + if (i === selectedIndex && rxData[`activeColor${i}`]) { + color = rxData[`activeColor${i}`]; + } + } + + if (icon) { + if (icon.startsWith('_PRJ_NAME/')) { + icon = icon.replace( + '_PRJ_NAME/', + `../${this.props.context.adapterName}.${this.props.context.instance}/${this.props.context.projectName}/`, + ); + } + const style: React.CSSProperties = { color }; + style.width = 'auto'; + style.height = 24; + + return ( + + ); + } + return null; + } + + renderText(i: number, selectedIndex: number): React.JSX.Element | null { + const rxData = this.state.rxData as unknown as Record; + if (rxData[`onlyIcon${i}`]) { + return null; + } + let text = rxData[`text${i}`]; + let color = rxData[`color${i}`]; + if (i === selectedIndex && rxData[`activeColor${i}`]) { + color = rxData[`activeColor${i}`]; + } + + text = text || rxData[`value${i}`]; + + return {text}; + } + + renderButton(i: number, selectedIndex: number, buttonStyle?: React.CSSProperties): React.JSX.Element | null { + const rxData = this.state.rxData as unknown as Record; + const icon = this.renderIcon(i, selectedIndex); + const text = this.renderText(i, selectedIndex); + + // Button + const button = ( + + ); + + if (rxData[`tooltip${i}`]) { + return ( + + {button} + + ); + } + + return button; + } + + renderRadio(i: number, selectedIndex: number, buttonStyle?: React.CSSProperties): React.JSX.Element { + const rxData = this.state.rxData as unknown as Record; + const icon = this.renderIcon(i, selectedIndex); + let text = this.renderText(i, selectedIndex); + + if (icon && text) { + text = ( +

+ {icon} + {text} +
+ ); + } + + // Button + const button = ( + this.onClick(i)} + checked={selectedIndex === i} + /> + } + labelPlacement="end" + label={text || icon} + /> + ); + + if (rxData[`tooltip${i}`]) { + return ( + + {button} + + ); + } + return button; + } + + renderMenuItem(i: number, selectedIndex: number, buttonStyle?: React.CSSProperties): React.JSX.Element { + const rxData = this.state.rxData as unknown as Record; + const icon = this.renderIcon(i, selectedIndex); + let text = this.renderText(i, selectedIndex); + + if (icon && text) { + text = ( +
+ {icon} + {text} +
+ ); + } + + // Button + return ( + this.onClick(i) : undefined} + > + {text || icon} + + ); + } + + renderWidgetBody(props: RxRenderWidgetProps): React.JSX.Element { + super.renderWidgetBody(props); + const selectedIndex = this.getSelectedIndex(); + const rxData = this.state.rxData as unknown as Record; + + if ((this.state.object as ioBroker.StateObject)?._id !== this.state.rxData.oid && this.state.object !== false) { + Object.assign(this.state, { object: false }); + setTimeout(async () => { + if (this.state.rxData.oid && this.state.rxData.oid !== 'nothing_selected') { + const obj = await this.props.context.socket.getObject(this.state.rxData.oid); + if (obj?.common?.type) { + this.setState({ + object: { + _id: obj._id, + common: { type: obj.common.type } as ioBroker.StateCommon, + type: 'state', + native: {}, + }, + }); + return; + } + } + this.setState({ + object: { + _id: this.state.rxData.oid, + common: { type: 'string' } as ioBroker.StateCommon, + type: 'state', + native: {}, + }, + }); + }, 0); + } + + const buttonStyle: React.CSSProperties = {}; + // apply style from the element + Object.keys(this.state.rxStyle).forEach(attr => { + const value = rxData[attr]; + if (value !== null && value !== undefined && VisRxWidget.POSSIBLE_MUI_STYLES.includes(attr)) { + attr = attr.replace(/(-\w)/g, text => text[1].toUpperCase()); + (buttonStyle as unknown as Record)[attr] = value; + } + }); + buttonStyle.minWidth = 'unset'; + if (buttonStyle.borderWidth) { + buttonStyle.borderWidth = VisBaseWidget.correctStylePxValue(buttonStyle.borderWidth); + } + if (buttonStyle.fontSize) { + buttonStyle.fontSize = VisBaseWidget.correctStylePxValue(buttonStyle.fontSize); + } + + let content; + if ( + (!this.state.rxData.count || + (this.state.rxData.count === 1 && !rxData.text0 && !rxData.icon0 && !rxData.image0)) && + (!this.state.rxData.oid || this.state.rxData.oid === 'nothing_selected') + ) { + content = ( + + ); + } else if (!this.state.rxData.count) { + content = ( + + ); + } else if (this.state.rxData.type === 'radio') { + const buttons = []; + for (let i = 1; i <= this.state.rxData.count; i++) { + buttons.push(this.renderRadio(i, selectedIndex, buttonStyle)); + } + + content = {buttons}; + } else if (this.state.rxData.type === 'select') { + const buttons = []; + for (let i = 1; i <= this.state.rxData.count; i++) { + buttons.push(this.renderMenuItem(i, selectedIndex, buttonStyle)); + } + + let variant: 'standard' | 'filled' | 'outlined' = 'standard'; + if (this.state.rxData.variant === 'contained') { + variant = 'filled'; + } else if (this.state.rxData.variant === 'outlined') { + variant = 'outlined'; + } + + if (this.state.rxData.open) { + content = {buttons}; + } else { + content = ( + + ); + } + } else if (this.state.rxData.type === 'slider') { + props.style.overflow = 'visible'; + const marks = []; + for (let i = 1; i <= this.state.rxData.count; i++) { + marks.push({ + value: parseFloat(rxData[`value${i}`]) || 0, + label: rxData[`text${i}`] || 0, + }); + } + + content = ( + this.onClick(value, true)} + onChange={(_e, value: number) => this.onClick(value)} + /> + ); + } else { + const buttons = []; + for (let i = 1; i <= this.state.rxData.count; i++) { + buttons.push(this.renderButton(i, selectedIndex, buttonStyle)); + } + + content = ( + + {buttons} + + ); + } + + if (this.state.rxData.widgetTitle) { + content = ( + + {this.state.rxData.type === 'select' ? ( + {this.state.rxData.widgetTitle} + ) : ( + + {this.state.rxData.widgetTitle} + + )} + {content} + + ); + } + + return
{content}
; + } +} + +export default JQuiState; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiToggle.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiToggle.tsx index 6322101c..b97f70f3 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiToggle.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiToggle.tsx @@ -1,71 +1,71 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 type { - RxWidgetInfo, - RxWidgetInfoAttributesField, - RxWidgetInfoAttributesFieldHelp, - RxWidgetInfoAttributesFieldSimple, - Writeable, -} from '@iobroker/types-vis-2'; -import JQuiBinaryState from './JQuiBinaryState'; - -class JQuiToggle extends JQuiBinaryState { - static getWidgetInfo(): RxWidgetInfo { - const widgetInfo: RxWidgetInfo = JQuiBinaryState.getWidgetInfo(); - const newWidgetInfo: RxWidgetInfo = { - id: 'tplJquiToogle', - visSet: 'jqui', - visName: 'Icon Toggle', - visWidgetLabel: 'jqui_icon_toggle', - visPrev: 'widgets/jqui/img/Prev_IconToggle.png', - visOrder: 32, - visAttrs: widgetInfo.visAttrs, - visDefaultStyle: { - width: 92, - height: 36, - }, - }; - - // Add note - (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ - name: '_note', - type: 'help', - text: 'jqui_button_binary_control_note', - } as Writeable); - - const iconFalse: Writeable = - JQuiBinaryState.findField(newWidgetInfo, 'icon_false'); - if (iconFalse) { - iconFalse.default = - 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSJjdXJyZW50Q29sb3IiIGQ9Ik0xMyAzaC0ydjEwaDJWM3ptNC44MyAyLjE3bC0xLjQyIDEuNDJBNi45MiA2LjkyIDAgMCAxIDE5IDEyYzAgMy44Ny0zLjEzIDctNyA3QTYuOTk1IDYuOTk1IDAgMCAxIDcuNTggNi41OEw2LjE3IDUuMTdBOC45MzIgOC45MzIgMCAwIDAgMyAxMmE5IDkgMCAwIDAgMTggMGMwLTIuNzQtMS4yMy01LjE4LTMuMTctNi44M3oiLz48L3N2Zz4='; - } - - const colorTrue = JQuiBinaryState.findField(newWidgetInfo, 'color_true'); - - if (colorTrue) { - colorTrue.default = '#93ff93'; - } - - return newWidgetInfo; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo(): RxWidgetInfo { - return JQuiToggle.getWidgetInfo(); - } -} - -export default JQuiToggle; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 type { + RxWidgetInfo, + RxWidgetInfoAttributesField, + RxWidgetInfoAttributesFieldHelp, + RxWidgetInfoAttributesFieldSimple, + Writeable, +} from '@iobroker/types-vis-2'; +import JQuiBinaryState from './JQuiBinaryState'; + +class JQuiToggle extends JQuiBinaryState { + static getWidgetInfo(): RxWidgetInfo { + const widgetInfo: RxWidgetInfo = JQuiBinaryState.getWidgetInfo(); + const newWidgetInfo: RxWidgetInfo = { + id: 'tplJquiToogle', + visSet: 'jqui', + visName: 'Icon Toggle', + visWidgetLabel: 'jqui_icon_toggle', + visPrev: 'widgets/jqui/img/Prev_IconToggle.png', + visOrder: 32, + visAttrs: widgetInfo.visAttrs, + visDefaultStyle: { + width: 92, + height: 36, + }, + }; + + // Add note + (newWidgetInfo.visAttrs[0].fields as Writeable).unshift({ + name: '_note', + type: 'help', + text: 'jqui_button_binary_control_note', + } as Writeable); + + const iconFalse: Writeable = + JQuiBinaryState.findField(newWidgetInfo, 'icon_false'); + if (iconFalse) { + iconFalse.default = + 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSJjdXJyZW50Q29sb3IiIGQ9Ik0xMyAzaC0ydjEwaDJWM3ptNC44MyAyLjE3bC0xLjQyIDEuNDJBNi45MiA2LjkyIDAgMCAxIDE5IDEyYzAgMy44Ny0zLjEzIDctNyA3QTYuOTk1IDYuOTk1IDAgMCAxIDcuNTggNi41OEw2LjE3IDUuMTdBOC45MzIgOC45MzIgMCAwIDAgMyAxMmE5IDkgMCAwIDAgMTggMGMwLTIuNzQtMS4yMy01LjE4LTMuMTctNi44M3oiLz48L3N2Zz4='; + } + + const colorTrue = JQuiBinaryState.findField(newWidgetInfo, 'color_true'); + + if (colorTrue) { + colorTrue.default = '#93ff93'; + } + + return newWidgetInfo; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo(): RxWidgetInfo { + return JQuiToggle.getWidgetInfo(); + } +} + +export default JQuiToggle; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiWriteState.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiWriteState.tsx index 8a4e9917..0368b3d3 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiWriteState.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/JQui/JQuiWriteState.tsx @@ -1,538 +1,538 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * Copyright (c) 2023-2024 Denis Haev https://github.com/GermanBluefox, - * Creative Common Attribution-NonCommercial (CC BY-NC) - * - * http://creativecommons.org/licenses/by-nc/4.0/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 React from 'react'; - -import { Button } from '@mui/material'; - -import { I18n, Icon, type LegacyConnection } from '@iobroker/adapter-react-v5'; - -import type { - RxRenderWidgetProps, - RxWidgetInfo, - RxWidgetInfoAttributesField, - VisBaseWidgetProps, - Writeable, -} from '@iobroker/types-vis-2'; - -import VisBaseWidget from '@/Vis/visBaseWidget'; - -import VisRxWidget, { type VisRxWidgetState } from '../../visRxWidget'; - -type RxData = { - oid: string; - click_id: string; - type: 'value' | 'oid' | 'toggle' | 'change'; - value_oid: string; - value: string; - step: number; - minmax: number; - repeat_delay: number; - repeat_interval: number; - min: number; - max: number; - src_active: string; - icon_active: string; - text_active: string; - src: string; - icon: string; - text: string; - invert_icon: boolean; - invert_icon_active: boolean; - variant: 'contained' | 'outlined' | 'standard'; -}; - -interface JQuiWriteStateState extends VisRxWidgetState { - value: string | number; - valueType: string | null; -} - -class JQuiWriteState< - P extends RxData = RxData, - S extends JQuiWriteStateState = JQuiWriteStateState, -> extends VisRxWidget { - private iterateInterval: ReturnType | null = null; - - private iterateTimeout: ReturnType | null = null; - - constructor(props: VisBaseWidgetProps) { - super(props); - Object.assign(this.state, { - value: '', - valueType: null, - }); - } - - static getWidgetInfo(): RxWidgetInfo { - return { - id: 'tplIconState', - visSet: 'jqui', - visName: 'Icon State', - visWidgetLabel: 'jqui_write_value', - visPrev: 'widgets/jqui/img/Prev_WriteState.png', - visOrder: 26, - visAttrs: [ - { - name: 'common', - fields: [ - { - name: 'oid', - type: 'id', - onChange: async ( - _field: RxWidgetInfoAttributesField, - data: RxData, - changeData: (newData: RxData) => void, - socket: LegacyConnection, - ): Promise => { - if (data.oid && data.oid !== 'nothing_selected') { - const obj = (await socket.getObject(data.oid)) as ioBroker.StateObject; - let changed = false; - if (obj?.common?.min !== undefined && obj?.common?.min !== null) { - if (data.min !== obj.common.min) { - data.min = obj.common.min; - changed = true; - } - } - if (obj?.common?.max !== undefined && obj?.common?.max !== null) { - if (data.max !== obj.common.max) { - data.max = obj.common.max; - changed = true; - } - if (data.step > 0) { - if (data.minmax !== obj.common.max) { - data.minmax = obj.common.max; - changed = true; - } - } else if (data.minmax !== obj.common.min) { - data.minmax = obj.common.min; - changed = true; - } - } - if (!data.step) { - if (obj?.common?.step !== undefined && obj?.common?.step !== null) { - if (data.step !== obj.common.step) { - data.step = obj.common.step; - changed = true; - } - } - } - changed && changeData(data); - } - }, - }, - { - name: 'click_id', - type: 'id', - noSubscribe: true, - }, - { - name: 'type', - label: 'jqui_type', - type: 'select', - default: 'value', - options: [ - { value: 'value', label: 'jqui_from_value' }, - { value: 'oid', label: 'jqui_from_oid' }, - { value: 'toggle', label: 'jqui_toggle' }, - { value: 'change', label: 'jqui_increment_decrement' }, - ], - }, - { - name: 'value_oid', - type: 'id', - hidden: (data: RxData) => data.type !== 'oid', - }, - { - name: 'value', - type: 'text', - hidden: (data: RxData) => data.type !== 'value', - }, - { - name: 'step', - type: 'number', - hidden: (data: RxData) => data.type !== 'change', - default: 1, - }, - { - name: 'minmax', - label: 'jqui_max', - type: 'number', - hidden: (data: RxData) => - data.type !== 'change' || (parseFloat(data.step as unknown as string) || 0) < 0, - default: 1, - }, - { - name: 'minmax', - label: 'jqui_min', - type: 'number', - hidden: (data: RxData) => - data.type !== 'change' || (parseFloat(data.step as unknown as string) || 0) >= 0, - default: 1, - }, - { - name: 'repeat_delay', - type: 'number', - label: 'jqui_repeat_delay', - hidden: (data: RxData) => data.type !== 'change', - default: 800, - }, - { - name: 'repeat_interval', - type: 'number', - label: 'jqui_repeat_interval', - hidden: (data: RxData) => data.type !== 'change', - default: 300, - }, - { - name: 'min', - type: 'number', - label: 'jqui_min', - hidden: (data: RxData) => data.type !== 'toggle', - default: 0, - }, - { - name: 'max', - label: 'jqui_max', - type: 'number', - hidden: (data: RxData) => data.type !== 'toggle', - default: 100, - }, - ], - }, - { - name: 'style', - fields: [ - { - name: 'variant', - label: 'jqui_variant', - type: 'select', - noTranslation: true, - options: ['contained', 'outlined', 'standard'], - default: 'contained', - }, - { - name: 'text', - type: 'text', - default: I18n.t('jqui_Write state'), - }, - { - name: 'src', - type: 'image', - label: 'jqui_image', - hidden: (data: RxData) => !!data.icon, - }, - { - name: 'icon', - type: 'icon64', - label: 'jqui_icon', - hidden: (data: RxData) => !!data.src, - }, - { - name: 'text_active', - label: 'jqui_text_active', - type: 'text', - }, - { - name: 'src_active', - type: 'image', - label: 'jqui_image_active', - hidden: (data: RxData) => !!data.icon_active, - }, - { - name: 'icon_active', - type: 'icon64', - label: 'jqui_icon_active', - hidden: (data: RxData) => !!data.src_active, - }, - { - name: 'invert_icon', - type: 'checkbox', - hidden: (data: RxData) => !data.icon || !data.src, - }, - { - name: 'invert_icon_active', - type: 'checkbox', - hidden: (data: RxData) => !data.icon_active || !data.src_active, - }, - ], - }, - ], - visDefaultStyle: { - width: 100, - height: 40, - }, - } as const; - } - - async componentDidMount(): Promise { - super.componentDidMount(); - if (this.state.rxData.oid && this.state.rxData.oid !== 'nothing_selected') { - try { - const state = await this.props.context.socket.getState(this.state.rxData.oid); - this.onStateUpdated(this.state.rxData.oid, state); - } catch (e) { - console.error(`Cannot get state ${this.state.rxData.oid}: ${e}`); - } - } - } - - static findField( - widgetInfo: RxWidgetInfo, - name: string, - ): Writeable | null { - return VisRxWidget.findField(widgetInfo, name) as unknown as Writeable; - } - - // eslint-disable-next-line class-methods-use-this - getWidgetInfo(): RxWidgetInfo { - return JQuiWriteState.getWidgetInfo(); - } - - onStateUpdated(id: string, state: ioBroker.State | null): void { - if (id === this.state.rxData.oid && state) { - const value = state.val === null || state.val === undefined ? '' : state.val; - - if (this.state.value !== value.toString()) { - this.setState({ value: value.toString() }); - } - } - } - - getControlOid(): string { - if (this.state.rxData.click_id && this.state.rxData.click_id !== 'nothing_selected') { - return this.state.rxData.click_id; - } - if (this.state.rxData.oid && this.state.rxData.oid !== 'nothing_selected') { - return this.state.rxData.oid; - } - return ''; - } - - onClick(): void { - if (this.props.editMode) { - return; - } - - let value; - switch (this.state.rxData.type) { - case 'value': - value = this.state.rxData.value.toString(); - break; - case 'oid': { - value = this.state.values[`${this.state.rxData.value_oid}.val`]; - if (value === undefined || value === null) { - return; - } - break; - } - case 'toggle': - value = parseFloat(this.state.values[`${this.state.rxData.oid}.val`]) || 0; - value = - value.toString() === (this.state.rxData.max || 0).toString() - ? this.state.rxData.min - : this.state.rxData.max; - break; - case 'change': - value = parseFloat(this.state.values[`${this.state.rxData.oid}.val`]) || 0; - value += parseFloat(this.state.rxData.step as any as string) || 0; - if (this.state.rxData.step > 0) { - if (value > parseFloat(this.state.rxData.minmax as any as string)) { - value = parseFloat(this.state.rxData.minmax as any as string); - } - } else if (value < parseFloat(this.state.rxData.minmax as any as string)) { - value = parseFloat(this.state.rxData.minmax as any as string); - } - - break; - default: - return; - } - - const oid = this.getControlOid(); - if (oid) { - if (this.state.valueType === 'number') { - this.props.context.setValue(oid, parseFloat(value)); - } else { - this.props.context.setValue(oid, value); - } - } - this.setState({ value }); - } - - renderIcon(isActive: boolean): React.JSX.Element | null { - let icon; - let invertIcon = null; - if (isActive) { - invertIcon = this.state.rxData.invert_icon_active; - icon = this.state.rxData.icon_active || this.state.rxData.src_active; - } - if (!icon) { - icon = this.state.rxData.icon || this.state.rxData.src; - invertIcon = this.state.rxData.invert_icon; - } - const color = this.state.rxStyle.color; - - if (icon) { - if (icon.startsWith('_PRJ_NAME/')) { - icon = icon.replace( - '_PRJ_NAME/', - `../${this.props.context.adapterName}.${this.props.context.instance}/${this.props.context.projectName}/`, - ); - } - return ( - - ); - } - return null; - } - - renderText(): React.JSX.Element | null { - let text = this.state.rxData.text; - const color = this.state.rxStyle.color; - - if (!text) { - if (this.state.rxData.type === 'oid') { - text = this.state.values[`${this.state.rxData.value_oid}.val`]; - } else if (this.state.rxData.type === 'value') { - text = this.state.rxData.value; - } else if (this.state.rxData.type === 'change') { - if (this.state.rxData.step > 0) { - text = `+${this.state.rxData.step}`; - } else { - text = this.state.rxData.step.toString(); - } - } - } - - return text ? {text} : null; - } - - getIsActive(): boolean { - let actualValue = this.state.value; - if (actualValue === undefined || actualValue === null) { - actualValue = ''; - } - - switch (this.state.rxData.type) { - case 'value': { - let desiredValue = this.state.rxData.value; - if (desiredValue === undefined || desiredValue === null) { - desiredValue = ''; - } - return actualValue.toString() === desiredValue.toString(); - } - case 'oid': { - let oidValue = this.state.values[`${this.state.rxData.value_oid}.val`]; - if (oidValue === undefined || oidValue === null) { - oidValue = ''; - } - return actualValue.toString() === oidValue.toString(); - } - case 'toggle': - return actualValue.toString() === (this.state.rxData.max || 0).toString(); - default: - return false; - } - } - - componentWillUnmount(): void { - super.componentWillUnmount(); - this.iterateInterval && clearInterval(this.iterateInterval); - this.iterateInterval = null; - this.iterateTimeout && clearTimeout(this.iterateTimeout); - this.iterateTimeout = null; - } - - renderWidgetBody(props: RxRenderWidgetProps): React.JSX.Element { - super.renderWidgetBody(props); - const isActive = this.getIsActive(); - - const buttonStyle: React.CSSProperties = {}; - // apply style from the element - Object.keys(this.state.rxStyle).forEach(attr => { - const value = (this.state.rxStyle as Record)[attr]; - if (value !== null && value !== undefined && VisRxWidget.POSSIBLE_MUI_STYLES.includes(attr)) { - attr = attr.replace(/(-\w)/g, text => text[1].toUpperCase()); - (buttonStyle as Record)[attr] = value; - } - }); - if (buttonStyle.borderWidth) { - buttonStyle.borderWidth = VisBaseWidget.correctStylePxValue(buttonStyle.borderWidth); - } - if (buttonStyle.fontSize) { - buttonStyle.fontSize = VisBaseWidget.correctStylePxValue(buttonStyle.fontSize); - } - - const text = this.renderText(); - - buttonStyle.width = '100%'; - buttonStyle.height = '100%'; - buttonStyle.minWidth = 'unset'; - - const buttonColor: 'primary' | 'grey' = isActive ? 'primary' : 'grey'; - - const content = ( - - ); - - return
{content}
; - } -} - -export default JQuiWriteState; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2023-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 React from 'react'; + +import { Button } from '@mui/material'; + +import { I18n, Icon, type LegacyConnection } from '@iobroker/adapter-react-v5'; + +import type { + RxRenderWidgetProps, + RxWidgetInfo, + RxWidgetInfoAttributesField, + VisBaseWidgetProps, + Writeable, +} from '@iobroker/types-vis-2'; + +import VisBaseWidget from '@/Vis/visBaseWidget'; + +import VisRxWidget, { type VisRxWidgetState } from '../../visRxWidget'; + +type RxData = { + oid: string; + click_id: string; + type: 'value' | 'oid' | 'toggle' | 'change'; + value_oid: string; + value: string; + step: number; + minmax: number; + repeat_delay: number; + repeat_interval: number; + min: number; + max: number; + src_active: string; + icon_active: string; + text_active: string; + src: string; + icon: string; + text: string; + invert_icon: boolean; + invert_icon_active: boolean; + variant: 'contained' | 'outlined' | 'standard'; +}; + +interface JQuiWriteStateState extends VisRxWidgetState { + value: string | number; + valueType: string | null; +} + +class JQuiWriteState< + P extends RxData = RxData, + S extends JQuiWriteStateState = JQuiWriteStateState, +> extends VisRxWidget { + private iterateInterval: ReturnType | null = null; + + private iterateTimeout: ReturnType | null = null; + + constructor(props: VisBaseWidgetProps) { + super(props); + Object.assign(this.state, { + value: '', + valueType: null, + }); + } + + static getWidgetInfo(): RxWidgetInfo { + return { + id: 'tplIconState', + visSet: 'jqui', + visName: 'Icon State', + visWidgetLabel: 'jqui_write_value', + visPrev: 'widgets/jqui/img/Prev_WriteState.png', + visOrder: 26, + visAttrs: [ + { + name: 'common', + fields: [ + { + name: 'oid', + type: 'id', + onChange: async ( + _field: RxWidgetInfoAttributesField, + data: RxData, + changeData: (newData: RxData) => void, + socket: LegacyConnection, + ): Promise => { + if (data.oid && data.oid !== 'nothing_selected') { + const obj = (await socket.getObject(data.oid)) as ioBroker.StateObject; + let changed = false; + if (obj?.common?.min !== undefined && obj?.common?.min !== null) { + if (data.min !== obj.common.min) { + data.min = obj.common.min; + changed = true; + } + } + if (obj?.common?.max !== undefined && obj?.common?.max !== null) { + if (data.max !== obj.common.max) { + data.max = obj.common.max; + changed = true; + } + if (data.step > 0) { + if (data.minmax !== obj.common.max) { + data.minmax = obj.common.max; + changed = true; + } + } else if (data.minmax !== obj.common.min) { + data.minmax = obj.common.min; + changed = true; + } + } + if (!data.step) { + if (obj?.common?.step !== undefined && obj?.common?.step !== null) { + if (data.step !== obj.common.step) { + data.step = obj.common.step; + changed = true; + } + } + } + changed && changeData(data); + } + }, + }, + { + name: 'click_id', + type: 'id', + noSubscribe: true, + }, + { + name: 'type', + label: 'jqui_type', + type: 'select', + default: 'value', + options: [ + { value: 'value', label: 'jqui_from_value' }, + { value: 'oid', label: 'jqui_from_oid' }, + { value: 'toggle', label: 'jqui_toggle' }, + { value: 'change', label: 'jqui_increment_decrement' }, + ], + }, + { + name: 'value_oid', + type: 'id', + hidden: (data: RxData) => data.type !== 'oid', + }, + { + name: 'value', + type: 'text', + hidden: (data: RxData) => data.type !== 'value', + }, + { + name: 'step', + type: 'number', + hidden: (data: RxData) => data.type !== 'change', + default: 1, + }, + { + name: 'minmax', + label: 'jqui_max', + type: 'number', + hidden: (data: RxData) => + data.type !== 'change' || (parseFloat(data.step as unknown as string) || 0) < 0, + default: 1, + }, + { + name: 'minmax', + label: 'jqui_min', + type: 'number', + hidden: (data: RxData) => + data.type !== 'change' || (parseFloat(data.step as unknown as string) || 0) >= 0, + default: 1, + }, + { + name: 'repeat_delay', + type: 'number', + label: 'jqui_repeat_delay', + hidden: (data: RxData) => data.type !== 'change', + default: 800, + }, + { + name: 'repeat_interval', + type: 'number', + label: 'jqui_repeat_interval', + hidden: (data: RxData) => data.type !== 'change', + default: 300, + }, + { + name: 'min', + type: 'number', + label: 'jqui_min', + hidden: (data: RxData) => data.type !== 'toggle', + default: 0, + }, + { + name: 'max', + label: 'jqui_max', + type: 'number', + hidden: (data: RxData) => data.type !== 'toggle', + default: 100, + }, + ], + }, + { + name: 'style', + fields: [ + { + name: 'variant', + label: 'jqui_variant', + type: 'select', + noTranslation: true, + options: ['contained', 'outlined', 'standard'], + default: 'contained', + }, + { + name: 'text', + type: 'text', + default: I18n.t('jqui_Write state'), + }, + { + name: 'src', + type: 'image', + label: 'jqui_image', + hidden: (data: RxData) => !!data.icon, + }, + { + name: 'icon', + type: 'icon64', + label: 'jqui_icon', + hidden: (data: RxData) => !!data.src, + }, + { + name: 'text_active', + label: 'jqui_text_active', + type: 'text', + }, + { + name: 'src_active', + type: 'image', + label: 'jqui_image_active', + hidden: (data: RxData) => !!data.icon_active, + }, + { + name: 'icon_active', + type: 'icon64', + label: 'jqui_icon_active', + hidden: (data: RxData) => !!data.src_active, + }, + { + name: 'invert_icon', + type: 'checkbox', + hidden: (data: RxData) => !data.icon || !data.src, + }, + { + name: 'invert_icon_active', + type: 'checkbox', + hidden: (data: RxData) => !data.icon_active || !data.src_active, + }, + ], + }, + ], + visDefaultStyle: { + width: 100, + height: 40, + }, + } as const; + } + + async componentDidMount(): Promise { + super.componentDidMount(); + if (this.state.rxData.oid && this.state.rxData.oid !== 'nothing_selected') { + try { + const state = await this.props.context.socket.getState(this.state.rxData.oid); + this.onStateUpdated(this.state.rxData.oid, state); + } catch (e) { + console.error(`Cannot get state ${this.state.rxData.oid}: ${e}`); + } + } + } + + static findField( + widgetInfo: RxWidgetInfo, + name: string, + ): Writeable | null { + return VisRxWidget.findField(widgetInfo, name) as unknown as Writeable; + } + + // eslint-disable-next-line class-methods-use-this + getWidgetInfo(): RxWidgetInfo { + return JQuiWriteState.getWidgetInfo(); + } + + onStateUpdated(id: string, state: ioBroker.State | null): void { + if (id === this.state.rxData.oid && state) { + const value = state.val === null || state.val === undefined ? '' : state.val; + + if (this.state.value !== value.toString()) { + this.setState({ value: value.toString() }); + } + } + } + + getControlOid(): string { + if (this.state.rxData.click_id && this.state.rxData.click_id !== 'nothing_selected') { + return this.state.rxData.click_id; + } + if (this.state.rxData.oid && this.state.rxData.oid !== 'nothing_selected') { + return this.state.rxData.oid; + } + return ''; + } + + onClick(): void { + if (this.props.editMode) { + return; + } + + let value; + switch (this.state.rxData.type) { + case 'value': + value = this.state.rxData.value.toString(); + break; + case 'oid': { + value = this.state.values[`${this.state.rxData.value_oid}.val`]; + if (value === undefined || value === null) { + return; + } + break; + } + case 'toggle': + value = parseFloat(this.state.values[`${this.state.rxData.oid}.val`]) || 0; + value = + value.toString() === (this.state.rxData.max || 0).toString() + ? this.state.rxData.min + : this.state.rxData.max; + break; + case 'change': + value = parseFloat(this.state.values[`${this.state.rxData.oid}.val`]) || 0; + value += parseFloat(this.state.rxData.step as any as string) || 0; + if (this.state.rxData.step > 0) { + if (value > parseFloat(this.state.rxData.minmax as any as string)) { + value = parseFloat(this.state.rxData.minmax as any as string); + } + } else if (value < parseFloat(this.state.rxData.minmax as any as string)) { + value = parseFloat(this.state.rxData.minmax as any as string); + } + + break; + default: + return; + } + + const oid = this.getControlOid(); + if (oid) { + if (this.state.valueType === 'number') { + this.props.context.setValue(oid, parseFloat(value)); + } else { + this.props.context.setValue(oid, value); + } + } + this.setState({ value }); + } + + renderIcon(isActive: boolean): React.JSX.Element | null { + let icon; + let invertIcon = null; + if (isActive) { + invertIcon = this.state.rxData.invert_icon_active; + icon = this.state.rxData.icon_active || this.state.rxData.src_active; + } + if (!icon) { + icon = this.state.rxData.icon || this.state.rxData.src; + invertIcon = this.state.rxData.invert_icon; + } + const color = this.state.rxStyle.color; + + if (icon) { + if (icon.startsWith('_PRJ_NAME/')) { + icon = icon.replace( + '_PRJ_NAME/', + `../${this.props.context.adapterName}.${this.props.context.instance}/${this.props.context.projectName}/`, + ); + } + return ( + + ); + } + return null; + } + + renderText(): React.JSX.Element | null { + let text = this.state.rxData.text; + const color = this.state.rxStyle.color; + + if (!text) { + if (this.state.rxData.type === 'oid') { + text = this.state.values[`${this.state.rxData.value_oid}.val`]; + } else if (this.state.rxData.type === 'value') { + text = this.state.rxData.value; + } else if (this.state.rxData.type === 'change') { + if (this.state.rxData.step > 0) { + text = `+${this.state.rxData.step}`; + } else { + text = this.state.rxData.step.toString(); + } + } + } + + return text ? {text} : null; + } + + getIsActive(): boolean { + let actualValue = this.state.value; + if (actualValue === undefined || actualValue === null) { + actualValue = ''; + } + + switch (this.state.rxData.type) { + case 'value': { + let desiredValue = this.state.rxData.value; + if (desiredValue === undefined || desiredValue === null) { + desiredValue = ''; + } + return actualValue.toString() === desiredValue.toString(); + } + case 'oid': { + let oidValue = this.state.values[`${this.state.rxData.value_oid}.val`]; + if (oidValue === undefined || oidValue === null) { + oidValue = ''; + } + return actualValue.toString() === oidValue.toString(); + } + case 'toggle': + return actualValue.toString() === (this.state.rxData.max || 0).toString(); + default: + return false; + } + } + + componentWillUnmount(): void { + super.componentWillUnmount(); + this.iterateInterval && clearInterval(this.iterateInterval); + this.iterateInterval = null; + this.iterateTimeout && clearTimeout(this.iterateTimeout); + this.iterateTimeout = null; + } + + renderWidgetBody(props: RxRenderWidgetProps): React.JSX.Element { + super.renderWidgetBody(props); + const isActive = this.getIsActive(); + + const buttonStyle: React.CSSProperties = {}; + // apply style from the element + Object.keys(this.state.rxStyle).forEach(attr => { + const value = (this.state.rxStyle as Record)[attr]; + if (value !== null && value !== undefined && VisRxWidget.POSSIBLE_MUI_STYLES.includes(attr)) { + attr = attr.replace(/(-\w)/g, text => text[1].toUpperCase()); + (buttonStyle as Record)[attr] = value; + } + }); + if (buttonStyle.borderWidth) { + buttonStyle.borderWidth = VisBaseWidget.correctStylePxValue(buttonStyle.borderWidth); + } + if (buttonStyle.fontSize) { + buttonStyle.fontSize = VisBaseWidget.correctStylePxValue(buttonStyle.fontSize); + } + + const text = this.renderText(); + + buttonStyle.width = '100%'; + buttonStyle.height = '100%'; + buttonStyle.minWidth = 'unset'; + + const buttonColor: 'primary' | 'grey' = isActive ? 'primary' : 'grey'; + + const content = ( + + ); + + return
{content}
; + } +} + +export default JQuiWriteState; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/Swipe/Swipe.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/Swipe/Swipe.tsx index f702ea2b..230d76cb 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/Swipe/Swipe.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/Swipe/Swipe.tsx @@ -2,7 +2,7 @@ * ioBroker.vis-2 * https://github.com/ioBroker/ioBroker.vis-2 * - * Copyright (c) 2024 Denis Haev https://github.com/GermanBluefox, + * Copyright (c) 2024-2025 Denis Haev https://github.com/GermanBluefox, * Creative Common Attribution-NonCommercial (CC BY-NC) * * http://creativecommons.org/licenses/by-nc/4.0/ diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/Tabs/TabsSliderTabs.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/Tabs/TabsSliderTabs.tsx index 7a9b4a99..8c9668c2 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/Tabs/TabsSliderTabs.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/Widgets/Tabs/TabsSliderTabs.tsx @@ -2,7 +2,7 @@ * ioBroker.vis-2 * https://github.com/ioBroker/ioBroker.vis-2 * - * Copyright (c) 2022-2024 Denis Haev https://github.com/GermanBluefox, + * Copyright (c) 2022-2025 Denis Haev https://github.com/GermanBluefox, * Creative Common Attribution-NonCommercial (CC BY-NC) * * http://creativecommons.org/licenses/by-nc/4.0/ diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/visBaseWidget.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/visBaseWidget.tsx index f9737067..b8f05fbf 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/visBaseWidget.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/visBaseWidget.tsx @@ -1,2306 +1,2306 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * 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/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 React from 'react'; - -import { - Anchor as AnchorIcon, - Expand as ExpandIcon, - ArrowUpward as UpIcon, - ArrowDownward as DownIcon, - KeyboardReturn, -} from '@mui/icons-material'; - -import { I18n, Utils } from '@iobroker/adapter-react-v5'; - -import { calculateOverflow, deepClone, isVarFinite } from '@/Utils/utils'; -import type { - AnyWidgetId, - ResizeHandler, - GroupData, - WidgetData, - WidgetStyle, - Widget, - RxRenderWidgetProps, - VisRxWidgetStateValues, - VisWidgetCommand, - VisBaseWidgetProps, -} from '@iobroker/types-vis-2'; -import { addClass, removeClass, replaceGroupAttr } from './visUtils'; - -import VisOrderMenu from './visOrderMenu'; - -interface HTMLDivElementResizers extends HTMLDivElement { - _storedOpacity: string; -} - -type Resize = 'left' | 'right' | 'top' | 'bottom' | 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right' | boolean; - -export interface WidgetDataState extends WidgetData { - bindings: string[]; - _originalData?: string; -} - -export interface GroupDataState extends GroupData { - bindings?: string[]; - _originalData?: string; -} - -export interface WidgetStyleState extends WidgetStyle { - bindings?: string[]; - _originalData?: string; -} - -export interface VisBaseWidgetState { - applyBindings?: false | true | { top: string | number; left: string | number }; - data: WidgetDataState | GroupDataState; - draggable?: boolean; - editMode: boolean; - gap?: number; - hideHelper?: boolean; - isHidden?: boolean; - multiViewWidget?: boolean; - resizable?: boolean; - resizeHandles?: ResizeHandler[]; - rxStyle?: WidgetStyleState; - selected?: boolean; - selectedOne?: boolean; - showRelativeMoveMenu?: boolean; - style: WidgetStyleState; - usedInWidget: boolean; - widgetHint?: 'light' | 'dark' | 'hide'; -} - -export interface VisBaseWidgetMovement { - top: number; - left: number; - width: number; - height: number; - order?: AnyWidgetId[]; -} - -interface Handler { - top?: string | number; - left?: string | number; - bottom?: string | number; - height?: string | number; - right?: string | number; - width?: string | number; - cursor: 'nwse-resize' | 'ns-resize' | 'ew-resize' | 'nesw-resize' | 'default'; - background: string; - opacity: number; - borderTop?: string; - borderBottom?: string; - borderLeft?: string; - borderRight?: string; -} - -interface ResizerElement extends HTMLDivElement { - _storedOpacity: string; -} - -/** - * Methods which should be optionally implemented by inherited classes - */ -interface VisBaseWidget { - renderSignals(): React.ReactNode; - 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< - VisBaseWidgetProps, - TState & VisBaseWidgetState -> { - static FORBIDDEN_CHARS = /[^._\-/ :!#$%&()+=@^{}|~]+/g; // from https://github.com/ioBroker/ioBroker.js-controller/blob/master/packages/common/lib/common/tools.js - - /** We do not store the SVG Element in the state because it is cyclic */ - private relativeMoveMenu?: EventTarget & SVGSVGElement; - - /** if currently resizing */ - private resize: Resize = false; - - private readonly uuid = `${Date.now()}.${Math.round(Math.random() * 1_000_000)}`; - - protected refService = React.createRef(); - - protected widDiv: null | CanHTMLDivElement = null; - - readonly onCommandBound: typeof this.onCommand; - - protected onResize: undefined | (() => void); - - private updateInterval?: ReturnType; - - private pressTimeout?: ReturnType; - - private shadowDiv: HTMLDivElement | null; - - private stealCursor?: string; - - private beforeIncludeColor?: string; - - private lastClick?: number; - - protected movement?: VisBaseWidgetMovement; - - /** If resizing is currently locked */ - protected resizeLocked?: boolean; - - protected visDynamicResizable: - | undefined - | null - | { default: boolean; desiredSize: { width: number; height: number } | boolean }; - - protected isCanWidget?: boolean; - - constructor(props: VisBaseWidgetProps) { - super(props); - - const widget = props.context.views[props.view].widgets[props.id]; - const multiViewWidget = props.id.includes('_'); - - const selected = !multiViewWidget && props.editMode && props.selectedWidgets?.includes(props.id); - - const data: WidgetDataState | GroupDataState = deepClone(widget.data || {}) as WidgetDataState | GroupDataState; - const style: WidgetStyle = deepClone(widget.style || {}); - VisBaseWidget.replacePRJ_NAME(data, style, props); - - this.state = { - data, - style, - // eslint-disable-next-line react/no-unused-state - applyBindings: false, - editMode: !multiViewWidget && this.props.editMode, - multiViewWidget, - selected, - selectedOne: selected && this.props.selectedWidgets.length === 1, - resizable: true, - resizeHandles: ['n', 'e', 's', 'w', 'nw', 'ne', 'sw', 'se'], - widgetHint: props.context.widgetHint, - isHidden: VisBaseWidget.isWidgetFilteredOutStatic( - props.viewsActiveFilter, - widget.data, - props.view, - props.editMode, - ), - usedInWidget: widget.usedInWidget, - hideHelper: false, - gap: - style.position === 'relative' - ? isVarFinite(props.context.views[props.view].settings?.rowGap) - ? parseFloat(props.context.views[props.view].settings?.rowGap as string) - : 0 - : 0, - } as TState & VisBaseWidgetState; - - this.onCommandBound = this.onCommand.bind(this); - } - - static replacePRJ_NAME(data: Record, style: Record, props: VisBaseWidgetProps): void { - const context = props.context; - if (data) { - delete data._originalData; - Object.keys(data).forEach(attr => { - if ( - attr && - data[attr] && - typeof data[attr] === 'string' && - (attr.startsWith('src') || attr.endsWith('src') || attr.includes('icon')) && - data[attr].startsWith('_PRJ_NAME') - ) { - if (!data._originalData) { - data._originalData = JSON.stringify(data); - } - // "_PRJ_NAME".length = 9 - data[attr] = - `../${context.adapterName}.${context.instance}/${context.projectName}${data[attr].substring(9)}`; - } - }); - } - if (style) { - delete style._originalData; - if (style['background-image'] && style['background-image'].startsWith('_PRJ_NAME')) { - if (!style._originalData) { - style._originalData = JSON.stringify(style); - } - style['background-image'] = - `../${context.adapterName}.${context.instance}/${context.projectName}${style['background-image'].substring(9)}`; // "_PRJ_NAME".length = 9 - } - } - } - - componentDidMount(): void { - // register service ref by view for resize and move only in edit mode - this.props.askView && - this.props.askView('register', { - id: this.props.id, - uuid: this.uuid, - widDiv: this.widDiv, - refService: this.refService, - onMove: this.onMove, - onResize: this.onResize, - onTempSelect: this.onTempSelect, - onCommand: this.onCommandBound, - }); - } - - componentWillUnmount(): void { - this.updateInterval && clearInterval(this.updateInterval); - this.updateInterval = undefined; - - this.pressTimeout && clearTimeout(this.pressTimeout); - this.pressTimeout = undefined; - - // delete service ref from view only in edit mode - this.props.askView && this.props.askView('unregister', { id: this.props.id, uuid: this.uuid }); - if (this.shadowDiv) { - this.shadowDiv.remove(); - this.shadowDiv = null; - } - } - - // this method may be not in form onCommand = command => {}, as it can be overloaded - // eslint-disable-next-line @typescript-eslint/no-unused-vars - onCommand(command: VisWidgetCommand, _option?: any): any { - if (command === 'includePossible') { - const overlay: HTMLDivElement = this.refService.current?.querySelector('.vis-editmode-overlay'); - if (overlay && this.beforeIncludeColor === undefined) { - this.beforeIncludeColor = overlay.style.backgroundColor; - overlay.style.backgroundColor = 'rgba(0, 255, 0, 0.3)'; - } - return true; - } - if (command === 'includePossibleNOT') { - if (this.beforeIncludeColor !== undefined) { - const overlay: HTMLDivElement = this.refService.current?.querySelector('.vis-editmode-overlay'); - overlay && (overlay.style.backgroundColor = this.beforeIncludeColor); - this.beforeIncludeColor = undefined; - } - return true; - } - - if (command === 'startStealMode') { - this.stealCursor = this.refService.current?.style.cursor || 'nocursor'; - if (this.refService.current) { - this.refService.current.style.cursor = 'crosshair'; - this.refService.current.className = addClass( - this.refService.current.className, - 'vis-editmode-steal-style', - ); - } - // eslint-disable-next-line no-undef - const resizers: NodeListOf = - this.refService.current?.querySelectorAll('.vis-editmode-resizer'); - resizers?.forEach(item => (item.style.display = 'none')); - return true; - } - - if (command === 'cancelStealMode') { - if (this.stealCursor !== 'nocursor' && this.refService.current && this.stealCursor) { - this.refService.current.style.cursor = this.stealCursor; - } - this.stealCursor = undefined; - if (this.refService.current) { - this.refService.current.className = removeClass( - this.refService.current.className, - 'vis-editmode-steal-style', - ); - } - // eslint-disable-next-line no-undef - const resizers: NodeListOf = - this.refService.current?.querySelectorAll('.vis-editmode-resizer'); - resizers?.forEach(item => (item.style.display = '')); - return true; - } - - if (command === 'startMove' || command === 'startResize') { - const overlay = this.refService.current?.querySelector('.vis-editmode-overlay'); - if (overlay) { - if (this.state.selected) { - overlay.className = removeClass(overlay.className, 'vis-editmode-selected'); - } else { - overlay.className = removeClass(overlay.className, 'vis-editmode-overlay-not-selected'); - } - } - - if (command === 'startResize') { - this.resize = true; - } - return true; - } - - if (command === 'stopMove' || command === 'stopResize') { - const overlay: HTMLDivElement = this.refService.current?.querySelector('.vis-editmode-overlay'); - if (overlay) { - if (this.beforeIncludeColor !== undefined) { - overlay.style.backgroundColor = this.beforeIncludeColor; - this.beforeIncludeColor = undefined; - } - - if (this.state.selected) { - overlay.className = addClass(overlay.className, 'vis-editmode-selected'); - } else { - overlay.className = addClass(overlay.className, 'vis-editmode-overlay-not-selected'); - } - } - - // show resizers again - // eslint-disable-next-line no-undef - const resizers: NodeListOf = - this.refService.current?.querySelectorAll('.vis-editmode-resizer'); - resizers?.forEach(item => (item.style.display = 'block')); - - if (command === 'stopResize') { - this.resize = false; - } - return true; - } - - return false; - } - - static getDerivedStateFromProps( - props: VisBaseWidgetProps, - state: VisBaseWidgetState, - ): Partial | null { - const context = props.context; - let newState: Partial | null = null; // No change to state by default - let widget = context.views[props.view].widgets[props.id]; - const gap = - widget.style.position === 'relative' - ? isVarFinite(context.views[props.view].settings?.rowGap) - ? parseFloat(context.views[props.view].settings?.rowGap as string) - : 0 - : 0; - let copied = false; - - if (widget.groupid) { - // this widget belongs to group - const parentWidgetData = context.views[props.view].widgets[widget.groupid].data; - // extract attribute names - const names = Object.keys(parentWidgetData) - .map(attr => (attr.startsWith('attrType_') ? attr.substring(9) : null)) - .filter(attr => attr); - - if (names.length && widget.data) { - for (const [attr, val] of Object.entries(widget.data)) { - if (typeof val === 'string' && names.find(a => val.includes(a))) { - const result = replaceGroupAttr(widget.data[attr], parentWidgetData); - if (result.doesMatch) { - // create a copy as we will substitute the values - if (!copied) { - copied = true; - widget = deepClone(widget); - } - widget.data[attr] = result.newString || ''; - } - } - } - } - } - - // take actual (old) style and data - const styleStr: string = state.style?._originalData ? state.style._originalData : JSON.stringify(state.style); - const dataStr: string = state.data?._originalData ? state.data._originalData : JSON.stringify(state.data); - - const isHidden = VisBaseWidget.isWidgetFilteredOutStatic( - props.viewsActiveFilter, - widget.data, - props.view, - props.editMode, - ); - - // compare with new style and data - if ( - JSON.stringify(widget.style || {}) !== styleStr || - JSON.stringify(widget.data || {}) !== dataStr || - gap !== state.gap || - isHidden !== state.isHidden - ) { - if (!props.runtime) { - const styleObj: WidgetStyle = JSON.parse(styleStr); - Object.keys(styleObj as Record).forEach(attr => { - const oldStyle = (widget.style as Record)[attr]; - const newStyle = (styleObj as Record)[attr]; - if (newStyle !== oldStyle) { - console.log( - `[${Date.now()} / ${props.id}] Rerender because of style.${attr}: ${newStyle} !== ${oldStyle}`, - ); - } - }); - Object.keys(widget.style).forEach((attr: string) => { - const oldStyle = (widget.style as Record)[attr]; - const newStyle = (styleObj as Record)[attr]; - if (newStyle !== oldStyle) { - console.log( - `[${Date.now()} / ${props.id}] Rerender because of style.${attr}: ${newStyle} !== ${oldStyle}`, - ); - } - }); - - const dataObj: GroupData = JSON.parse(dataStr); - Object.keys(dataObj).forEach((attr: string) => { - if (JSON.stringify(dataObj[attr]) !== JSON.stringify(widget.data[attr])) { - console.log( - `[${Date.now()} / ${props.id}] Rerender because of data.${attr}: ${dataObj[attr]} !== ${widget.data[attr]}`, - ); - } - }); - } - - let data: WidgetDataState; - let style: WidgetStyleState; - // restore original data - if (copied) { - data = (widget.data as WidgetDataState) || { bindings: [] }; - // detect for CanWidgets if size was changed - style = (widget.style as WidgetStyleState) || { bindings: [] }; - } else { - data = widget.data - ? (deepClone(widget.data) as WidgetDataState) - : ({ bindings: [] } as WidgetDataState); - // detect for CanWidgets if size was changed - style = widget.style - ? (deepClone(widget.style) as WidgetStyleState) - : ({ bindings: [] } as WidgetStyleState); - } - - // replace all _PRJ_NAME with vis.0/name - VisBaseWidget.replacePRJ_NAME(data, style, props); - - newState = {}; - newState.isHidden = isHidden; - newState.style = style; - newState.data = data; - newState.gap = gap; - newState.applyBindings = { top: widget.style.top as number, left: widget.style.left as number }; - } - - if (props.editMode !== state.editMode) { - newState = newState || {}; - newState.editMode = props.editMode; - newState.applyBindings = true; - } - - if (props.context.widgetHint !== state.widgetHint) { - newState = newState || {}; - newState.widgetHint = props.context.widgetHint; - } - - const selected = - !state.multiViewWidget && - props.editMode && - props.selectedWidgets && - props.selectedWidgets.includes(props.id); - const selectedOne = selected && props.selectedWidgets.length === 1; - - if (selected !== state.selected || selectedOne !== state.selectedOne) { - newState = newState || {}; - newState.selected = selected; - newState.selectedOne = selectedOne; - } - - if (!!widget.usedInWidget !== !!state.usedInWidget) { - newState = newState || {}; - newState.usedInWidget = !!widget.usedInWidget; - } - - return newState; - } - - static removeFromArray(items: Record, IDs: string[], view: string, widget: string): void { - items && - Object.keys(items).forEach(id => { - if (!IDs || IDs.includes(id)) { - for (let i = items[id].length - 1; i >= 0; i--) { - const item = items[id][i]; - if (item.view === view && item.widget === widget) { - items[id].splice(i, 1); - } - } - } - }); - } - - static parseStyle(style: string, isRxStyle?: boolean): Record { - const result: Record = {}; - // style is like "height: 10; width: 20" - (style || '').split(';').forEach(part => { - part = part.trim(); - if (part) { - let [attr, value] = part.split(':'); - attr = attr.trim(); - if (attr && value) { - value = value.trim(); - if (!isRxStyle && (attr === 'top' || attr === 'left' || attr === 'width' || attr === 'height')) { - if (!isRxStyle) { - if (value !== '0' && value.match(/^[-+]?\d+$/)) { - value = `${value}px`; - } - } else { - const f = parseFloat(value); - if (value === f.toString()) { - // @ts-expect-error fix later - value = f; - } - } - } - - if (value) { - if (isRxStyle) { - attr = attr.replace(/(-\w)/, text => text[1].toUpperCase()); - } - - result[attr] = value; - } - } - } - }); - - return result; - } - - onMouseDown(e: React.MouseEvent): void { - e.stopPropagation(); - if (this.stealCursor && !this.state.multiViewWidget) { - e.stopPropagation(); - this.props.mouseDownOnView(e, this.props.id, this.props.isRelative); - return; - } - if (this.props.context.views[this.props.view].widgets[this.props.id].data.locked) { - return; - } - - if (this.lastClick !== undefined && Date.now() - this.lastClick < 250) { - console.log('AAA'); - } - - // detect double click for multi-view widgets - if (this.lastClick) { - if (this.state.multiViewWidget) { - if (Date.now() - this.lastClick < 250) { - // change view - const parts: string[] = this.props.id.split('_'); - const multiView: string = parts[0]; - const multiId: AnyWidgetId = parts[1] as AnyWidgetId; - this.props.context.setSelectedWidgets([multiId], multiView); - } - - this.lastClick = Date.now(); - return; - } - } - - if (e.shiftKey || e.ctrlKey) { - // add or remove - const pos = this.props.selectedWidgets.indexOf(this.props.id); - if (pos === -1) { - const selectedWidgets = [...this.props.selectedWidgets, this.props.id]; - this.props.context.setSelectedWidgets(selectedWidgets); - } else { - const selectedWidgets = [...this.props.selectedWidgets]; - selectedWidgets.splice(pos, 1); - this.props.context.setSelectedWidgets(selectedWidgets); - } - return; - } - - if (!this.props.selectedWidgets.includes(this.props.id)) { - // set select - this.props.context.setSelectedWidgets([this.props.id]); - } else if (this.props.moveAllowed && this.state.draggable !== false) { - if (!this.props.isRelative) { - // User can drag only objects of the same type - this.props.mouseDownOnView( - e, - this.props.id, - this.props.isRelative, - false, - this.lastClick !== undefined && Date.now() - this.lastClick < 300, - ); - } else if (this.lastClick && Date.now() - this.lastClick < 250) { - // if double-click on a group - if ( - this.props.selectedWidgets.length === 1 && - this.props.context.views[this.props.view].widgets[this.props.selectedWidgets[0]].tpl === '_tplGroup' - ) { - this.props.context.setSelectedGroup(this.props.selectedWidgets[0]); - } - } - } - this.lastClick = Date.now(); - } - - createWidgetMovementShadow(): void { - if (this.shadowDiv) { - this.shadowDiv.remove(); - this.shadowDiv = null; - } - - if (!this.movement) { - console.error('Unknown issue, movement is falsy'); - return; - } - - this.shadowDiv = window.document.createElement('div'); - this.shadowDiv.setAttribute('id', `${this.props.id}_shadow`); - this.shadowDiv.className = 'vis-editmode-widget-shadow'; - if (this.refService.current) { - this.shadowDiv.style.width = `${this.refService.current.clientWidth}px`; - this.shadowDiv.style.height = `${this.refService.current.clientHeight}px`; - if (this.refService.current.style.borderRadius) { - this.shadowDiv.style.borderRadius = this.refService.current.style.borderRadius; - } - } - - let parentDiv: HTMLElement; - if (Object.prototype.hasOwnProperty.call(this.props.refParent, 'current')) { - parentDiv = this.props.refParent.current; - } else { - parentDiv = this.props.refParent as any as HTMLElement; - } - - if (this.widDiv) { - parentDiv.insertBefore(this.shadowDiv, this.widDiv); - this.widDiv.style.position = 'absolute'; - this.widDiv.style.left = `${this.movement.left}px`; - this.widDiv.style.top = `${this.movement.top}px`; - } else { - parentDiv.insertBefore(this.shadowDiv, this.refService.current); - if (this.refService.current) { - this.refService.current.style.position = 'absolute'; - } - } - if (this.refService.current) { - this.refService.current.style.left = `${this.movement.left}px`; - this.refService.current.style.top = `${this.movement.top}px`; - } - } - - isResizable(): boolean { - if (this.visDynamicResizable) { - // take data from field "visResizable" - // this value cannot be bound, so we can read it directly from widget.data - return typeof this.state.data.visResizable === 'boolean' - ? this.state.data.visResizable - : this.visDynamicResizable.default; // by default all widgets are resizable - } - - return this.state.resizable; - } - - onMove = ( - x: number | undefined, - y: number | undefined, - save?: boolean, - calculateRelativeWidgetPosition?: - | null - | ((id: AnyWidgetId, left: string, top: string, shadowDiv: HTMLDivElement, order: AnyWidgetId[]) => void), - ): void => { - if (this.state.multiViewWidget || !this.state.editMode) { - return; - } - - const movement = this.movement; - - if (!this.refService.current) { - return; - } - if (this.resize) { - if (this.isResizable() === false) { - return; - } - - if (x === undefined) { - // start of resizing - const rect = (this.widDiv || this.refService.current)?.getBoundingClientRect(); - - if (rect) { - this.movement = { - top: this.refService.current.offsetTop, - left: this.refService.current.offsetLeft, - width: rect.width, - height: rect.height, - }; - } - // eslint-disable-next-line no-undef - const resizers: NodeListOf = - this.refService.current.querySelectorAll('.vis-editmode-resizer'); - resizers.forEach(item => { - item._storedOpacity = item.style.opacity; - item.style.opacity = '0.3'; - }); - } else if (movement && y !== undefined /* && x !== undefined */) { - if (this.resize === 'top') { - this.refService.current.style.top = `${movement.top + y}px`; - this.refService.current.style.height = `${movement.height - y}px`; - if (this.resizeLocked) { - // noinspection JSSuspiciousNameCombination - this.refService.current.style.width = this.refService.current.style.height; - } - - if (this.widDiv) { - this.widDiv.style.top = `${movement.top + y}px`; - this.widDiv.style.height = `${movement.height - y}px`; - if (this.resizeLocked) { - // noinspection JSSuspiciousNameCombination - this.widDiv.style.width = this.widDiv.style.height; - } - } - } else if (this.resize === 'bottom') { - this.widDiv && (this.widDiv.style.height = `${movement.height + y}px`); - this.refService.current.style.height = `${movement.height + y}px`; - - if (this.resizeLocked) { - // noinspection JSSuspiciousNameCombination - this.refService.current.style.width = this.refService.current.style.height; - } - if (this.widDiv && this.resizeLocked) { - // noinspection JSSuspiciousNameCombination - this.widDiv.style.width = this.widDiv.style.height; - } - } else if (this.resize === 'left') { - this.refService.current.style.left = `${movement.left + x}px`; - this.refService.current.style.width = `${movement.width - x}px`; - if (this.resizeLocked) { - // noinspection JSSuspiciousNameCombination - this.refService.current.style.height = this.refService.current.style.width; - } - if (this.widDiv) { - this.widDiv.style.left = `${movement.left + x}px`; - this.widDiv.style.width = `${movement.width - x}px`; - if (this.resizeLocked) { - // noinspection JSSuspiciousNameCombination - this.widDiv.style.height = this.widDiv.style.width; - } - } - } else if (this.resize === 'right') { - this.widDiv && (this.widDiv.style.width = `${movement.width + x}px`); - this.refService.current.style.width = `${movement.width + x}px`; - if (this.resizeLocked) { - // noinspection JSSuspiciousNameCombination - this.refService.current.style.height = this.refService.current.style.width; - } - if (this.widDiv && this.resizeLocked) { - // noinspection JSSuspiciousNameCombination - this.widDiv.style.height = this.widDiv.style.width; - } - } else if (this.resize === 'top-left') { - this.refService.current.style.top = `${movement.top + y}px`; - this.refService.current.style.left = `${movement.left + x}px`; - this.refService.current.style.height = `${movement.height - y}px`; - this.refService.current.style.width = `${movement.width - x}px`; - if (this.resizeLocked) { - // noinspection JSSuspiciousNameCombination - this.refService.current.style.height = this.refService.current.style.width; - } - - if (this.widDiv) { - this.widDiv.style.top = `${movement.top + y}px`; - this.widDiv.style.left = `${movement.left + x}px`; - this.widDiv.style.height = `${movement.height - y}px`; - this.widDiv.style.width = `${movement.width - x}px`; - if (this.resizeLocked) { - // noinspection JSSuspiciousNameCombination - this.widDiv.style.height = this.widDiv.style.width; - } - } - } else if (this.resize === 'top-right') { - this.refService.current.style.top = `${movement.top + y}px`; - this.refService.current.style.height = `${movement.height - y}px`; - this.refService.current.style.width = `${movement.width + x}px`; - if (this.resizeLocked) { - // noinspection JSSuspiciousNameCombination - this.refService.current.style.height = this.refService.current.style.width; - } - if (this.widDiv) { - this.widDiv.style.top = `${movement.top + y}px`; - this.widDiv.style.height = `${movement.height - y}px`; - this.widDiv.style.width = `${movement.width + x}px`; - if (this.resizeLocked) { - // noinspection JSSuspiciousNameCombination - this.widDiv.style.height = this.widDiv.style.width; - } - } - } else if (this.resize === 'bottom-left') { - this.refService.current.style.left = `${movement.left + x}px`; - this.refService.current.style.height = `${movement.height + y}px`; - this.refService.current.style.width = `${movement.width - x}px`; - if (this.resizeLocked) { - // noinspection JSSuspiciousNameCombination - this.refService.current.style.height = this.refService.current.style.width; - } - if (this.widDiv) { - this.widDiv.style.left = `${movement.left + x}px`; - this.widDiv.style.height = `${movement.height + y}px`; - this.widDiv.style.width = `${movement.width - x}px`; - if (this.resizeLocked) { - // noinspection JSSuspiciousNameCombination - this.widDiv.style.height = this.widDiv.style.width; - } - } - } else { - // bottom-right - this.refService.current.style.height = `${movement.height + y}px`; - this.refService.current.style.width = `${movement.width + x}px`; - if (this.resizeLocked) { - // noinspection JSSuspiciousNameCombination - this.refService.current.style.height = this.refService.current.style.width; - } - if (this.widDiv) { - this.widDiv.style.height = `${movement.height + y}px`; - this.widDiv.style.width = `${movement.width + x}px`; - if (this.resizeLocked) { - // noinspection JSSuspiciousNameCombination - this.widDiv.style.height = this.widDiv.style.width; - } - } - } - } - - // end of resize - if (save) { - // eslint-disable-next-line no-undef - const resizers: NodeListOf = - this.refService.current?.querySelectorAll('.vis-editmode-resizer'); - resizers?.forEach(item => { - if (item._storedOpacity !== undefined) { - item.style.opacity = item._storedOpacity; - delete item._storedOpacity; - } - }); - this.resize = false; - this.props.context.onWidgetsChanged([ - { - wid: this.props.id, - view: this.props.view, - style: { - top: this.refService.current.style.top, - left: this.refService.current.style.left, - width: this.refService.current.style.width, - height: this.refService.current.style.height, - }, - }, - ]); - - this.movement = undefined; - } - } else if (x === undefined) { - if (this.state.draggable === false) { - return; - } - - // initiate movement - this.movement = { - top: this.refService.current.offsetTop, - left: this.refService.current.offsetLeft, - order: [...this.props.relativeWidgetOrder], - width: 0, - height: 0, - }; - - // hide resizers - // eslint-disable-next-line no-undef - const resizers: NodeListOf = - this.refService.current.querySelectorAll('.vis-editmode-resizer'); - resizers.forEach(item => (item.style.display = 'none')); - - if (this.props.isRelative) { - // create shadow widget - this.createWidgetMovementShadow(); - } - } else if (this.movement && y !== undefined && x !== undefined) { - // move widget - const left = `${this.movement.left + x}px`; - const top = `${this.movement.top + y}px`; - - if (this.refService.current) { - this.refService.current.style.left = left; - this.refService.current.style.top = top; - } - - if (this.widDiv) { - this.widDiv.style.left = left; - this.widDiv.style.top = top; - - // @ts-expect-error check later - if (this.widDiv._customHandlers && this.widDiv._customHandlers.onMove) { - // @ts-expect-error check later - this.widDiv._customHandlers.onMove(this.widDiv, this.props.id); - } - } - - if (this.props.isRelative && calculateRelativeWidgetPosition) { - // calculate widget position - calculateRelativeWidgetPosition(this.props.id, left, top, this.shadowDiv, this.movement.order); - } - - // End of movement - if (save) { - // show resizers - // eslint-disable-next-line no-undef - const resizers: NodeListOf = - this.refService.current.querySelectorAll('.vis-editmode-resizer'); - resizers.forEach(item => (item.style.display = 'block')); - - if (this.props.isRelative) { - let parentDiv: HTMLElement; - if (Object.prototype.hasOwnProperty.call(this.props.refParent, 'current')) { - parentDiv = this.props.refParent.current; - } else { - parentDiv = this.props.refParent as any as HTMLElement; - } - // create shadow widget - if (this.widDiv) { - this.widDiv.style.position = 'relative'; - this.widDiv.style.top = '0'; - this.widDiv.style.left = '0'; - parentDiv.insertBefore(this.widDiv, this.shadowDiv); - this.refService.current.style.top = `${this.widDiv.offsetTop}px`; - this.refService.current.style.left = `${this.widDiv.offsetLeft}px`; - } else { - parentDiv.insertBefore(this.refService.current, this.shadowDiv); - this.refService.current.style.position = 'relative'; - this.refService.current.style.top = '0'; - this.refService.current.style.left = '0'; - } - this.shadowDiv.remove(); - this.shadowDiv = null; - - this.props.context.onWidgetsChanged( - [ - { - wid: this.props.id, - view: this.props.view, - style: { - left: null, - top: null, - }, - }, - ], - this.props.view, - { order: this.movement.order }, - ); - } else { - this.props.context.onWidgetsChanged([ - { - wid: this.props.id, - view: this.props.view, - style: { - left: this.movement.left + x, - top: this.movement.top + y, - }, - }, - ]); - } - - this.movement = undefined; - } - } - }; - - onTempSelect = (selected?: boolean): void => { - const ref: HTMLElement = this.refService.current?.querySelector('.vis-editmode-overlay'); - if (!ref) { - return; - } - if (selected === null || selected === undefined) { - // restore original state - if (this.props.selectedWidgets.includes(this.props.id)) { - if (!ref.className.includes('vis-editmode-selected')) { - ref.className = addClass('vis-editmode-selected', ref.className); - } - } else { - ref.style.backgroundColor = ''; - ref.className = removeClass(ref.className, 'vis-editmode-selected'); - } - } else if (selected) { - if (!ref.className.includes('vis-editmode-selected')) { - ref.className = addClass('vis-editmode-selected', ref.className); - } - } else { - ref.className = removeClass(ref.className, 'vis-editmode-selected'); - } - }; - - onResizeStart(e: React.MouseEvent, type: Resize): void { - e.stopPropagation(); - this.resize = type; - this.props.mouseDownOnView(e, this.props.id, this.props.isRelative, true); - } - - getResizeHandlers(selected: boolean, widget: Widget, borderWidth: string): React.JSX.Element[] | null { - if (!this.state.editMode || !selected || this.props.selectedWidgets?.length !== 1) { - return null; - } - - const thickness = 0.4; - const shift = 0.3; - const square = 0.4; - - const squareShift = `calc(${shift - square}em - ${borderWidth})`; - const squareWidthHeight = `${square}em`; - const shiftEm = `${shift}em`; - const thicknessEm = `${thickness}em`; - const offsetEm = `calc(${shift - thickness}em - ${borderWidth})`; - - const widgetWidth100 = widget.style.width === '100%'; - const widgetHeight100 = widget.style.height === '100%'; - - const color = '#014488'; // it is so, to be able to change color in web storm - const border = `0.1em dashed ${color}`; - const borderDisabled = '0.1em dashed #888'; - - const resizable = this.isResizable(); - - let resizeHandlers: ResizeHandler[] = resizable && this.state.resizeHandles ? this.state.resizeHandles : []; - - if (resizable && this.props.selectedGroup && this.props.selectedGroup === this.props.id) { - resizeHandlers = ['s', 'e', 'se']; - } - - const RESIZERS_OPACITY = 0.9; - const RESIZERS_OPACITY_DISABLED = 0.5; - - const isRelative = widget.usedInWidget || this.props.isRelative; - - const controllable = { - top: !isRelative && resizeHandlers.includes('n'), - bottom: !widget.usedInWidget && !widgetHeight100 && resizeHandlers.includes('s'), - left: !isRelative && resizeHandlers.includes('w'), - right: !widget.usedInWidget && !widgetWidth100 && resizeHandlers.includes('e'), - 'top-left': !widgetHeight100 && !widgetWidth100 && !isRelative && resizeHandlers.includes('nw'), - 'top-right': !widgetHeight100 && !widgetWidth100 && !isRelative && resizeHandlers.includes('ne'), - 'bottom-left': !widgetHeight100 && !widgetWidth100 && !isRelative && resizeHandlers.includes('sw'), - 'bottom-right': - !widgetHeight100 && !widgetWidth100 && !widget.usedInWidget && resizeHandlers.includes('se'), - }; - - const handlers: Record = { - top: { - top: offsetEm, - height: thicknessEm, - left: controllable['top-left'] ? shiftEm : 0, - right: controllable['top-right'] ? shiftEm : 0, - cursor: 'ns-resize', - background: 'transparent', - opacity: controllable.top ? RESIZERS_OPACITY : RESIZERS_OPACITY_DISABLED, - borderTop: controllable.top ? border : borderDisabled, - }, - bottom: { - bottom: offsetEm, - height: thicknessEm, - left: controllable['bottom-left'] ? shiftEm : 0, - right: controllable['bottom-right'] ? shiftEm : 0, - cursor: 'ns-resize', - background: 'transparent', - opacity: controllable.bottom ? RESIZERS_OPACITY : RESIZERS_OPACITY_DISABLED, - borderBottom: controllable.bottom ? border : borderDisabled, - }, - left: { - top: controllable['top-left'] ? shiftEm : 0, - bottom: controllable['bottom-left'] ? shiftEm : 0, - left: offsetEm, - width: thicknessEm, - cursor: 'ew-resize', - background: 'transparent', - opacity: controllable.left ? RESIZERS_OPACITY : RESIZERS_OPACITY_DISABLED, - borderLeft: controllable.left ? border : borderDisabled, - }, - right: { - top: controllable['top-right'] ? shiftEm : 0, - bottom: controllable['bottom-right'] ? shiftEm : 0, - right: offsetEm, - width: thicknessEm, - cursor: 'ew-resize', - background: 'transparent', - opacity: controllable.right ? RESIZERS_OPACITY : RESIZERS_OPACITY_DISABLED, - borderRight: controllable.right ? border : borderDisabled, - }, - 'top-left': { - top: squareShift, - height: squareWidthHeight, - left: squareShift, - width: squareWidthHeight, - cursor: 'nwse-resize', - background: color, - opacity: RESIZERS_OPACITY, - }, - 'top-right': { - top: squareShift, - height: squareWidthHeight, - right: squareShift, - width: squareWidthHeight, - cursor: 'nesw-resize', - background: color, - opacity: RESIZERS_OPACITY, - }, - 'bottom-left': { - bottom: squareShift, - height: squareWidthHeight, - left: squareShift, - width: squareWidthHeight, - cursor: 'nesw-resize', - background: color, - opacity: RESIZERS_OPACITY, - }, - 'bottom-right': { - bottom: squareShift, - height: squareWidthHeight, - right: squareShift, - width: squareWidthHeight, - cursor: 'nwse-resize', - background: color, - opacity: RESIZERS_OPACITY, - }, - }; - - const style = { - position: 'absolute', - zIndex: 1001, - }; - - return Object.keys(handlers).map((key: string) => { - const handler = handlers[key]; - if (!(controllable as Record)[key]) { - if (key.includes('-')) { - return null; - } - handler.cursor = 'default'; - } - - return ( -
this.onResizeStart(e, key as Resize) : undefined - } - /> - ); - }); - } - - // eslint-disable-next-line react/no-unused-class-component-methods - isUserMemberOfGroup(user: string, userGroups: string[]): boolean { - if (!userGroups) { - return true; - } - if (!Array.isArray(userGroups)) { - userGroups = [userGroups]; - } - - return !!userGroups.find(groupId => { - const group = this.props.context.userGroups[`system.group.${groupId}`]; - return group?.common?.members?.length && group.common.members.includes(`system.user.${user}`); - }); - } - - static isWidgetFilteredOutStatic( - viewsActiveFilter: { [view: string]: string[] } | null, - widgetData: WidgetData | GroupData, - view: string, - editMode: boolean, - ): boolean { - if (!viewsActiveFilter) { - console.warn(`viewsActiveFilter is not defined in ${view}, data: ${JSON.stringify(widgetData)}`); - return false; - } - - const vf = viewsActiveFilter[view]; - if (!editMode && widgetData?.filterkey && vf?.length) { - if (vf[0] === '$') { - return true; - } - - let filterKeys: string[]; - - if (typeof widgetData.filterkey === 'string') { - // deprecated, but for back compatibility - filterKeys = (widgetData.filterkey as any as string) - .split(',') - .map(f => f.trim()) - .filter(f => f); - } else { - filterKeys = widgetData.filterkey; - } - - // we cannot use here find as filterkey could be observable (can) and is not normal array - for (let f = 0; f < filterKeys.length; f++) { - if (vf.includes(filterKeys[f])) { - return false; // widget is not hidden - } - } - return true; - } - - return false; - } - - // eslint-disable-next-line react/no-unused-class-component-methods - isWidgetFilteredOut(widgetData: WidgetData | GroupData): boolean { - return VisBaseWidget.isWidgetFilteredOutStatic( - this.props.viewsActiveFilter, - widgetData, - this.props.view, - this.state.editMode, - ); - } - - // eslint-disable-next-line react/no-unused-class-component-methods - static isWidgetHidden(widgetData: WidgetData | GroupData, states: VisRxWidgetStateValues, id: string): boolean { - const oid = widgetData['visibility-oid']; - const condition = widgetData['visibility-cond']; - - if (oid) { - if (!Object.keys(states).includes(`${oid}.val`)) { - // if we don't have state information yet - hide to prevent shortly showing widget during render - return true; - } - - let val = states[`${oid}.val`]; - - if (val === undefined || val === null) { - return condition === 'not exist'; - } - - let value = widgetData['visibility-val']; - - 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); - } - - // Take care: return true if the widget is hidden! - 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 val === 'null'; - case 'not exist': - return val !== 'null'; - default: - console.log(`[${id}] Unknown visibility condition: ${condition}`); - return false; - } - } else { - return condition && condition === 'not exist'; - } - } - - /** - * Render the widget body - */ - // eslint-disable-next-line class-methods-use-this,no-unused-vars, @typescript-eslint/no-unused-vars - renderWidgetBody(_props: RxRenderWidgetProps): React.JSX.Element | React.JSX.Element[] | null { - // Default render method. Normally it should be overwritten - return ( -
-
- {I18n.t('Unknown widget type "%s"', this.props.tpl)} -
-
{JSON.stringify(this.state.data, null, 2)}
-
- ); - } - - // eslint-disable-next-line react/no-unused-class-component-methods - changeOrder(e: React.MouseEvent, dir: number): void { - e.stopPropagation(); - e.preventDefault(); - if (this.state.multiViewWidget || !this.state.editMode) { - return; - } - - const order = [...this.props.relativeWidgetOrder]; - - const pos = order.indexOf(this.props.id); - if (dir > 0) { - if (pos === order.length - 1) { - return; - } - const nextId = order[pos + 1]; - order[pos + 1] = this.props.id; - order[pos] = nextId; - } else if (!pos) { - return; - } else { - const nextId = order[pos - 1]; - order[pos - 1] = this.props.id; - order[pos] = nextId; - } - - this.props.context.onWidgetsChanged(null, this.props.view, { order }); - } - - static formatValue(value: string | number, decimals: number | string, _format?: string): string { - if (typeof decimals !== 'number') { - decimals = 2; - _format = decimals as any as string; - } - const format = _format === undefined ? '.,' : _format; - if (typeof value !== 'number') { - value = parseFloat(value); - } - return Number.isNaN(value) - ? '' - : value - .toFixed(decimals || 0) - .replace(format[0], format[1]) - .replace(/\B(?=(\d{3})+(?!\d))/g, format[0]); - } - - formatIntervalHelper(value: number, type: 'seconds' | 'minutes' | 'hours' | 'days'): string { - let singular; - let plural; - let special24; - if (this.props.context.lang === 'de') { - if (type === 'seconds') { - singular = 'Sekunde'; - plural = 'Sekunden'; - } else if (type === 'minutes') { - singular = 'Minute'; - plural = 'Minuten'; - } else if (type === 'hours') { - singular = 'Stunde'; - plural = 'Stunden'; - } else if (type === 'days') { - singular = 'Tag'; - plural = 'Tagen'; - } - } else if (this.props.context.lang === 'ru') { - if (type === 'seconds') { - singular = 'секунду'; - plural = 'секунд'; - special24 = 'секунды'; - } else if (type === 'minutes') { - singular = 'минуту'; - plural = 'минут'; - special24 = 'минуты'; - } else if (type === 'hours') { - singular = 'час'; - plural = 'часов'; - special24 = 'часа'; - } else if (type === 'days') { - singular = 'день'; - plural = 'дней'; - special24 = 'дня'; - } - } else if (type === 'seconds') { - singular = 'second'; - plural = 'seconds'; - } else if (type === 'minutes') { - singular = 'minute'; - plural = 'minutes'; - } else if (type === 'hours') { - singular = 'hour'; - plural = 'hours'; - } else if (type === 'days') { - singular = 'day'; - plural = 'days'; - } - - if (value === 1) { - if (this.props.context.lang === 'de') { - if (type === 'days') { - return `einem ${singular}`; - } - return `einer ${singular}`; - } - - if (this.props.context.lang === 'ru') { - if (type === 'days' || type === 'hours') { - return `один ${singular}`; - } - return `одну ${singular}`; - } - - return `one ${singular}`; - } - - if (this.props.context.lang === 'de') { - return `${value} ${plural}`; - } - - if (this.props.context.lang === 'ru') { - const d = value % 10; - if (d === 1 && value !== 11) { - return `${value} ${singular}`; - } - if (d >= 2 && d <= 4 && (value > 20 || value < 10)) { - return `${value} ${special24}`; - } - - return `${value} ${plural}`; - } - - return `${value} ${plural}`; - } - - formatInterval(timestamp: number, isMomentJs: boolean): string { - if (isMomentJs) { - // init moment - return this.props.context.moment(new Date(timestamp)).fromNow(); - } - let diff = Date.now() - timestamp; - diff = Math.round(diff / 1000); - let text; - if (diff <= 60) { - if (this.props.context.lang === 'de') { - text = `vor ${this.formatIntervalHelper(diff, 'seconds')}`; - } else if (this.props.context.lang === 'ru') { - text = `${this.formatIntervalHelper(diff, 'seconds')} назад`; - } else { - text = `${this.formatIntervalHelper(diff, 'seconds')} ago`; - } - } else if (diff < 3600) { - const m = Math.floor(diff / 60); - const s = diff - m * 60; - text = ''; - if (this.props.context.lang === 'de') { - text = `vor ${this.formatIntervalHelper(m, 'minutes')}`; - } else if (this.props.context.lang === 'ru') { - text = this.formatIntervalHelper(m, 'minutes'); - } else { - text = this.formatIntervalHelper(m, 'minutes'); - } - - if (m < 5) { - // add seconds - if (this.props.context.lang === 'de') { - text += ` und ${this.formatIntervalHelper(s, 'seconds')}`; - } else if (this.props.context.lang === 'ru') { - text += ` и ${this.formatIntervalHelper(s, 'seconds')}`; - } else { - text += ` and ${this.formatIntervalHelper(s, 'seconds')}`; - } - } - - if (this.props.context.lang === 'de') { - // nothing - } else if (this.props.context.lang === 'ru') { - text += ' назад'; - } else { - text += ' ago'; - } - } else if (diff < 3600 * 24) { - const h = Math.floor(diff / 3600); - const m = Math.floor((diff - h * 3600) / 60); - text = ''; - if (this.props.context.lang === 'de') { - text = `vor ${this.formatIntervalHelper(h, 'hours')}`; - } else if (this.props.context.lang === 'ru') { - text = this.formatIntervalHelper(h, 'hours'); - } else { - text = this.formatIntervalHelper(h, 'hours'); - } - - if (h < 10) { - // add seconds - if (this.props.context.lang === 'de') { - text += ` und ${this.formatIntervalHelper(m, 'minutes')}`; - } else if (this.props.context.lang === 'ru') { - text += ` и ${this.formatIntervalHelper(m, 'minutes')}`; - } else { - text += ` and ${this.formatIntervalHelper(m, 'minutes')}`; - } - } - - if (this.props.context.lang === 'de') { - // nothing - } else if (this.props.context.lang === 'ru') { - text += ' назад'; - } else { - text += ' ago'; - } - } else { - const d = Math.floor(diff / (3600 * 24)); - const h = Math.floor((diff - d * (3600 * 24)) / 3600); - text = ''; - if (this.props.context.lang === 'de') { - text = `vor ${this.formatIntervalHelper(d, 'days')}`; - } else if (this.props.context.lang === 'ru') { - text = this.formatIntervalHelper(d, 'days'); - } else { - text = this.formatIntervalHelper(d, 'days'); - } - - if (d < 3) { - // add seconds - if (this.props.context.lang === 'de') { - text += ` und ${this.formatIntervalHelper(h, 'hours')}`; - } else if (this.props.context.lang === 'ru') { - text += ` и ${this.formatIntervalHelper(h, 'hours')}`; - } else { - text += ` and ${this.formatIntervalHelper(h, 'hours')}`; - } - } - - if (this.props.context.lang === 'de') { - // nothing - } else if (this.props.context.lang === 'ru') { - text += ' назад'; - } else { - text += ' ago'; - } - } - return text; - } - - startUpdateInterval(): void { - this.updateInterval = - this.updateInterval || - setInterval(() => { - const timeIntervalEl: HTMLDivElement = (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'); - } - }, 10_000); - } - - // eslint-disable-next-line react/no-unused-class-component-methods - formatDate( - value: string | Date | number, - format?: boolean | string, - interval?: boolean, - isMomentJs?: boolean, - forRx?: boolean, - ): string | React.JSX.Element { - if (typeof format === 'boolean') { - interval = format; - format = 'auto'; - } - - if (format === 'auto') { - format = `${this.props.context.dateFormat || 'DD.MM.YYYY'} hh:mm:ss`; - } - - format = format || this.props.context.dateFormat || 'DD.MM.YYYY'; - - if (!value) { - return ''; - } - let dateObj: Date; - const text = typeof value; - if (text === 'string') { - dateObj = new Date(value); - } - if (text !== 'object') { - if (isVarFinite(value as string)) { - const i = parseInt(value as string, 10); - // if greater than 2000.01.01 00:00:00 - if (i > 946681200000) { - dateObj = new Date(value); - } else { - dateObj = new Date((value as number) * 1000); - } - } else { - dateObj = new Date(value); - } - } - if (interval) { - this.startUpdateInterval(); - if (forRx) { - return ( - - {this.formatInterval(dateObj.getTime(), isMomentJs)} - - ); - } - - return `${this.formatInterval(dateObj.getTime(), isMomentJs)}`; - } - - // Year - if (format.includes('YYYY') || format.includes('JJJJ') || format.includes('ГГГГ')) { - const yearStr = dateObj.getFullYear().toString(); - format = format.replace('YYYY', yearStr); - format = format.replace('JJJJ', yearStr); - format = format.replace('ГГГГ', yearStr); - } else if (format.includes('YY') || format.includes('JJ') || format.includes('ГГ')) { - const yearStr = (dateObj.getFullYear() % 100).toString(); - format = format.replace('YY', yearStr); - format = format.replace('JJ', yearStr); - format = format.replace('ГГ', yearStr); - } - // Month - if (format.includes('MM') || format.includes('ММ')) { - const monthStr = (dateObj.getMonth() + 1).toString().padStart(2, '0'); - format = format.replace('MM', monthStr); - format = format.replace('ММ', monthStr); - } else if (format.includes('M') || format.includes('М')) { - const monthStr = (dateObj.getMonth() + 1).toString(); - format = format.replace('M', monthStr); - format = format.replace('М', monthStr); - } - - // Day - if (format.includes('DD') || format.includes('TT') || format.includes('ДД')) { - const dateStr = dateObj.getDate().toString().padStart(2, '0'); - format = format.replace('DD', dateStr); - format = format.replace('TT', dateStr); - format = format.replace('ДД', dateStr); - } else if (format.includes('D') || format.includes('TT') || format.includes('Д')) { - const dateStr = dateObj.getDate().toString(); - format = format.replace('D', dateStr); - format = format.replace('T', dateStr); - format = format.replace('Д', dateStr); - } - - // hours - if (format.includes('hh') || format.includes('SS') || format.includes('чч')) { - const hoursStr = dateObj.getHours().toString().padStart(2, '0'); - format = format.replace('hh', hoursStr); - format = format.replace('SS', hoursStr); - format = format.replace('чч', hoursStr); - } else if (format.includes('h') || format.includes('S') || format.includes('ч')) { - const hoursStr = dateObj.getHours().toString(); - format = format.replace('h', hoursStr); - format = format.replace('S', hoursStr); - format = format.replace('ч', hoursStr); - } - - // minutes - if (format.includes('mm') || format.includes('мм')) { - const minutesStr = dateObj.getMinutes().toString().padStart(2, '0'); - format = format.replace('mm', minutesStr); - format = format.replace('мм', minutesStr); - } else if (format.includes('m') || format.includes('м')) { - const minutesStr = dateObj.getMinutes().toString(); - format = format.replace('m', minutesStr); - format = format.replace('v', minutesStr); - } - - // milliseconds - if (format.includes('sss') || format.includes('ссс')) { - const msStr = dateObj.getMilliseconds().toString().padStart(3, '0'); - format = format.replace('sss', msStr); - format = format.replace('ссс', msStr); - } - - // seconds - if (format.includes('ss') || format.includes('сс')) { - const secondsStr = dateObj.getSeconds().toString().padStart(2, '0'); - format = format.replace('ss', secondsStr); - format = format.replace('cc', secondsStr); - } else if (format.includes('s') || format.includes('с')) { - const secondsStr = dateObj.getSeconds().toString(); - format = format.replace('s', secondsStr); - format = format.replace('с', secondsStr); - } - - return format; - } - - onToggleRelative(e: React.MouseEvent): void { - e.stopPropagation(); - e.preventDefault(); - - const widget = this.props.context.views[this.props.view].widgets[this.props.id]; - - const width = this.props.isRelative ? widget.style.absoluteWidth || '100px' : '100%'; - - this.props.context.onWidgetsChanged([ - { - wid: this.props.id, - view: this.props.view, - style: { - position: this.props.isRelative ? 'absolute' : 'relative', - width, - absoluteWidth: !this.props.isRelative ? widget.style.width : null, - noPxToPercent: true, // special command - }, - }, - ]); - } - - onToggleWidth(e: React.MouseEvent): void { - e.stopPropagation(); - e.preventDefault(); - const widget = this.props.context.views[this.props.view].widgets[this.props.id]; - - this.props.context.onWidgetsChanged([ - { - wid: this.props.id, - view: this.props.view, - style: { - width: widget.style.width === '100%' ? widget.style.absoluteWidth || '100px' : '100%', - absoluteWidth: widget.style.width !== '100%' ? widget.style.width : null, - noPxToPercent: true, // special command - }, - }, - ]); - } - - onToggleLineBreak(e: React.MouseEvent): void { - e.stopPropagation(); - e.preventDefault(); - - const widget = this.props.context.views[this.props.view].widgets[this.props.id]; - - this.props.context.onWidgetsChanged([ - { - wid: this.props.id, - view: this.props.view, - style: { newLine: !widget.style.newLine }, - }, - ]); - } - - static correctStylePxValue(value: string | number): string | number { - if (typeof value === 'string') { - // eslint-disable-next-line no-restricted-properties - if (isVarFinite(value)) { - return parseFloat(value) || 0; - } - } - - return value; - } - - renderRelativeMoveMenu(): React.JSX.Element | null { - if (this.props.context.runtime || !this.state.editMode) { - return null; - } - - return ( - { - this.props.onIgnoreMouseEvents(false); - this.setState({ showRelativeMoveMenu: false }); - order && this.props.context.onWidgetsChanged(null, this.props.view, { order }); - }} - /> - ); - } - - render(): React.JSX.Element | null { - const widget = this.props.context.views[this.props.view].widgets[this.props.id]; - if (!widget || typeof widget !== 'object') { - console.error(`EMPTY Widget: ${this.props.id}`); - return null; - } - - const style: React.CSSProperties = { - boxSizing: 'border-box', - }; - const selected = - !this.state.multiViewWidget && this.state.editMode && this.props.selectedWidgets?.includes(this.props.id); - const classNames = selected ? ['vis-editmode-selected'] : ['vis-editmode-overlay-not-selected']; - if (selected && this.state.widgetHint === 'hide') { - classNames.push('vis-editmode-selected-background'); - } - - if (this.state.editMode && !(widget.groupid && !this.props.selectedGroup)) { - if (!this.props.isRelative && Object.prototype.hasOwnProperty.call(this.state.style, 'top')) { - style.top = VisBaseWidget.correctStylePxValue(this.state.style.top); - } - if (!this.props.isRelative && Object.prototype.hasOwnProperty.call(this.state.style, 'left')) { - style.left = VisBaseWidget.correctStylePxValue(this.state.style.left); - } - if (Object.prototype.hasOwnProperty.call(this.state.style, 'width')) { - style.width = VisBaseWidget.correctStylePxValue(this.state.style.width); - } - if (Object.prototype.hasOwnProperty.call(this.state.style, 'height')) { - style.height = VisBaseWidget.correctStylePxValue(this.state.style.height); - } - if (!this.props.isRelative && Object.prototype.hasOwnProperty.call(this.state.style, 'right')) { - style.right = VisBaseWidget.correctStylePxValue(this.state.style.right); - } - if (!this.props.isRelative && Object.prototype.hasOwnProperty.call(this.state.style, 'bottom')) { - style.bottom = VisBaseWidget.correctStylePxValue(this.state.style.bottom); - } - - style.position = this.props.isRelative ? 'relative' : 'absolute'; - style.userSelect = 'none'; - - if (selected) { - if ( - this.props.moveAllowed && - this.state.draggable !== false && - !this.props.isRelative && - (!this.props.selectedGroup || this.props.selectedGroup !== this.props.id) - ) { - style.cursor = 'move'; - } else { - style.cursor = 'default'; - } - } else if (widget.data?.locked) { - style.cursor = 'default'; - } else if (this.props.selectedGroup !== this.props.id && !this.state.multiViewWidget) { - style.cursor = 'pointer'; - } - - if (this.props.tpl?.toLowerCase().includes('image')) { - classNames.push('vis-editmode-helper'); - } - } - - const props = { - className: '', - overlayClassNames: classNames, - style, - id: `rx_${this.props.id}`, - refService: this.refService, - widget, - }; - - // If the resizable flag can be controlled dynamically by settings, and it is now not resizable - let doNotTakeWidth = false; - let doNotTakeHeight = false; - if (this.visDynamicResizable && !this.isResizable()) { - if (this.visDynamicResizable.desiredSize === false) { - doNotTakeWidth = true; - doNotTakeHeight = true; - delete style.width; - delete style.height; - } else if (typeof this.visDynamicResizable.desiredSize === 'object') { - if (this.state.style.width) { - style.width = VisBaseWidget.correctStylePxValue(this.visDynamicResizable.desiredSize.width); - } else { - doNotTakeWidth = true; - delete style.width; - } - - if (this.state.style.height) { - style.height = VisBaseWidget.correctStylePxValue(this.visDynamicResizable.desiredSize.height); - } else { - doNotTakeHeight = true; - delete style.height; - } - } - } - - if ( - this.props.isRelative && - isVarFinite(this.props.context.views[this.props.view].settings?.rowGap as string) - ) { - style.marginBottom = parseFloat(this.props.context.views[this.props.view].settings?.rowGap as string) || 0; - } - - const rxWidget = this.renderWidgetBody(props as any); - - if (doNotTakeWidth) { - delete style.width; - } - if (doNotTakeHeight) { - delete style.height; - } - - // in group edit mode show it in the top left corner - if (this.props.id === this.props.selectedGroup) { - style.top = 0; - style.left = 0; - } - - if (!this.props.isRelative) { - style.top = style.top || 0; - style.left = style.left || 0; - } - - // convert string to number+'px' - [ - 'top', - 'left', - 'width', - 'height', - 'right', - 'bottom', - 'fontSize', - 'borderRadius', - 'paddingLeft', - 'paddingTop', - 'paddingRight', - 'paddingBottom', - 'marginTop', - 'marginBottom', - 'marginLeft', - 'marginRight', - 'borderWidth', - ].forEach(attr => { - const anyStyle = style as Record; - if (anyStyle[attr] !== undefined && typeof anyStyle[attr] === 'string') { - // eslint-disable-next-line no-restricted-properties - if (isVarFinite(anyStyle[attr])) { - anyStyle[attr] = parseFloat(anyStyle[attr] as any as string) || 0; - } else if (anyStyle[attr].includes('{')) { - // try to steal style by rxWidget - const value = (this.state.rxStyle as Record)?.[attr]; - if (this.state.rxStyle && value !== undefined) { - if (value && typeof value === 'string' && !value.includes('{')) { - anyStyle[attr] = VisBaseWidget.correctStylePxValue(value); - } - } else { - const styleVal: string | number | undefined | null = ( - this.props.context.allWidgets[this.props.id]?.style as unknown as Record< - string, - string | number - > - )?.[attr]; - if (styleVal !== undefined && styleVal !== null) { - // try to steal style by canWidget - if (!styleVal.toString().includes('{')) { - anyStyle[attr] = VisBaseWidget.correctStylePxValue(styleVal); - } - } - } - } - } - }); - - classNames.push('vis-editmode-overlay'); - - let widgetName = null; - let widgetMoveButtons = null; - const borderWidth = - (typeof style.borderWidth === 'number' ? `${style.borderWidth}px` : style.borderWidth) || '0px'; - if ( - this.state.widgetHint !== 'hide' && - !this.state.hideHelper && - this.state.editMode && - (!widget.groupid || this.props.selectedGroup) && - this.props.selectedGroup !== this.props.id && - this.props.context.showWidgetNames !== false - ) { - // show widget name on widget body - const widgetNameBottom = - !widget.usedInWidget && - (this.refService.current?.offsetTop === 0 || - (this.refService.current?.offsetTop && this.refService.current?.offsetTop < 15)); - - // come again when the ref is filled - if (!this.refService.current) { - setTimeout(() => this.forceUpdate(), 50); - } - - const parts: (string | null)[] = this.state.multiViewWidget ? this.props.id.split('_') : [null, null]; - const multiView: string | null = parts[0]; - const multiId: AnyWidgetId | null = parts[1] as AnyWidgetId | null; - - const resizable = !widget.usedInWidget && this.isResizable(); - - widgetName = ( -
{ - if (this.props.context.setSelectedWidgets) { - this.onMouseDown(e); - } - }} - > - - {this.state.multiViewWidget - ? I18n.t('%s from %s', multiId as string, multiView) - : widget.data?.name || this.props.id} - - {this.state.multiViewWidget || widget.usedInWidget ? null : ( - this.onToggleRelative(e)} - className={Utils.clsx( - 'vis-anchor', - this.props.isRelative ? 'vis-anchor-enabled' : 'vis-anchor-disabled', - )} - /> - )} - {this.state.multiViewWidget || - !this.props.isRelative || - !resizable || - widget.usedInWidget ? null : ( - this.onToggleWidth(e)} - className={Utils.clsx( - 'vis-expand', - widget.style.width === '100%' ? 'vis-expand-enabled' : 'vis-expand-disabled', - )} - /> - )} - {this.state.multiViewWidget || !this.props.isRelative || widget.usedInWidget ? null : ( - this.onToggleLineBreak(e)} - className={Utils.clsx( - 'vis-new-line', - widget.style.newLine ? 'vis-new-line-enabled' : 'vis-new-line-disabled', - )} - /> - )} -
- ); - - if (this.props.isRelative && !this.state.multiViewWidget && !widget.usedInWidget) { - const pos = this.props.relativeWidgetOrder.indexOf(this.props.id); - const showUp = !!pos; - let showDown = pos !== this.props.relativeWidgetOrder.length - 1; - if (showDown && this.props.selectedGroup) { - // Check if the next widget is relative - const widget__ = - this.props.context.views[this.props.view].widgets[this.props.relativeWidgetOrder[pos + 1]]; - if (widget__.style.position === 'absolute') { - showDown = false; - } - } - - if (showUp || showDown) { - widgetMoveButtons = ( -
-
- {this.props.relativeWidgetOrder.indexOf(this.props.id) + 1} -
- {showUp ? ( -
- { - e.stopPropagation(); - e.preventDefault(); - this.pressTimeout = setTimeout( - target => { - this.props.onIgnoreMouseEvents(true); - this.relativeMoveMenu = target; - this.setState({ showRelativeMoveMenu: true }); - this.pressTimeout = undefined; - }, - 300, - e.currentTarget, - ); - }} - onMouseUp={e => this.changeOrder(e, -1)} - /> -
- ) : null} - {showDown ? ( -
- { - e.stopPropagation(); - e.preventDefault(); - this.pressTimeout = setTimeout( - target => { - this.props.onIgnoreMouseEvents(true); - this.relativeMoveMenu = target; - this.setState({ showRelativeMoveMenu: true }); - this.pressTimeout = undefined; - }, - 300, - e.currentTarget, - ); - }} - onMouseUp={e => this.changeOrder(e, 1)} - /> -
- ) : null} -
- ); - } - } - - calculateOverflow(style); - } - - // if multi-view widget and it is not "canJS", dim it in edit mode - if (!this.isCanWidget && this.state.multiViewWidget && this.state.editMode) { - if (style.opacity === undefined || style.opacity === null || (style.opacity as number) > 0.5) { - style.opacity = 0.5; - } - } - - const overlay = - !this.state.hideHelper && // if the helper isn't hidden - !widget.usedInWidget && // not used in another widget, that has own overlay - this.state.editMode && // if edit mode - !widget.data.locked && // if not locked - (!widget.groupid || this.props.selectedGroup) && // if not in group or in the edit group mode - this.props.selectedGroup !== this.props.id ? ( // and it does not the edited group itself -
{ - if (this.props.context.setSelectedWidgets) { - this.onMouseDown(e); - } - }} - /> - ) : null; - - let groupInstructions = null; - - // Show border of the group if in group edit mode - if (this.props.selectedGroup === this.props.id) { - style.borderBottom = '1px dotted #888'; - style.borderRight = '1px dotted #888'; - groupInstructions = ( -
{ - e.stopPropagation(); - this.props.context.setSelectedWidgets([this.props.id]); - }} - > - {I18n.t('group_size_hint')} -
- ); - } - - const signals = this.renderSignals ? this.renderSignals() : null; - - const lastChange = this.renderLastChange ? this.renderLastChange(style) : null; - - return ( -
- {signals} - {lastChange} - {widgetName} - {widgetMoveButtons} - {overlay} - {this.getResizeHandlers(selected, widget, borderWidth)} - {rxWidget} - {groupInstructions} - {this.state.showRelativeMoveMenu && this.renderRelativeMoveMenu()} -
- ); - } -} - -export default VisBaseWidget; +/** + * ioBroker.vis-2 + * https://github.com/ioBroker/ioBroker.vis-2 + * + * Copyright (c) 2022-2025 Denis Haev https://github.com/GermanBluefox, + * Creative Common Attribution-NonCommercial (CC BY-NC) + * + * http://creativecommons.org/licenses/by-nc/4.0/ + * + * Short content: + * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. + * 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 React from 'react'; + +import { + Anchor as AnchorIcon, + Expand as ExpandIcon, + ArrowUpward as UpIcon, + ArrowDownward as DownIcon, + KeyboardReturn, +} from '@mui/icons-material'; + +import { I18n, Utils } from '@iobroker/adapter-react-v5'; + +import { calculateOverflow, deepClone, isVarFinite } from '@/Utils/utils'; +import type { + AnyWidgetId, + ResizeHandler, + GroupData, + WidgetData, + WidgetStyle, + Widget, + RxRenderWidgetProps, + VisRxWidgetStateValues, + VisWidgetCommand, + VisBaseWidgetProps, +} from '@iobroker/types-vis-2'; +import { addClass, removeClass, replaceGroupAttr } from './visUtils'; + +import VisOrderMenu from './visOrderMenu'; + +interface HTMLDivElementResizers extends HTMLDivElement { + _storedOpacity: string; +} + +type Resize = 'left' | 'right' | 'top' | 'bottom' | 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right' | boolean; + +export interface WidgetDataState extends WidgetData { + bindings: string[]; + _originalData?: string; +} + +export interface GroupDataState extends GroupData { + bindings?: string[]; + _originalData?: string; +} + +export interface WidgetStyleState extends WidgetStyle { + bindings?: string[]; + _originalData?: string; +} + +export interface VisBaseWidgetState { + applyBindings?: false | true | { top: string | number; left: string | number }; + data: WidgetDataState | GroupDataState; + draggable?: boolean; + editMode: boolean; + gap?: number; + hideHelper?: boolean; + isHidden?: boolean; + multiViewWidget?: boolean; + resizable?: boolean; + resizeHandles?: ResizeHandler[]; + rxStyle?: WidgetStyleState; + selected?: boolean; + selectedOne?: boolean; + showRelativeMoveMenu?: boolean; + style: WidgetStyleState; + usedInWidget: boolean; + widgetHint?: 'light' | 'dark' | 'hide'; +} + +export interface VisBaseWidgetMovement { + top: number; + left: number; + width: number; + height: number; + order?: AnyWidgetId[]; +} + +interface Handler { + top?: string | number; + left?: string | number; + bottom?: string | number; + height?: string | number; + right?: string | number; + width?: string | number; + cursor: 'nwse-resize' | 'ns-resize' | 'ew-resize' | 'nesw-resize' | 'default'; + background: string; + opacity: number; + borderTop?: string; + borderBottom?: string; + borderLeft?: string; + borderRight?: string; +} + +interface ResizerElement extends HTMLDivElement { + _storedOpacity: string; +} + +/** + * Methods which should be optionally implemented by inherited classes + */ +interface VisBaseWidget { + renderSignals(): React.ReactNode; + 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< + VisBaseWidgetProps, + TState & VisBaseWidgetState +> { + static FORBIDDEN_CHARS = /[^._\-/ :!#$%&()+=@^{}|~]+/g; // from https://github.com/ioBroker/ioBroker.js-controller/blob/master/packages/common/lib/common/tools.js + + /** We do not store the SVG Element in the state because it is cyclic */ + private relativeMoveMenu?: EventTarget & SVGSVGElement; + + /** if currently resizing */ + private resize: Resize = false; + + private readonly uuid = `${Date.now()}.${Math.round(Math.random() * 1_000_000)}`; + + protected refService = React.createRef(); + + protected widDiv: null | CanHTMLDivElement = null; + + readonly onCommandBound: typeof this.onCommand; + + protected onResize: undefined | (() => void); + + private updateInterval?: ReturnType; + + private pressTimeout?: ReturnType; + + private shadowDiv: HTMLDivElement | null; + + private stealCursor?: string; + + private beforeIncludeColor?: string; + + private lastClick?: number; + + protected movement?: VisBaseWidgetMovement; + + /** If resizing is currently locked */ + protected resizeLocked?: boolean; + + protected visDynamicResizable: + | undefined + | null + | { default: boolean; desiredSize: { width: number; height: number } | boolean }; + + protected isCanWidget?: boolean; + + constructor(props: VisBaseWidgetProps) { + super(props); + + const widget = props.context.views[props.view].widgets[props.id]; + const multiViewWidget = props.id.includes('_'); + + const selected = !multiViewWidget && props.editMode && props.selectedWidgets?.includes(props.id); + + const data: WidgetDataState | GroupDataState = deepClone(widget.data || {}) as WidgetDataState | GroupDataState; + const style: WidgetStyle = deepClone(widget.style || {}); + VisBaseWidget.replacePRJ_NAME(data, style, props); + + this.state = { + data, + style, + // eslint-disable-next-line react/no-unused-state + applyBindings: false, + editMode: !multiViewWidget && this.props.editMode, + multiViewWidget, + selected, + selectedOne: selected && this.props.selectedWidgets.length === 1, + resizable: true, + resizeHandles: ['n', 'e', 's', 'w', 'nw', 'ne', 'sw', 'se'], + widgetHint: props.context.widgetHint, + isHidden: VisBaseWidget.isWidgetFilteredOutStatic( + props.viewsActiveFilter, + widget.data, + props.view, + props.editMode, + ), + usedInWidget: widget.usedInWidget, + hideHelper: false, + gap: + style.position === 'relative' + ? isVarFinite(props.context.views[props.view].settings?.rowGap) + ? parseFloat(props.context.views[props.view].settings?.rowGap as string) + : 0 + : 0, + } as TState & VisBaseWidgetState; + + this.onCommandBound = this.onCommand.bind(this); + } + + static replacePRJ_NAME(data: Record, style: Record, props: VisBaseWidgetProps): void { + const context = props.context; + if (data) { + delete data._originalData; + Object.keys(data).forEach(attr => { + if ( + attr && + data[attr] && + typeof data[attr] === 'string' && + (attr.startsWith('src') || attr.endsWith('src') || attr.includes('icon')) && + data[attr].startsWith('_PRJ_NAME') + ) { + if (!data._originalData) { + data._originalData = JSON.stringify(data); + } + // "_PRJ_NAME".length = 9 + data[attr] = + `../${context.adapterName}.${context.instance}/${context.projectName}${data[attr].substring(9)}`; + } + }); + } + if (style) { + delete style._originalData; + if (style['background-image'] && style['background-image'].startsWith('_PRJ_NAME')) { + if (!style._originalData) { + style._originalData = JSON.stringify(style); + } + style['background-image'] = + `../${context.adapterName}.${context.instance}/${context.projectName}${style['background-image'].substring(9)}`; // "_PRJ_NAME".length = 9 + } + } + } + + componentDidMount(): void { + // register service ref by view for resize and move only in edit mode + this.props.askView && + this.props.askView('register', { + id: this.props.id, + uuid: this.uuid, + widDiv: this.widDiv, + refService: this.refService, + onMove: this.onMove, + onResize: this.onResize, + onTempSelect: this.onTempSelect, + onCommand: this.onCommandBound, + }); + } + + componentWillUnmount(): void { + this.updateInterval && clearInterval(this.updateInterval); + this.updateInterval = undefined; + + this.pressTimeout && clearTimeout(this.pressTimeout); + this.pressTimeout = undefined; + + // delete service ref from view only in edit mode + this.props.askView && this.props.askView('unregister', { id: this.props.id, uuid: this.uuid }); + if (this.shadowDiv) { + this.shadowDiv.remove(); + this.shadowDiv = null; + } + } + + // this method may be not in form onCommand = command => {}, as it can be overloaded + // eslint-disable-next-line @typescript-eslint/no-unused-vars + onCommand(command: VisWidgetCommand, _option?: any): any { + if (command === 'includePossible') { + const overlay: HTMLDivElement = this.refService.current?.querySelector('.vis-editmode-overlay'); + if (overlay && this.beforeIncludeColor === undefined) { + this.beforeIncludeColor = overlay.style.backgroundColor; + overlay.style.backgroundColor = 'rgba(0, 255, 0, 0.3)'; + } + return true; + } + if (command === 'includePossibleNOT') { + if (this.beforeIncludeColor !== undefined) { + const overlay: HTMLDivElement = this.refService.current?.querySelector('.vis-editmode-overlay'); + overlay && (overlay.style.backgroundColor = this.beforeIncludeColor); + this.beforeIncludeColor = undefined; + } + return true; + } + + if (command === 'startStealMode') { + this.stealCursor = this.refService.current?.style.cursor || 'nocursor'; + if (this.refService.current) { + this.refService.current.style.cursor = 'crosshair'; + this.refService.current.className = addClass( + this.refService.current.className, + 'vis-editmode-steal-style', + ); + } + // eslint-disable-next-line no-undef + const resizers: NodeListOf = + this.refService.current?.querySelectorAll('.vis-editmode-resizer'); + resizers?.forEach(item => (item.style.display = 'none')); + return true; + } + + if (command === 'cancelStealMode') { + if (this.stealCursor !== 'nocursor' && this.refService.current && this.stealCursor) { + this.refService.current.style.cursor = this.stealCursor; + } + this.stealCursor = undefined; + if (this.refService.current) { + this.refService.current.className = removeClass( + this.refService.current.className, + 'vis-editmode-steal-style', + ); + } + // eslint-disable-next-line no-undef + const resizers: NodeListOf = + this.refService.current?.querySelectorAll('.vis-editmode-resizer'); + resizers?.forEach(item => (item.style.display = '')); + return true; + } + + if (command === 'startMove' || command === 'startResize') { + const overlay = this.refService.current?.querySelector('.vis-editmode-overlay'); + if (overlay) { + if (this.state.selected) { + overlay.className = removeClass(overlay.className, 'vis-editmode-selected'); + } else { + overlay.className = removeClass(overlay.className, 'vis-editmode-overlay-not-selected'); + } + } + + if (command === 'startResize') { + this.resize = true; + } + return true; + } + + if (command === 'stopMove' || command === 'stopResize') { + const overlay: HTMLDivElement = this.refService.current?.querySelector('.vis-editmode-overlay'); + if (overlay) { + if (this.beforeIncludeColor !== undefined) { + overlay.style.backgroundColor = this.beforeIncludeColor; + this.beforeIncludeColor = undefined; + } + + if (this.state.selected) { + overlay.className = addClass(overlay.className, 'vis-editmode-selected'); + } else { + overlay.className = addClass(overlay.className, 'vis-editmode-overlay-not-selected'); + } + } + + // show resizers again + // eslint-disable-next-line no-undef + const resizers: NodeListOf = + this.refService.current?.querySelectorAll('.vis-editmode-resizer'); + resizers?.forEach(item => (item.style.display = 'block')); + + if (command === 'stopResize') { + this.resize = false; + } + return true; + } + + return false; + } + + static getDerivedStateFromProps( + props: VisBaseWidgetProps, + state: VisBaseWidgetState, + ): Partial | null { + const context = props.context; + let newState: Partial | null = null; // No change to state by default + let widget = context.views[props.view].widgets[props.id]; + const gap = + widget.style.position === 'relative' + ? isVarFinite(context.views[props.view].settings?.rowGap) + ? parseFloat(context.views[props.view].settings?.rowGap as string) + : 0 + : 0; + let copied = false; + + if (widget.groupid) { + // this widget belongs to group + const parentWidgetData = context.views[props.view].widgets[widget.groupid].data; + // extract attribute names + const names = Object.keys(parentWidgetData) + .map(attr => (attr.startsWith('attrType_') ? attr.substring(9) : null)) + .filter(attr => attr); + + if (names.length && widget.data) { + for (const [attr, val] of Object.entries(widget.data)) { + if (typeof val === 'string' && names.find(a => val.includes(a))) { + const result = replaceGroupAttr(widget.data[attr], parentWidgetData); + if (result.doesMatch) { + // create a copy as we will substitute the values + if (!copied) { + copied = true; + widget = deepClone(widget); + } + widget.data[attr] = result.newString || ''; + } + } + } + } + } + + // take actual (old) style and data + const styleStr: string = state.style?._originalData ? state.style._originalData : JSON.stringify(state.style); + const dataStr: string = state.data?._originalData ? state.data._originalData : JSON.stringify(state.data); + + const isHidden = VisBaseWidget.isWidgetFilteredOutStatic( + props.viewsActiveFilter, + widget.data, + props.view, + props.editMode, + ); + + // compare with new style and data + if ( + JSON.stringify(widget.style || {}) !== styleStr || + JSON.stringify(widget.data || {}) !== dataStr || + gap !== state.gap || + isHidden !== state.isHidden + ) { + if (!props.runtime) { + const styleObj: WidgetStyle = JSON.parse(styleStr); + Object.keys(styleObj as Record).forEach(attr => { + const oldStyle = (widget.style as Record)[attr]; + const newStyle = (styleObj as Record)[attr]; + if (newStyle !== oldStyle) { + console.log( + `[${Date.now()} / ${props.id}] Rerender because of style.${attr}: ${newStyle} !== ${oldStyle}`, + ); + } + }); + Object.keys(widget.style).forEach((attr: string) => { + const oldStyle = (widget.style as Record)[attr]; + const newStyle = (styleObj as Record)[attr]; + if (newStyle !== oldStyle) { + console.log( + `[${Date.now()} / ${props.id}] Rerender because of style.${attr}: ${newStyle} !== ${oldStyle}`, + ); + } + }); + + const dataObj: GroupData = JSON.parse(dataStr); + Object.keys(dataObj).forEach((attr: string) => { + if (JSON.stringify(dataObj[attr]) !== JSON.stringify(widget.data[attr])) { + console.log( + `[${Date.now()} / ${props.id}] Rerender because of data.${attr}: ${dataObj[attr]} !== ${widget.data[attr]}`, + ); + } + }); + } + + let data: WidgetDataState; + let style: WidgetStyleState; + // restore original data + if (copied) { + data = (widget.data as WidgetDataState) || { bindings: [] }; + // detect for CanWidgets if size was changed + style = (widget.style as WidgetStyleState) || { bindings: [] }; + } else { + data = widget.data + ? (deepClone(widget.data) as WidgetDataState) + : ({ bindings: [] } as WidgetDataState); + // detect for CanWidgets if size was changed + style = widget.style + ? (deepClone(widget.style) as WidgetStyleState) + : ({ bindings: [] } as WidgetStyleState); + } + + // replace all _PRJ_NAME with vis.0/name + VisBaseWidget.replacePRJ_NAME(data, style, props); + + newState = {}; + newState.isHidden = isHidden; + newState.style = style; + newState.data = data; + newState.gap = gap; + newState.applyBindings = { top: widget.style.top as number, left: widget.style.left as number }; + } + + if (props.editMode !== state.editMode) { + newState = newState || {}; + newState.editMode = props.editMode; + newState.applyBindings = true; + } + + if (props.context.widgetHint !== state.widgetHint) { + newState = newState || {}; + newState.widgetHint = props.context.widgetHint; + } + + const selected = + !state.multiViewWidget && + props.editMode && + props.selectedWidgets && + props.selectedWidgets.includes(props.id); + const selectedOne = selected && props.selectedWidgets.length === 1; + + if (selected !== state.selected || selectedOne !== state.selectedOne) { + newState = newState || {}; + newState.selected = selected; + newState.selectedOne = selectedOne; + } + + if (!!widget.usedInWidget !== !!state.usedInWidget) { + newState = newState || {}; + newState.usedInWidget = !!widget.usedInWidget; + } + + return newState; + } + + static removeFromArray(items: Record, IDs: string[], view: string, widget: string): void { + items && + Object.keys(items).forEach(id => { + if (!IDs || IDs.includes(id)) { + for (let i = items[id].length - 1; i >= 0; i--) { + const item = items[id][i]; + if (item.view === view && item.widget === widget) { + items[id].splice(i, 1); + } + } + } + }); + } + + static parseStyle(style: string, isRxStyle?: boolean): Record { + const result: Record = {}; + // style is like "height: 10; width: 20" + (style || '').split(';').forEach(part => { + part = part.trim(); + if (part) { + let [attr, value] = part.split(':'); + attr = attr.trim(); + if (attr && value) { + value = value.trim(); + if (!isRxStyle && (attr === 'top' || attr === 'left' || attr === 'width' || attr === 'height')) { + if (!isRxStyle) { + if (value !== '0' && value.match(/^[-+]?\d+$/)) { + value = `${value}px`; + } + } else { + const f = parseFloat(value); + if (value === f.toString()) { + // @ts-expect-error fix later + value = f; + } + } + } + + if (value) { + if (isRxStyle) { + attr = attr.replace(/(-\w)/, text => text[1].toUpperCase()); + } + + result[attr] = value; + } + } + } + }); + + return result; + } + + onMouseDown(e: React.MouseEvent): void { + e.stopPropagation(); + if (this.stealCursor && !this.state.multiViewWidget) { + e.stopPropagation(); + this.props.mouseDownOnView(e, this.props.id, this.props.isRelative); + return; + } + if (this.props.context.views[this.props.view].widgets[this.props.id].data.locked) { + return; + } + + if (this.lastClick !== undefined && Date.now() - this.lastClick < 250) { + console.log('AAA'); + } + + // detect double click for multi-view widgets + if (this.lastClick) { + if (this.state.multiViewWidget) { + if (Date.now() - this.lastClick < 250) { + // change view + const parts: string[] = this.props.id.split('_'); + const multiView: string = parts[0]; + const multiId: AnyWidgetId = parts[1] as AnyWidgetId; + this.props.context.setSelectedWidgets([multiId], multiView); + } + + this.lastClick = Date.now(); + return; + } + } + + if (e.shiftKey || e.ctrlKey) { + // add or remove + const pos = this.props.selectedWidgets.indexOf(this.props.id); + if (pos === -1) { + const selectedWidgets = [...this.props.selectedWidgets, this.props.id]; + this.props.context.setSelectedWidgets(selectedWidgets); + } else { + const selectedWidgets = [...this.props.selectedWidgets]; + selectedWidgets.splice(pos, 1); + this.props.context.setSelectedWidgets(selectedWidgets); + } + return; + } + + if (!this.props.selectedWidgets.includes(this.props.id)) { + // set select + this.props.context.setSelectedWidgets([this.props.id]); + } else if (this.props.moveAllowed && this.state.draggable !== false) { + if (!this.props.isRelative) { + // User can drag only objects of the same type + this.props.mouseDownOnView( + e, + this.props.id, + this.props.isRelative, + false, + this.lastClick !== undefined && Date.now() - this.lastClick < 300, + ); + } else if (this.lastClick && Date.now() - this.lastClick < 250) { + // if double-click on a group + if ( + this.props.selectedWidgets.length === 1 && + this.props.context.views[this.props.view].widgets[this.props.selectedWidgets[0]].tpl === '_tplGroup' + ) { + this.props.context.setSelectedGroup(this.props.selectedWidgets[0]); + } + } + } + this.lastClick = Date.now(); + } + + createWidgetMovementShadow(): void { + if (this.shadowDiv) { + this.shadowDiv.remove(); + this.shadowDiv = null; + } + + if (!this.movement) { + console.error('Unknown issue, movement is falsy'); + return; + } + + this.shadowDiv = window.document.createElement('div'); + this.shadowDiv.setAttribute('id', `${this.props.id}_shadow`); + this.shadowDiv.className = 'vis-editmode-widget-shadow'; + if (this.refService.current) { + this.shadowDiv.style.width = `${this.refService.current.clientWidth}px`; + this.shadowDiv.style.height = `${this.refService.current.clientHeight}px`; + if (this.refService.current.style.borderRadius) { + this.shadowDiv.style.borderRadius = this.refService.current.style.borderRadius; + } + } + + let parentDiv: HTMLElement; + if (Object.prototype.hasOwnProperty.call(this.props.refParent, 'current')) { + parentDiv = this.props.refParent.current; + } else { + parentDiv = this.props.refParent as any as HTMLElement; + } + + if (this.widDiv) { + parentDiv.insertBefore(this.shadowDiv, this.widDiv); + this.widDiv.style.position = 'absolute'; + this.widDiv.style.left = `${this.movement.left}px`; + this.widDiv.style.top = `${this.movement.top}px`; + } else { + parentDiv.insertBefore(this.shadowDiv, this.refService.current); + if (this.refService.current) { + this.refService.current.style.position = 'absolute'; + } + } + if (this.refService.current) { + this.refService.current.style.left = `${this.movement.left}px`; + this.refService.current.style.top = `${this.movement.top}px`; + } + } + + isResizable(): boolean { + if (this.visDynamicResizable) { + // take data from field "visResizable" + // this value cannot be bound, so we can read it directly from widget.data + return typeof this.state.data.visResizable === 'boolean' + ? this.state.data.visResizable + : this.visDynamicResizable.default; // by default all widgets are resizable + } + + return this.state.resizable; + } + + onMove = ( + x: number | undefined, + y: number | undefined, + save?: boolean, + calculateRelativeWidgetPosition?: + | null + | ((id: AnyWidgetId, left: string, top: string, shadowDiv: HTMLDivElement, order: AnyWidgetId[]) => void), + ): void => { + if (this.state.multiViewWidget || !this.state.editMode) { + return; + } + + const movement = this.movement; + + if (!this.refService.current) { + return; + } + if (this.resize) { + if (this.isResizable() === false) { + return; + } + + if (x === undefined) { + // start of resizing + const rect = (this.widDiv || this.refService.current)?.getBoundingClientRect(); + + if (rect) { + this.movement = { + top: this.refService.current.offsetTop, + left: this.refService.current.offsetLeft, + width: rect.width, + height: rect.height, + }; + } + // eslint-disable-next-line no-undef + const resizers: NodeListOf = + this.refService.current.querySelectorAll('.vis-editmode-resizer'); + resizers.forEach(item => { + item._storedOpacity = item.style.opacity; + item.style.opacity = '0.3'; + }); + } else if (movement && y !== undefined /* && x !== undefined */) { + if (this.resize === 'top') { + this.refService.current.style.top = `${movement.top + y}px`; + this.refService.current.style.height = `${movement.height - y}px`; + if (this.resizeLocked) { + // noinspection JSSuspiciousNameCombination + this.refService.current.style.width = this.refService.current.style.height; + } + + if (this.widDiv) { + this.widDiv.style.top = `${movement.top + y}px`; + this.widDiv.style.height = `${movement.height - y}px`; + if (this.resizeLocked) { + // noinspection JSSuspiciousNameCombination + this.widDiv.style.width = this.widDiv.style.height; + } + } + } else if (this.resize === 'bottom') { + this.widDiv && (this.widDiv.style.height = `${movement.height + y}px`); + this.refService.current.style.height = `${movement.height + y}px`; + + if (this.resizeLocked) { + // noinspection JSSuspiciousNameCombination + this.refService.current.style.width = this.refService.current.style.height; + } + if (this.widDiv && this.resizeLocked) { + // noinspection JSSuspiciousNameCombination + this.widDiv.style.width = this.widDiv.style.height; + } + } else if (this.resize === 'left') { + this.refService.current.style.left = `${movement.left + x}px`; + this.refService.current.style.width = `${movement.width - x}px`; + if (this.resizeLocked) { + // noinspection JSSuspiciousNameCombination + this.refService.current.style.height = this.refService.current.style.width; + } + if (this.widDiv) { + this.widDiv.style.left = `${movement.left + x}px`; + this.widDiv.style.width = `${movement.width - x}px`; + if (this.resizeLocked) { + // noinspection JSSuspiciousNameCombination + this.widDiv.style.height = this.widDiv.style.width; + } + } + } else if (this.resize === 'right') { + this.widDiv && (this.widDiv.style.width = `${movement.width + x}px`); + this.refService.current.style.width = `${movement.width + x}px`; + if (this.resizeLocked) { + // noinspection JSSuspiciousNameCombination + this.refService.current.style.height = this.refService.current.style.width; + } + if (this.widDiv && this.resizeLocked) { + // noinspection JSSuspiciousNameCombination + this.widDiv.style.height = this.widDiv.style.width; + } + } else if (this.resize === 'top-left') { + this.refService.current.style.top = `${movement.top + y}px`; + this.refService.current.style.left = `${movement.left + x}px`; + this.refService.current.style.height = `${movement.height - y}px`; + this.refService.current.style.width = `${movement.width - x}px`; + if (this.resizeLocked) { + // noinspection JSSuspiciousNameCombination + this.refService.current.style.height = this.refService.current.style.width; + } + + if (this.widDiv) { + this.widDiv.style.top = `${movement.top + y}px`; + this.widDiv.style.left = `${movement.left + x}px`; + this.widDiv.style.height = `${movement.height - y}px`; + this.widDiv.style.width = `${movement.width - x}px`; + if (this.resizeLocked) { + // noinspection JSSuspiciousNameCombination + this.widDiv.style.height = this.widDiv.style.width; + } + } + } else if (this.resize === 'top-right') { + this.refService.current.style.top = `${movement.top + y}px`; + this.refService.current.style.height = `${movement.height - y}px`; + this.refService.current.style.width = `${movement.width + x}px`; + if (this.resizeLocked) { + // noinspection JSSuspiciousNameCombination + this.refService.current.style.height = this.refService.current.style.width; + } + if (this.widDiv) { + this.widDiv.style.top = `${movement.top + y}px`; + this.widDiv.style.height = `${movement.height - y}px`; + this.widDiv.style.width = `${movement.width + x}px`; + if (this.resizeLocked) { + // noinspection JSSuspiciousNameCombination + this.widDiv.style.height = this.widDiv.style.width; + } + } + } else if (this.resize === 'bottom-left') { + this.refService.current.style.left = `${movement.left + x}px`; + this.refService.current.style.height = `${movement.height + y}px`; + this.refService.current.style.width = `${movement.width - x}px`; + if (this.resizeLocked) { + // noinspection JSSuspiciousNameCombination + this.refService.current.style.height = this.refService.current.style.width; + } + if (this.widDiv) { + this.widDiv.style.left = `${movement.left + x}px`; + this.widDiv.style.height = `${movement.height + y}px`; + this.widDiv.style.width = `${movement.width - x}px`; + if (this.resizeLocked) { + // noinspection JSSuspiciousNameCombination + this.widDiv.style.height = this.widDiv.style.width; + } + } + } else { + // bottom-right + this.refService.current.style.height = `${movement.height + y}px`; + this.refService.current.style.width = `${movement.width + x}px`; + if (this.resizeLocked) { + // noinspection JSSuspiciousNameCombination + this.refService.current.style.height = this.refService.current.style.width; + } + if (this.widDiv) { + this.widDiv.style.height = `${movement.height + y}px`; + this.widDiv.style.width = `${movement.width + x}px`; + if (this.resizeLocked) { + // noinspection JSSuspiciousNameCombination + this.widDiv.style.height = this.widDiv.style.width; + } + } + } + } + + // end of resize + if (save) { + // eslint-disable-next-line no-undef + const resizers: NodeListOf = + this.refService.current?.querySelectorAll('.vis-editmode-resizer'); + resizers?.forEach(item => { + if (item._storedOpacity !== undefined) { + item.style.opacity = item._storedOpacity; + delete item._storedOpacity; + } + }); + this.resize = false; + this.props.context.onWidgetsChanged([ + { + wid: this.props.id, + view: this.props.view, + style: { + top: this.refService.current.style.top, + left: this.refService.current.style.left, + width: this.refService.current.style.width, + height: this.refService.current.style.height, + }, + }, + ]); + + this.movement = undefined; + } + } else if (x === undefined) { + if (this.state.draggable === false) { + return; + } + + // initiate movement + this.movement = { + top: this.refService.current.offsetTop, + left: this.refService.current.offsetLeft, + order: [...this.props.relativeWidgetOrder], + width: 0, + height: 0, + }; + + // hide resizers + // eslint-disable-next-line no-undef + const resizers: NodeListOf = + this.refService.current.querySelectorAll('.vis-editmode-resizer'); + resizers.forEach(item => (item.style.display = 'none')); + + if (this.props.isRelative) { + // create shadow widget + this.createWidgetMovementShadow(); + } + } else if (this.movement && y !== undefined && x !== undefined) { + // move widget + const left = `${this.movement.left + x}px`; + const top = `${this.movement.top + y}px`; + + if (this.refService.current) { + this.refService.current.style.left = left; + this.refService.current.style.top = top; + } + + if (this.widDiv) { + this.widDiv.style.left = left; + this.widDiv.style.top = top; + + // @ts-expect-error check later + if (this.widDiv._customHandlers && this.widDiv._customHandlers.onMove) { + // @ts-expect-error check later + this.widDiv._customHandlers.onMove(this.widDiv, this.props.id); + } + } + + if (this.props.isRelative && calculateRelativeWidgetPosition) { + // calculate widget position + calculateRelativeWidgetPosition(this.props.id, left, top, this.shadowDiv, this.movement.order); + } + + // End of movement + if (save) { + // show resizers + // eslint-disable-next-line no-undef + const resizers: NodeListOf = + this.refService.current.querySelectorAll('.vis-editmode-resizer'); + resizers.forEach(item => (item.style.display = 'block')); + + if (this.props.isRelative) { + let parentDiv: HTMLElement; + if (Object.prototype.hasOwnProperty.call(this.props.refParent, 'current')) { + parentDiv = this.props.refParent.current; + } else { + parentDiv = this.props.refParent as any as HTMLElement; + } + // create shadow widget + if (this.widDiv) { + this.widDiv.style.position = 'relative'; + this.widDiv.style.top = '0'; + this.widDiv.style.left = '0'; + parentDiv.insertBefore(this.widDiv, this.shadowDiv); + this.refService.current.style.top = `${this.widDiv.offsetTop}px`; + this.refService.current.style.left = `${this.widDiv.offsetLeft}px`; + } else { + parentDiv.insertBefore(this.refService.current, this.shadowDiv); + this.refService.current.style.position = 'relative'; + this.refService.current.style.top = '0'; + this.refService.current.style.left = '0'; + } + this.shadowDiv.remove(); + this.shadowDiv = null; + + this.props.context.onWidgetsChanged( + [ + { + wid: this.props.id, + view: this.props.view, + style: { + left: null, + top: null, + }, + }, + ], + this.props.view, + { order: this.movement.order }, + ); + } else { + this.props.context.onWidgetsChanged([ + { + wid: this.props.id, + view: this.props.view, + style: { + left: this.movement.left + x, + top: this.movement.top + y, + }, + }, + ]); + } + + this.movement = undefined; + } + } + }; + + onTempSelect = (selected?: boolean): void => { + const ref: HTMLElement = this.refService.current?.querySelector('.vis-editmode-overlay'); + if (!ref) { + return; + } + if (selected === null || selected === undefined) { + // restore original state + if (this.props.selectedWidgets.includes(this.props.id)) { + if (!ref.className.includes('vis-editmode-selected')) { + ref.className = addClass('vis-editmode-selected', ref.className); + } + } else { + ref.style.backgroundColor = ''; + ref.className = removeClass(ref.className, 'vis-editmode-selected'); + } + } else if (selected) { + if (!ref.className.includes('vis-editmode-selected')) { + ref.className = addClass('vis-editmode-selected', ref.className); + } + } else { + ref.className = removeClass(ref.className, 'vis-editmode-selected'); + } + }; + + onResizeStart(e: React.MouseEvent, type: Resize): void { + e.stopPropagation(); + this.resize = type; + this.props.mouseDownOnView(e, this.props.id, this.props.isRelative, true); + } + + getResizeHandlers(selected: boolean, widget: Widget, borderWidth: string): React.JSX.Element[] | null { + if (!this.state.editMode || !selected || this.props.selectedWidgets?.length !== 1) { + return null; + } + + const thickness = 0.4; + const shift = 0.3; + const square = 0.4; + + const squareShift = `calc(${shift - square}em - ${borderWidth})`; + const squareWidthHeight = `${square}em`; + const shiftEm = `${shift}em`; + const thicknessEm = `${thickness}em`; + const offsetEm = `calc(${shift - thickness}em - ${borderWidth})`; + + const widgetWidth100 = widget.style.width === '100%'; + const widgetHeight100 = widget.style.height === '100%'; + + const color = '#014488'; // it is so, to be able to change color in web storm + const border = `0.1em dashed ${color}`; + const borderDisabled = '0.1em dashed #888'; + + const resizable = this.isResizable(); + + let resizeHandlers: ResizeHandler[] = resizable && this.state.resizeHandles ? this.state.resizeHandles : []; + + if (resizable && this.props.selectedGroup && this.props.selectedGroup === this.props.id) { + resizeHandlers = ['s', 'e', 'se']; + } + + const RESIZERS_OPACITY = 0.9; + const RESIZERS_OPACITY_DISABLED = 0.5; + + const isRelative = widget.usedInWidget || this.props.isRelative; + + const controllable = { + top: !isRelative && resizeHandlers.includes('n'), + bottom: !widget.usedInWidget && !widgetHeight100 && resizeHandlers.includes('s'), + left: !isRelative && resizeHandlers.includes('w'), + right: !widget.usedInWidget && !widgetWidth100 && resizeHandlers.includes('e'), + 'top-left': !widgetHeight100 && !widgetWidth100 && !isRelative && resizeHandlers.includes('nw'), + 'top-right': !widgetHeight100 && !widgetWidth100 && !isRelative && resizeHandlers.includes('ne'), + 'bottom-left': !widgetHeight100 && !widgetWidth100 && !isRelative && resizeHandlers.includes('sw'), + 'bottom-right': + !widgetHeight100 && !widgetWidth100 && !widget.usedInWidget && resizeHandlers.includes('se'), + }; + + const handlers: Record = { + top: { + top: offsetEm, + height: thicknessEm, + left: controllable['top-left'] ? shiftEm : 0, + right: controllable['top-right'] ? shiftEm : 0, + cursor: 'ns-resize', + background: 'transparent', + opacity: controllable.top ? RESIZERS_OPACITY : RESIZERS_OPACITY_DISABLED, + borderTop: controllable.top ? border : borderDisabled, + }, + bottom: { + bottom: offsetEm, + height: thicknessEm, + left: controllable['bottom-left'] ? shiftEm : 0, + right: controllable['bottom-right'] ? shiftEm : 0, + cursor: 'ns-resize', + background: 'transparent', + opacity: controllable.bottom ? RESIZERS_OPACITY : RESIZERS_OPACITY_DISABLED, + borderBottom: controllable.bottom ? border : borderDisabled, + }, + left: { + top: controllable['top-left'] ? shiftEm : 0, + bottom: controllable['bottom-left'] ? shiftEm : 0, + left: offsetEm, + width: thicknessEm, + cursor: 'ew-resize', + background: 'transparent', + opacity: controllable.left ? RESIZERS_OPACITY : RESIZERS_OPACITY_DISABLED, + borderLeft: controllable.left ? border : borderDisabled, + }, + right: { + top: controllable['top-right'] ? shiftEm : 0, + bottom: controllable['bottom-right'] ? shiftEm : 0, + right: offsetEm, + width: thicknessEm, + cursor: 'ew-resize', + background: 'transparent', + opacity: controllable.right ? RESIZERS_OPACITY : RESIZERS_OPACITY_DISABLED, + borderRight: controllable.right ? border : borderDisabled, + }, + 'top-left': { + top: squareShift, + height: squareWidthHeight, + left: squareShift, + width: squareWidthHeight, + cursor: 'nwse-resize', + background: color, + opacity: RESIZERS_OPACITY, + }, + 'top-right': { + top: squareShift, + height: squareWidthHeight, + right: squareShift, + width: squareWidthHeight, + cursor: 'nesw-resize', + background: color, + opacity: RESIZERS_OPACITY, + }, + 'bottom-left': { + bottom: squareShift, + height: squareWidthHeight, + left: squareShift, + width: squareWidthHeight, + cursor: 'nesw-resize', + background: color, + opacity: RESIZERS_OPACITY, + }, + 'bottom-right': { + bottom: squareShift, + height: squareWidthHeight, + right: squareShift, + width: squareWidthHeight, + cursor: 'nwse-resize', + background: color, + opacity: RESIZERS_OPACITY, + }, + }; + + const style = { + position: 'absolute', + zIndex: 1001, + }; + + return Object.keys(handlers).map((key: string) => { + const handler = handlers[key]; + if (!(controllable as Record)[key]) { + if (key.includes('-')) { + return null; + } + handler.cursor = 'default'; + } + + return ( +
this.onResizeStart(e, key as Resize) : undefined + } + /> + ); + }); + } + + // eslint-disable-next-line react/no-unused-class-component-methods + isUserMemberOfGroup(user: string, userGroups: string[]): boolean { + if (!userGroups) { + return true; + } + if (!Array.isArray(userGroups)) { + userGroups = [userGroups]; + } + + return !!userGroups.find(groupId => { + const group = this.props.context.userGroups[`system.group.${groupId}`]; + return group?.common?.members?.length && group.common.members.includes(`system.user.${user}`); + }); + } + + static isWidgetFilteredOutStatic( + viewsActiveFilter: { [view: string]: string[] } | null, + widgetData: WidgetData | GroupData, + view: string, + editMode: boolean, + ): boolean { + if (!viewsActiveFilter) { + console.warn(`viewsActiveFilter is not defined in ${view}, data: ${JSON.stringify(widgetData)}`); + return false; + } + + const vf = viewsActiveFilter[view]; + if (!editMode && widgetData?.filterkey && vf?.length) { + if (vf[0] === '$') { + return true; + } + + let filterKeys: string[]; + + if (typeof widgetData.filterkey === 'string') { + // deprecated, but for back compatibility + filterKeys = (widgetData.filterkey as any as string) + .split(',') + .map(f => f.trim()) + .filter(f => f); + } else { + filterKeys = widgetData.filterkey; + } + + // we cannot use here find as filterkey could be observable (can) and is not normal array + for (let f = 0; f < filterKeys.length; f++) { + if (vf.includes(filterKeys[f])) { + return false; // widget is not hidden + } + } + return true; + } + + return false; + } + + // eslint-disable-next-line react/no-unused-class-component-methods + isWidgetFilteredOut(widgetData: WidgetData | GroupData): boolean { + return VisBaseWidget.isWidgetFilteredOutStatic( + this.props.viewsActiveFilter, + widgetData, + this.props.view, + this.state.editMode, + ); + } + + // eslint-disable-next-line react/no-unused-class-component-methods + static isWidgetHidden(widgetData: WidgetData | GroupData, states: VisRxWidgetStateValues, id: string): boolean { + const oid = widgetData['visibility-oid']; + const condition = widgetData['visibility-cond']; + + if (oid) { + if (!Object.keys(states).includes(`${oid}.val`)) { + // if we don't have state information yet - hide to prevent shortly showing widget during render + return true; + } + + let val = states[`${oid}.val`]; + + if (val === undefined || val === null) { + return condition === 'not exist'; + } + + let value = widgetData['visibility-val']; + + 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); + } + + // Take care: return true if the widget is hidden! + 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 val === 'null'; + case 'not exist': + return val !== 'null'; + default: + console.log(`[${id}] Unknown visibility condition: ${condition}`); + return false; + } + } else { + return condition && condition === 'not exist'; + } + } + + /** + * Render the widget body + */ + // eslint-disable-next-line class-methods-use-this,no-unused-vars, @typescript-eslint/no-unused-vars + renderWidgetBody(_props: RxRenderWidgetProps): React.JSX.Element | React.JSX.Element[] | null { + // Default render method. Normally it should be overwritten + return ( +
+
+ {I18n.t('Unknown widget type "%s"', this.props.tpl)} +
+
{JSON.stringify(this.state.data, null, 2)}
+
+ ); + } + + // eslint-disable-next-line react/no-unused-class-component-methods + changeOrder(e: React.MouseEvent, dir: number): void { + e.stopPropagation(); + e.preventDefault(); + if (this.state.multiViewWidget || !this.state.editMode) { + return; + } + + const order = [...this.props.relativeWidgetOrder]; + + const pos = order.indexOf(this.props.id); + if (dir > 0) { + if (pos === order.length - 1) { + return; + } + const nextId = order[pos + 1]; + order[pos + 1] = this.props.id; + order[pos] = nextId; + } else if (!pos) { + return; + } else { + const nextId = order[pos - 1]; + order[pos - 1] = this.props.id; + order[pos] = nextId; + } + + this.props.context.onWidgetsChanged(null, this.props.view, { order }); + } + + static formatValue(value: string | number, decimals: number | string, _format?: string): string { + if (typeof decimals !== 'number') { + decimals = 2; + _format = decimals as any as string; + } + const format = _format === undefined ? '.,' : _format; + if (typeof value !== 'number') { + value = parseFloat(value); + } + return Number.isNaN(value) + ? '' + : value + .toFixed(decimals || 0) + .replace(format[0], format[1]) + .replace(/\B(?=(\d{3})+(?!\d))/g, format[0]); + } + + formatIntervalHelper(value: number, type: 'seconds' | 'minutes' | 'hours' | 'days'): string { + let singular; + let plural; + let special24; + if (this.props.context.lang === 'de') { + if (type === 'seconds') { + singular = 'Sekunde'; + plural = 'Sekunden'; + } else if (type === 'minutes') { + singular = 'Minute'; + plural = 'Minuten'; + } else if (type === 'hours') { + singular = 'Stunde'; + plural = 'Stunden'; + } else if (type === 'days') { + singular = 'Tag'; + plural = 'Tagen'; + } + } else if (this.props.context.lang === 'ru') { + if (type === 'seconds') { + singular = 'секунду'; + plural = 'секунд'; + special24 = 'секунды'; + } else if (type === 'minutes') { + singular = 'минуту'; + plural = 'минут'; + special24 = 'минуты'; + } else if (type === 'hours') { + singular = 'час'; + plural = 'часов'; + special24 = 'часа'; + } else if (type === 'days') { + singular = 'день'; + plural = 'дней'; + special24 = 'дня'; + } + } else if (type === 'seconds') { + singular = 'second'; + plural = 'seconds'; + } else if (type === 'minutes') { + singular = 'minute'; + plural = 'minutes'; + } else if (type === 'hours') { + singular = 'hour'; + plural = 'hours'; + } else if (type === 'days') { + singular = 'day'; + plural = 'days'; + } + + if (value === 1) { + if (this.props.context.lang === 'de') { + if (type === 'days') { + return `einem ${singular}`; + } + return `einer ${singular}`; + } + + if (this.props.context.lang === 'ru') { + if (type === 'days' || type === 'hours') { + return `один ${singular}`; + } + return `одну ${singular}`; + } + + return `one ${singular}`; + } + + if (this.props.context.lang === 'de') { + return `${value} ${plural}`; + } + + if (this.props.context.lang === 'ru') { + const d = value % 10; + if (d === 1 && value !== 11) { + return `${value} ${singular}`; + } + if (d >= 2 && d <= 4 && (value > 20 || value < 10)) { + return `${value} ${special24}`; + } + + return `${value} ${plural}`; + } + + return `${value} ${plural}`; + } + + formatInterval(timestamp: number, isMomentJs: boolean): string { + if (isMomentJs) { + // init moment + return this.props.context.moment(new Date(timestamp)).fromNow(); + } + let diff = Date.now() - timestamp; + diff = Math.round(diff / 1000); + let text; + if (diff <= 60) { + if (this.props.context.lang === 'de') { + text = `vor ${this.formatIntervalHelper(diff, 'seconds')}`; + } else if (this.props.context.lang === 'ru') { + text = `${this.formatIntervalHelper(diff, 'seconds')} назад`; + } else { + text = `${this.formatIntervalHelper(diff, 'seconds')} ago`; + } + } else if (diff < 3600) { + const m = Math.floor(diff / 60); + const s = diff - m * 60; + text = ''; + if (this.props.context.lang === 'de') { + text = `vor ${this.formatIntervalHelper(m, 'minutes')}`; + } else if (this.props.context.lang === 'ru') { + text = this.formatIntervalHelper(m, 'minutes'); + } else { + text = this.formatIntervalHelper(m, 'minutes'); + } + + if (m < 5) { + // add seconds + if (this.props.context.lang === 'de') { + text += ` und ${this.formatIntervalHelper(s, 'seconds')}`; + } else if (this.props.context.lang === 'ru') { + text += ` и ${this.formatIntervalHelper(s, 'seconds')}`; + } else { + text += ` and ${this.formatIntervalHelper(s, 'seconds')}`; + } + } + + if (this.props.context.lang === 'de') { + // nothing + } else if (this.props.context.lang === 'ru') { + text += ' назад'; + } else { + text += ' ago'; + } + } else if (diff < 3600 * 24) { + const h = Math.floor(diff / 3600); + const m = Math.floor((diff - h * 3600) / 60); + text = ''; + if (this.props.context.lang === 'de') { + text = `vor ${this.formatIntervalHelper(h, 'hours')}`; + } else if (this.props.context.lang === 'ru') { + text = this.formatIntervalHelper(h, 'hours'); + } else { + text = this.formatIntervalHelper(h, 'hours'); + } + + if (h < 10) { + // add seconds + if (this.props.context.lang === 'de') { + text += ` und ${this.formatIntervalHelper(m, 'minutes')}`; + } else if (this.props.context.lang === 'ru') { + text += ` и ${this.formatIntervalHelper(m, 'minutes')}`; + } else { + text += ` and ${this.formatIntervalHelper(m, 'minutes')}`; + } + } + + if (this.props.context.lang === 'de') { + // nothing + } else if (this.props.context.lang === 'ru') { + text += ' назад'; + } else { + text += ' ago'; + } + } else { + const d = Math.floor(diff / (3600 * 24)); + const h = Math.floor((diff - d * (3600 * 24)) / 3600); + text = ''; + if (this.props.context.lang === 'de') { + text = `vor ${this.formatIntervalHelper(d, 'days')}`; + } else if (this.props.context.lang === 'ru') { + text = this.formatIntervalHelper(d, 'days'); + } else { + text = this.formatIntervalHelper(d, 'days'); + } + + if (d < 3) { + // add seconds + if (this.props.context.lang === 'de') { + text += ` und ${this.formatIntervalHelper(h, 'hours')}`; + } else if (this.props.context.lang === 'ru') { + text += ` и ${this.formatIntervalHelper(h, 'hours')}`; + } else { + text += ` and ${this.formatIntervalHelper(h, 'hours')}`; + } + } + + if (this.props.context.lang === 'de') { + // nothing + } else if (this.props.context.lang === 'ru') { + text += ' назад'; + } else { + text += ' ago'; + } + } + return text; + } + + startUpdateInterval(): void { + this.updateInterval = + this.updateInterval || + setInterval(() => { + const timeIntervalEl: HTMLDivElement = (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'); + } + }, 10_000); + } + + // eslint-disable-next-line react/no-unused-class-component-methods + formatDate( + value: string | Date | number, + format?: boolean | string, + interval?: boolean, + isMomentJs?: boolean, + forRx?: boolean, + ): string | React.JSX.Element { + if (typeof format === 'boolean') { + interval = format; + format = 'auto'; + } + + if (format === 'auto') { + format = `${this.props.context.dateFormat || 'DD.MM.YYYY'} hh:mm:ss`; + } + + format = format || this.props.context.dateFormat || 'DD.MM.YYYY'; + + if (!value) { + return ''; + } + let dateObj: Date; + const text = typeof value; + if (text === 'string') { + dateObj = new Date(value); + } + if (text !== 'object') { + if (isVarFinite(value as string)) { + const i = parseInt(value as string, 10); + // if greater than 2000.01.01 00:00:00 + if (i > 946681200000) { + dateObj = new Date(value); + } else { + dateObj = new Date((value as number) * 1000); + } + } else { + dateObj = new Date(value); + } + } + if (interval) { + this.startUpdateInterval(); + if (forRx) { + return ( + + {this.formatInterval(dateObj.getTime(), isMomentJs)} + + ); + } + + return `${this.formatInterval(dateObj.getTime(), isMomentJs)}`; + } + + // Year + if (format.includes('YYYY') || format.includes('JJJJ') || format.includes('ГГГГ')) { + const yearStr = dateObj.getFullYear().toString(); + format = format.replace('YYYY', yearStr); + format = format.replace('JJJJ', yearStr); + format = format.replace('ГГГГ', yearStr); + } else if (format.includes('YY') || format.includes('JJ') || format.includes('ГГ')) { + const yearStr = (dateObj.getFullYear() % 100).toString(); + format = format.replace('YY', yearStr); + format = format.replace('JJ', yearStr); + format = format.replace('ГГ', yearStr); + } + // Month + if (format.includes('MM') || format.includes('ММ')) { + const monthStr = (dateObj.getMonth() + 1).toString().padStart(2, '0'); + format = format.replace('MM', monthStr); + format = format.replace('ММ', monthStr); + } else if (format.includes('M') || format.includes('М')) { + const monthStr = (dateObj.getMonth() + 1).toString(); + format = format.replace('M', monthStr); + format = format.replace('М', monthStr); + } + + // Day + if (format.includes('DD') || format.includes('TT') || format.includes('ДД')) { + const dateStr = dateObj.getDate().toString().padStart(2, '0'); + format = format.replace('DD', dateStr); + format = format.replace('TT', dateStr); + format = format.replace('ДД', dateStr); + } else if (format.includes('D') || format.includes('TT') || format.includes('Д')) { + const dateStr = dateObj.getDate().toString(); + format = format.replace('D', dateStr); + format = format.replace('T', dateStr); + format = format.replace('Д', dateStr); + } + + // hours + if (format.includes('hh') || format.includes('SS') || format.includes('чч')) { + const hoursStr = dateObj.getHours().toString().padStart(2, '0'); + format = format.replace('hh', hoursStr); + format = format.replace('SS', hoursStr); + format = format.replace('чч', hoursStr); + } else if (format.includes('h') || format.includes('S') || format.includes('ч')) { + const hoursStr = dateObj.getHours().toString(); + format = format.replace('h', hoursStr); + format = format.replace('S', hoursStr); + format = format.replace('ч', hoursStr); + } + + // minutes + if (format.includes('mm') || format.includes('мм')) { + const minutesStr = dateObj.getMinutes().toString().padStart(2, '0'); + format = format.replace('mm', minutesStr); + format = format.replace('мм', minutesStr); + } else if (format.includes('m') || format.includes('м')) { + const minutesStr = dateObj.getMinutes().toString(); + format = format.replace('m', minutesStr); + format = format.replace('v', minutesStr); + } + + // milliseconds + if (format.includes('sss') || format.includes('ссс')) { + const msStr = dateObj.getMilliseconds().toString().padStart(3, '0'); + format = format.replace('sss', msStr); + format = format.replace('ссс', msStr); + } + + // seconds + if (format.includes('ss') || format.includes('сс')) { + const secondsStr = dateObj.getSeconds().toString().padStart(2, '0'); + format = format.replace('ss', secondsStr); + format = format.replace('cc', secondsStr); + } else if (format.includes('s') || format.includes('с')) { + const secondsStr = dateObj.getSeconds().toString(); + format = format.replace('s', secondsStr); + format = format.replace('с', secondsStr); + } + + return format; + } + + onToggleRelative(e: React.MouseEvent): void { + e.stopPropagation(); + e.preventDefault(); + + const widget = this.props.context.views[this.props.view].widgets[this.props.id]; + + const width = this.props.isRelative ? widget.style.absoluteWidth || '100px' : '100%'; + + this.props.context.onWidgetsChanged([ + { + wid: this.props.id, + view: this.props.view, + style: { + position: this.props.isRelative ? 'absolute' : 'relative', + width, + absoluteWidth: !this.props.isRelative ? widget.style.width : null, + noPxToPercent: true, // special command + }, + }, + ]); + } + + onToggleWidth(e: React.MouseEvent): void { + e.stopPropagation(); + e.preventDefault(); + const widget = this.props.context.views[this.props.view].widgets[this.props.id]; + + this.props.context.onWidgetsChanged([ + { + wid: this.props.id, + view: this.props.view, + style: { + width: widget.style.width === '100%' ? widget.style.absoluteWidth || '100px' : '100%', + absoluteWidth: widget.style.width !== '100%' ? widget.style.width : null, + noPxToPercent: true, // special command + }, + }, + ]); + } + + onToggleLineBreak(e: React.MouseEvent): void { + e.stopPropagation(); + e.preventDefault(); + + const widget = this.props.context.views[this.props.view].widgets[this.props.id]; + + this.props.context.onWidgetsChanged([ + { + wid: this.props.id, + view: this.props.view, + style: { newLine: !widget.style.newLine }, + }, + ]); + } + + static correctStylePxValue(value: string | number): string | number { + if (typeof value === 'string') { + // eslint-disable-next-line no-restricted-properties + if (isVarFinite(value)) { + return parseFloat(value) || 0; + } + } + + return value; + } + + renderRelativeMoveMenu(): React.JSX.Element | null { + if (this.props.context.runtime || !this.state.editMode) { + return null; + } + + return ( + { + this.props.onIgnoreMouseEvents(false); + this.setState({ showRelativeMoveMenu: false }); + order && this.props.context.onWidgetsChanged(null, this.props.view, { order }); + }} + /> + ); + } + + render(): React.JSX.Element | null { + const widget = this.props.context.views[this.props.view].widgets[this.props.id]; + if (!widget || typeof widget !== 'object') { + console.error(`EMPTY Widget: ${this.props.id}`); + return null; + } + + const style: React.CSSProperties = { + boxSizing: 'border-box', + }; + const selected = + !this.state.multiViewWidget && this.state.editMode && this.props.selectedWidgets?.includes(this.props.id); + const classNames = selected ? ['vis-editmode-selected'] : ['vis-editmode-overlay-not-selected']; + if (selected && this.state.widgetHint === 'hide') { + classNames.push('vis-editmode-selected-background'); + } + + if (this.state.editMode && !(widget.groupid && !this.props.selectedGroup)) { + if (!this.props.isRelative && Object.prototype.hasOwnProperty.call(this.state.style, 'top')) { + style.top = VisBaseWidget.correctStylePxValue(this.state.style.top); + } + if (!this.props.isRelative && Object.prototype.hasOwnProperty.call(this.state.style, 'left')) { + style.left = VisBaseWidget.correctStylePxValue(this.state.style.left); + } + if (Object.prototype.hasOwnProperty.call(this.state.style, 'width')) { + style.width = VisBaseWidget.correctStylePxValue(this.state.style.width); + } + if (Object.prototype.hasOwnProperty.call(this.state.style, 'height')) { + style.height = VisBaseWidget.correctStylePxValue(this.state.style.height); + } + if (!this.props.isRelative && Object.prototype.hasOwnProperty.call(this.state.style, 'right')) { + style.right = VisBaseWidget.correctStylePxValue(this.state.style.right); + } + if (!this.props.isRelative && Object.prototype.hasOwnProperty.call(this.state.style, 'bottom')) { + style.bottom = VisBaseWidget.correctStylePxValue(this.state.style.bottom); + } + + style.position = this.props.isRelative ? 'relative' : 'absolute'; + style.userSelect = 'none'; + + if (selected) { + if ( + this.props.moveAllowed && + this.state.draggable !== false && + !this.props.isRelative && + (!this.props.selectedGroup || this.props.selectedGroup !== this.props.id) + ) { + style.cursor = 'move'; + } else { + style.cursor = 'default'; + } + } else if (widget.data?.locked) { + style.cursor = 'default'; + } else if (this.props.selectedGroup !== this.props.id && !this.state.multiViewWidget) { + style.cursor = 'pointer'; + } + + if (this.props.tpl?.toLowerCase().includes('image')) { + classNames.push('vis-editmode-helper'); + } + } + + const props = { + className: '', + overlayClassNames: classNames, + style, + id: `rx_${this.props.id}`, + refService: this.refService, + widget, + }; + + // If the resizable flag can be controlled dynamically by settings, and it is now not resizable + let doNotTakeWidth = false; + let doNotTakeHeight = false; + if (this.visDynamicResizable && !this.isResizable()) { + if (this.visDynamicResizable.desiredSize === false) { + doNotTakeWidth = true; + doNotTakeHeight = true; + delete style.width; + delete style.height; + } else if (typeof this.visDynamicResizable.desiredSize === 'object') { + if (this.state.style.width) { + style.width = VisBaseWidget.correctStylePxValue(this.visDynamicResizable.desiredSize.width); + } else { + doNotTakeWidth = true; + delete style.width; + } + + if (this.state.style.height) { + style.height = VisBaseWidget.correctStylePxValue(this.visDynamicResizable.desiredSize.height); + } else { + doNotTakeHeight = true; + delete style.height; + } + } + } + + if ( + this.props.isRelative && + isVarFinite(this.props.context.views[this.props.view].settings?.rowGap as string) + ) { + style.marginBottom = parseFloat(this.props.context.views[this.props.view].settings?.rowGap as string) || 0; + } + + const rxWidget = this.renderWidgetBody(props as any); + + if (doNotTakeWidth) { + delete style.width; + } + if (doNotTakeHeight) { + delete style.height; + } + + // in group edit mode show it in the top left corner + if (this.props.id === this.props.selectedGroup) { + style.top = 0; + style.left = 0; + } + + if (!this.props.isRelative) { + style.top = style.top || 0; + style.left = style.left || 0; + } + + // convert string to number+'px' + [ + 'top', + 'left', + 'width', + 'height', + 'right', + 'bottom', + 'fontSize', + 'borderRadius', + 'paddingLeft', + 'paddingTop', + 'paddingRight', + 'paddingBottom', + 'marginTop', + 'marginBottom', + 'marginLeft', + 'marginRight', + 'borderWidth', + ].forEach(attr => { + const anyStyle = style as Record; + if (anyStyle[attr] !== undefined && typeof anyStyle[attr] === 'string') { + // eslint-disable-next-line no-restricted-properties + if (isVarFinite(anyStyle[attr])) { + anyStyle[attr] = parseFloat(anyStyle[attr] as any as string) || 0; + } else if (anyStyle[attr].includes('{')) { + // try to steal style by rxWidget + const value = (this.state.rxStyle as Record)?.[attr]; + if (this.state.rxStyle && value !== undefined) { + if (value && typeof value === 'string' && !value.includes('{')) { + anyStyle[attr] = VisBaseWidget.correctStylePxValue(value); + } + } else { + const styleVal: string | number | undefined | null = ( + this.props.context.allWidgets[this.props.id]?.style as unknown as Record< + string, + string | number + > + )?.[attr]; + if (styleVal !== undefined && styleVal !== null) { + // try to steal style by canWidget + if (!styleVal.toString().includes('{')) { + anyStyle[attr] = VisBaseWidget.correctStylePxValue(styleVal); + } + } + } + } + } + }); + + classNames.push('vis-editmode-overlay'); + + let widgetName = null; + let widgetMoveButtons = null; + const borderWidth = + (typeof style.borderWidth === 'number' ? `${style.borderWidth}px` : style.borderWidth) || '0px'; + if ( + this.state.widgetHint !== 'hide' && + !this.state.hideHelper && + this.state.editMode && + (!widget.groupid || this.props.selectedGroup) && + this.props.selectedGroup !== this.props.id && + this.props.context.showWidgetNames !== false + ) { + // show widget name on widget body + const widgetNameBottom = + !widget.usedInWidget && + (this.refService.current?.offsetTop === 0 || + (this.refService.current?.offsetTop && this.refService.current?.offsetTop < 15)); + + // come again when the ref is filled + if (!this.refService.current) { + setTimeout(() => this.forceUpdate(), 50); + } + + const parts: (string | null)[] = this.state.multiViewWidget ? this.props.id.split('_') : [null, null]; + const multiView: string | null = parts[0]; + const multiId: AnyWidgetId | null = parts[1] as AnyWidgetId | null; + + const resizable = !widget.usedInWidget && this.isResizable(); + + widgetName = ( +
{ + if (this.props.context.setSelectedWidgets) { + this.onMouseDown(e); + } + }} + > + + {this.state.multiViewWidget + ? I18n.t('%s from %s', multiId as string, multiView) + : widget.data?.name || this.props.id} + + {this.state.multiViewWidget || widget.usedInWidget ? null : ( + this.onToggleRelative(e)} + className={Utils.clsx( + 'vis-anchor', + this.props.isRelative ? 'vis-anchor-enabled' : 'vis-anchor-disabled', + )} + /> + )} + {this.state.multiViewWidget || + !this.props.isRelative || + !resizable || + widget.usedInWidget ? null : ( + this.onToggleWidth(e)} + className={Utils.clsx( + 'vis-expand', + widget.style.width === '100%' ? 'vis-expand-enabled' : 'vis-expand-disabled', + )} + /> + )} + {this.state.multiViewWidget || !this.props.isRelative || widget.usedInWidget ? null : ( + this.onToggleLineBreak(e)} + className={Utils.clsx( + 'vis-new-line', + widget.style.newLine ? 'vis-new-line-enabled' : 'vis-new-line-disabled', + )} + /> + )} +
+ ); + + if (this.props.isRelative && !this.state.multiViewWidget && !widget.usedInWidget) { + const pos = this.props.relativeWidgetOrder.indexOf(this.props.id); + const showUp = !!pos; + let showDown = pos !== this.props.relativeWidgetOrder.length - 1; + if (showDown && this.props.selectedGroup) { + // Check if the next widget is relative + const widget__ = + this.props.context.views[this.props.view].widgets[this.props.relativeWidgetOrder[pos + 1]]; + if (widget__.style.position === 'absolute') { + showDown = false; + } + } + + if (showUp || showDown) { + widgetMoveButtons = ( +
+
+ {this.props.relativeWidgetOrder.indexOf(this.props.id) + 1} +
+ {showUp ? ( +
+ { + e.stopPropagation(); + e.preventDefault(); + this.pressTimeout = setTimeout( + target => { + this.props.onIgnoreMouseEvents(true); + this.relativeMoveMenu = target; + this.setState({ showRelativeMoveMenu: true }); + this.pressTimeout = undefined; + }, + 300, + e.currentTarget, + ); + }} + onMouseUp={e => this.changeOrder(e, -1)} + /> +
+ ) : null} + {showDown ? ( +
+ { + e.stopPropagation(); + e.preventDefault(); + this.pressTimeout = setTimeout( + target => { + this.props.onIgnoreMouseEvents(true); + this.relativeMoveMenu = target; + this.setState({ showRelativeMoveMenu: true }); + this.pressTimeout = undefined; + }, + 300, + e.currentTarget, + ); + }} + onMouseUp={e => this.changeOrder(e, 1)} + /> +
+ ) : null} +
+ ); + } + } + + calculateOverflow(style); + } + + // if multi-view widget and it is not "canJS", dim it in edit mode + if (!this.isCanWidget && this.state.multiViewWidget && this.state.editMode) { + if (style.opacity === undefined || style.opacity === null || (style.opacity as number) > 0.5) { + style.opacity = 0.5; + } + } + + const overlay = + !this.state.hideHelper && // if the helper isn't hidden + !widget.usedInWidget && // not used in another widget, that has own overlay + this.state.editMode && // if edit mode + !widget.data.locked && // if not locked + (!widget.groupid || this.props.selectedGroup) && // if not in group or in the edit group mode + this.props.selectedGroup !== this.props.id ? ( // and it does not the edited group itself +
{ + if (this.props.context.setSelectedWidgets) { + this.onMouseDown(e); + } + }} + /> + ) : null; + + let groupInstructions = null; + + // Show border of the group if in group edit mode + if (this.props.selectedGroup === this.props.id) { + style.borderBottom = '1px dotted #888'; + style.borderRight = '1px dotted #888'; + groupInstructions = ( +
{ + e.stopPropagation(); + this.props.context.setSelectedWidgets([this.props.id]); + }} + > + {I18n.t('group_size_hint')} +
+ ); + } + + const signals = this.renderSignals ? this.renderSignals() : null; + + const lastChange = this.renderLastChange ? this.renderLastChange(style) : null; + + return ( +
+ {signals} + {lastChange} + {widgetName} + {widgetMoveButtons} + {overlay} + {this.getResizeHandlers(selected, widget, borderWidth)} + {rxWidget} + {groupInstructions} + {this.state.showRelativeMoveMenu && this.renderRelativeMoveMenu()} +
+ ); + } +} + +export default VisBaseWidget; diff --git a/packages/iobroker.vis-2/src-vis/src/Vis/visCanWidget.tsx b/packages/iobroker.vis-2/src-vis/src/Vis/visCanWidget.tsx index 8f021891..a10df5fc 100644 --- a/packages/iobroker.vis-2/src-vis/src/Vis/visCanWidget.tsx +++ b/packages/iobroker.vis-2/src-vis/src/Vis/visCanWidget.tsx @@ -1,1636 +1,1636 @@ -/** - * ioBroker.vis-2 - * https://github.com/ioBroker/ioBroker.vis-2 - * - * 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/ - * - * Short content: - * Licensees may copy, distribute, display and perform the work and make derivative works based on it only if they give the author or licensor the credits in the manner specified by these. - * 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 React from 'react'; - -import type { - CanObservable, - CanWidgetStore, - ResizeHandler, - RxRenderWidgetProps, - SingleWidget, - SingleWidgetId, - StateID, - VisBaseWidgetProps, - VisLinkContextBinding, - VisLinkContextItem, - VisLinkContextSignalItem, - VisStateUsage, - VisWidgetCommand, - WidgetData, - WidgetStyle, -} from '@iobroker/types-vis-2'; - -import { calculateOverflow, isVarFinite, deepClone } from '@/Utils/utils'; -import { replaceGroupAttr, addClass, getUsedObjectIDsInWidget } from './visUtils'; -import VisBaseWidget, { type VisBaseWidgetState } from './visBaseWidget'; - -interface WidgetDataWithParsedFilter extends WidgetData { - wid: SingleWidgetId; - filterKeyParsed?: string[]; -} - -type VisWidgetCanCommand = VisWidgetCommand | 'updatePosition' | 'updateContainers' | 'changeFilter' | 'collectFilters'; - -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?.dataset) { - const resizableOptionsStr = el.dataset.visResizable; - if (resizableOptionsStr) { - let resizableOptions: { disabled?: boolean; handles?: string } | null = null; - try { - resizableOptions = JSON.parse(resizableOptionsStr); - } catch { - console.error( - `Cannot parse resizable options by ${el.getAttribute('id')}: ${JSON.stringify(resizableOptions)}`, - ); - } - if (resizableOptions) { - if (resizableOptions.disabled !== undefined) { - result.resizable = !resizableOptions.disabled; - } - if (resizableOptions.handles !== undefined) { - result.resizeHandles = resizableOptions.handles.split(',').map(h => h.trim()) as ResizeHandler[]; - } - } - if (widgetStyle && !result.resizable && (!widgetStyle.width || !widgetStyle.height)) { - result.virtualHeight = el.clientHeight; - result.virtualWidth = el.clientWidth; - } - } - - const draggableOptionsStr = el.dataset.visDraggable; - if (draggableOptionsStr) { - let draggableOptions: { disabled?: boolean } | null = null; - try { - draggableOptions = JSON.parse(draggableOptionsStr); - } catch { - console.error( - `Cannot parse draggable options by ${el.getAttribute('id')}: ${JSON.stringify(draggableOptions)}`, - ); - } - if (draggableOptions) { - if (draggableOptions.disabled !== undefined) { - result.draggable = !draggableOptions.disabled; - } - } - } - - result.hideHelper = el.dataset.visHideHelper === 'true'; - } - return result; -} - -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; - - private lastState: string | undefined; - - private oldEditMode: boolean | undefined; - - 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.setupSubscriptions(); - - 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 - props.context.buildLegacyStructures(); - // } - } - - setupSubscriptions(): void { - this.bindings = {}; - - const linkContext: VisStateUsage = { - IDs: [], - bindings: this.bindings, - visibility: this.props.context.linkContext.visibility, - lastChanges: this.props.context.linkContext.lastChanges, - signals: this.props.context.linkContext.signals, - }; - - getUsedObjectIDsInWidget(this.props.context.views, this.props.view, this.props.id, linkContext); - - this.IDs = linkContext.IDs; - - // merge bindings - Object.keys(this.bindings).forEach(id => { - this.props.context.linkContext.bindings[id] = this.props.context.linkContext.bindings[id] || []; - this.bindings[id].forEach(item => this.props.context.linkContext.bindings[id].push(item)); - }); - - // free mem - Object.keys(linkContext).forEach(attr => ((linkContext as Record)[attr] = null)); - } - - componentDidMount(): void { - super.componentDidMount(); - - this.props.context.linkContext.subscribe(this.IDs); - - if (!this.widDiv) { - // link could be a ref or direct a div (e.g., by groups) - // console.log('Widget mounted'); - this.renderWidget(undefined, undefined, undefined, undefined, () => { - const newState = { mounted: true }; - - if (this.props.context.allWidgets[this.props.id]) { - // try to read resize handlers - analyzeDraggableResizable( - this.widDiv, - newState, - this.props.context.allWidgets[this.props.id].style, - ); - } - - this.setState(newState); - }); - } - } - - componentWillUnmount(): void { - super.componentWillUnmount(); - - if (this.props.context.linkContext) { - if (this.props.context.linkContext && this.props.context.linkContext.unregisterChangeHandler) { - this.props.context.linkContext.unregisterChangeHandler(this.props.id, this.changeHandler); - } - } - - this.bindings = {}; - - if (this.props.context.linkContext) { - if (this.props.context.linkContext.unsubscribe) { - this.props.context.linkContext.unsubscribe(this.IDs); - } - - // remove all bindings from prop.linkContexts - VisBaseWidget.removeFromArray( - this.props.context.linkContext.visibility, - this.IDs, - this.props.view, - this.props.id, - ); - VisBaseWidget.removeFromArray( - this.props.context.linkContext.lastChanges, - this.IDs, - this.props.view, - this.props.id, - ); - VisBaseWidget.removeFromArray( - this.props.context.linkContext.signals, - this.IDs, - this.props.view, - this.props.id, - ); - VisBaseWidget.removeFromArray( - this.props.context.linkContext.bindings, - this.IDs, - this.props.view, - this.props.id, - ); - } - - this.destroy(); - } - - static applyStyle(el: HTMLDivElement, style: WidgetStyle | string, isSelected?: boolean, editMode?: boolean): void { - if (typeof style === 'string') { - // style is a string - // "height: 10; width: 20" - const styleFromString: Record = VisBaseWidget.parseStyle(style); - Object.keys(styleFromString).forEach(attr => { - if (!attr.startsWith('_')) { - 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`; - } - } - - if (attr.includes('-')) { - console.log(`${attr} to ${value}`); - attr = attr.replace(/-([a-z])/g, g => g[1].toUpperCase()); - } - if (value) { - (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; - // style is an object - // { - // height: 10, - // } - 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`; - } - } - // a-b => aB - if (attr.includes('-')) { - attr = attr.replace(/-([a-z])/g, g => g[1].toUpperCase()); - } - if (value) { - (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] = ''; - } - } - }); - - if (editMode) { - if (isSelected) { - // z-index - 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: HTMLDivElement = el.parentNode.querySelector(`#rx_${el.id}`); - if (overlay) { - overlay.style.zIndex = (zIndex + 1).toString(); - } - el.style.userSelect = 'none'; - el.style.pointerEvents = 'none'; - } - - // absolute widgets must have top and left - if (el.style.position === 'absolute') { - if (!el.style.top) { - el.style.top = '0px'; - } - if (!el.style.left) { - el.style.left = '0px'; - } - } - } - } - - // this method may be not in form onCommand = command => {} - onCommand(command: VisWidgetCanCommand, options?: { filter?: string[] }): any { - const result: boolean = super.onCommand(command, options); - - if (result === false) { - if (command === 'updatePosition') { - // move by canJS widgets the name and overlapping div - if (this.refService.current && this.widDiv) { - this.refService.current.style.width = `${this.widDiv.offsetWidth}px`; - this.refService.current.style.height = `${this.widDiv.offsetHeight}px`; - // Move helper to actual widget - this.refService.current.style.left = `${this.widDiv.offsetLeft}px`; - this.refService.current.style.top = `${this.widDiv.offsetTop}px`; - } - } else if (command === 'updateContainers') { - // try to find 'vis-view-container' in it - const containers = this.widDiv.querySelectorAll('.vis-view-container'); - if (containers.length) { - const legacyViewContainers: string[] = []; - for (let v = 0; v < containers.length; v++) { - 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'); - } - } - - legacyViewContainers.sort(); - - if (JSON.stringify(legacyViewContainers) !== JSON.stringify(this.state.legacyViewContainers)) { - this.setState({ legacyViewContainers }); - } - } - } else if (command === 'changeFilter') { - if (!this.widDiv) { - return null; - } - - // if filter was disabled - if (!options?.filter?.length) { - // just show if it was hidden - if (this.filterDisplay !== undefined) { - this.widDiv.style.display = this.filterDisplay; - if (this.widDiv._customHandlers?.onShow) { - this.widDiv._customHandlers.onShow(this.widDiv, this.props.id); - } - } - } else if (options.filter[0] === '$') { - // hide all - if (this.props.context.allWidgets[this.props.id]?.data?.filterkey) { - if (this.widDiv.style.display !== 'none') { - this.filterDisplay = this.widDiv.style.display; - this.widDiv.style.display = 'none'; - if (this.widDiv._customHandlers?.onHide) { - this.widDiv._customHandlers.onHide(this.widDiv, this.props.id); - } - } - } - } else { - 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 - let found = false; - for (let w = 0; w < wFilters.length; w++) { - if (options.filter.includes(wFilters[w])) { - found = true; - break; - } - } - - // this widget was not found in desired filters - if (!found) { - // if it was not hidden - if (this.widDiv.style.display !== 'none') { - // remember display mode - this.filterDisplay = this.widDiv.style.display; - // hide it - this.widDiv.style.display = 'none'; - if (this.widDiv._customHandlers?.onHide) { - this.widDiv._customHandlers?.onHide(this.widDiv, this.props.id); - } - } - } else if (this.filterDisplay !== undefined && this.widDiv.style.display === 'none') { - // if it was hidden => restore it - this.widDiv.style.display = this.filterDisplay; - if (this.widDiv._customHandlers?.onShow) { - this.widDiv._customHandlers?.onShow(this.widDiv, this.props.id); - } - } - } - } - } else if (command === 'collectFilters') { - return this.props.context.allWidgets[this.props.id]?.data?.filterkey; - } - } - - return result; - } - - // 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]) { - delete this.props.context.allWidgets[this.props.id]; - } - - // 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'); - - if (typeof destroy === 'function') { - destroy(this.props.id, $wid); - $wid.data('destroy', null); - } - - // remove from DOM - this.widDiv.remove(); - this.widDiv = null; - } - } - - changeHandler = ( - type: 'style' | 'signal' | 'visibility' | 'lastChange' | 'binding', - item?: VisLinkContextBinding | VisLinkContextItem | VisLinkContextSignalItem, - stateId?: string, - ): void => { - // console.log(`[${this.props.id}] update widget because of "${type}" "${stateId}": ${JSON.stringify(state)}`); - if (this.widDiv) { - if (type === 'style') { - if (this.props.context.allWidgets[this.props.id]) { - // apply style from this.props.context.allWidgets.style - 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 as VisLinkContextSignalItem); - } else if (type === 'visibility') { - this.updateVisibility(); - } else if (type === 'lastChange') { - this.updateLastChange(); - } else if (type === 'binding') { - this.applyBinding(stateId); - } - } - }; - - updateSignal(item: VisLinkContextSignalItem): void { - if (this.widDiv) { - const signalDiv: HTMLDivElement = this.widDiv.querySelector(`.vis-signal[data-index="${item.index}"]`); - if (signalDiv) { - if (this.isSignalVisible(item.index)) { - signalDiv.style.display = ''; - } else { - signalDiv.style.display = 'none'; - } - } - } - } - - updateLastChange(): void { - if (this.widDiv) { - const widgetData = this.props.context.allWidgets[this.props.id]?.data; - if (widgetData) { - 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'}`, - ) as string, - widgetData['lc-format'], - widgetData['lc-is-interval'], - widgetData['lc-is-moment'], - ); - } else { - console.warn(`[${this.props.id}] Last change not found!`); - } - } - } - } - - updateVisibility(): void { - 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) - ) { - this.widDiv._storedDisplay = this.widDiv.style.display; - this.widDiv.style.display = 'none'; - - if (this.widDiv && this.widDiv._customHandlers && this.widDiv._customHandlers.onHide) { - this.widDiv._customHandlers.onHide(this.widDiv, this.props.id); - } - } else { - this.widDiv.style.display = this.widDiv._storedDisplay || ''; - this.widDiv._storedDisplay = ''; - - if (this.widDiv && this.widDiv._customHandlers && this.widDiv._customHandlers.onShow) { - this.widDiv._customHandlers.onShow(this.widDiv, this.props.id); - } - } - } - } - } - - // 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): boolean { - widgetData = widgetData || this.props.context.allWidgets[this.props.id].data; - - if (!widgetData) { - return false; - } - - if (this.state.editMode) { - return !widgetData[`signals-hide-edit-${index}`]; - } - - const oid = widgetData[`signals-oid-${index}`]; - - if (oid) { - const val = this.props.context.canStates.attr(`${oid}.val`); - - const condition = widgetData[`signals-cond-${index}`]; - let value = widgetData[`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; - } - let valNotNull: string | number | boolean = val as string | number | boolean; - - 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') { - valNotNull = JSON.stringify(valNotNull); - } - - switch (condition) { - case '==': - value = value.toString(); - 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(); - 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 valNotNull >= value; - case '<=': - return valNotNull <= value; - case '>': - return valNotNull > value; - case '<': - return valNotNull < value; - case 'consist': - value = value.toString(); - valNotNull = valNotNull.toString(); - return valNotNull.toString().includes(value); - case 'not consist': - value = value.toString(); - valNotNull = valNotNull.toString(); - return !valNotNull.toString().includes(value); - case 'exist': - return value !== 'null'; - case 'not exist': - return value === 'null'; - default: - console.log(`[${this.props.id}] Unknown signals condition: ${condition}`); - return false; - } - } else { - return false; - } - } - - addSignalIcon(widgetData?: WidgetData, index?: number): void { - widgetData = widgetData || this.props.context.allWidgets[this.props.id]?.data; - if (!widgetData) { - return; - } - - //