diff --git a/packages/admin/src-admin/src/components/ObjectBrowser.tsx b/packages/admin/src-admin/src/components/ObjectBrowser.tsx index 051a680b1..a777021db 100644 --- a/packages/admin/src-admin/src/components/ObjectBrowser.tsx +++ b/packages/admin/src-admin/src/components/ObjectBrowser.tsx @@ -202,6 +202,8 @@ export interface TreeItemData { title?: string; /** if the item has "write" button (value=true, ack=false) */ button?: boolean; + /** If the item has read and write and is boolean */ + switch?: boolean; /** if the item has custom settings in `common.custom` */ hasCustoms?: boolean; /** If this item is visible */ @@ -1622,6 +1624,8 @@ function buildTree( typeof obj.common.role === 'string' && obj.common.role.startsWith('button') && obj.common?.write !== false, + switch: obj.type === 'state' && obj.common?.type === 'boolean' && + obj.common?.write !== false && obj.common?.read !== false, }, }; @@ -5343,8 +5347,20 @@ export class ObjectBrowserClass extends Component]; + if (!this.state.filter.expertMode) { + if (item.data.button) { + val = []; + } else if (item.data.switch) { + val = []; + } } return window.alert(`Cannot write state "${id}": ${e}`)); + } else if (!this.state.filter.expertMode && item.data.switch) { + // in non-expert mode control switch directly + this.props.socket + .setState(id, !this.states[id].val) + .catch(e => window.alert(`Cannot write state "${id}": ${e}`)); } else { this.edit = { val: this.states[id] ? this.states[id].val : '', diff --git a/packages/jsonConfig/src/JsonConfig.tsx b/packages/jsonConfig/src/JsonConfig.tsx index 38edbe1b0..b6364401c 100644 --- a/packages/jsonConfig/src/JsonConfig.tsx +++ b/packages/jsonConfig/src/JsonConfig.tsx @@ -248,8 +248,7 @@ class JsonConfig extends Router { schema, data: obj.native, common: obj.common, - // @ts-expect-error really no string? - hash: MD5(JSON.stringify(schema)), + hash: MD5(JSON.stringify(schema)).toString(), }); } else { window.alert(`Instance system.adapter.${this.props.adapterName}.${this.props.instance} not found!`); @@ -345,8 +344,7 @@ class JsonConfig extends Router { } else if (this.fileSubscribed.includes(fileName)) { try { const schema = await this.getConfigFile(this.fileSubscribed[0]); - // @ts-expect-error really no string? - this.setState({ schema, hash: MD5(JSON.stringify(schema)) }); + this.setState({ schema, hash: MD5(JSON.stringify(schema)).toString() }); } catch { // ignore errors } @@ -596,7 +594,7 @@ class JsonConfig extends Router { for (const attr of Object.keys(this.state.data)) { const item = this.findAttr(attr); - if ((!item || !item.doNotSave) && !attr.startsWith('_')) { + if ((!item || !item.doNotSave || item.type === 'state') && !attr.startsWith('_')) { ConfigGeneric.setValue(obj.native, attr, this.state.data[attr]); } else { ConfigGeneric.setValue(obj.native, attr, null); diff --git a/packages/jsonConfig/src/JsonConfigComponent/ConfigGeneric.tsx b/packages/jsonConfig/src/JsonConfigComponent/ConfigGeneric.tsx index ba7270868..753ffec76 100644 --- a/packages/jsonConfig/src/JsonConfigComponent/ConfigGeneric.tsx +++ b/packages/jsonConfig/src/JsonConfigComponent/ConfigGeneric.tsx @@ -171,7 +171,7 @@ export default class ConfigGeneric { - obj: ioBroker.Object | null = null; - controlTimeout: ReturnType | null = null; + delayedUpdate: { timer: ReturnType | null, value: string | boolean | number | null } = { timer: null, value: null }; + getObjectID() { return `${this.props.schema.system ? 'system.adapter.' : ''}${this.props.adapterName}.${this.props.instance}.${this.props.schema.oid}`; } async componentDidMount() { super.componentDidMount(); - this.obj = await this.props.socket.getObject(this.getObjectID()); - const controlType = this.props.schema.control || await this.detectType(); + const obj: ioBroker.StateObject = await this.props.socket.getObject(this.getObjectID()) as ioBroker.StateObject; + const controlType = this.props.schema.control || await this.detectType(obj); const state = await this.props.socket.getState(this.getObjectID()); - this.setState({ stateValue: state ? state.val : null, controlType }, async () => { + this.setState({ stateValue: state ? state.val : null, controlType, obj }, async () => { await this.props.socket.subscribeState(this.getObjectID(), this.onStateChanged); }); } @@ -46,6 +47,11 @@ class ConfigState extends ConfigGeneric { componentWillUnmount() { super.componentWillUnmount(); this.props.socket.unsubscribeState(this.getObjectID(), this.onStateChanged); + if (this.delayedUpdate.timer) { + clearTimeout(this.delayedUpdate.timer); + this.delayedUpdate.timer = null; + } + if (this.controlTimeout) { clearTimeout(this.controlTimeout); this.controlTimeout = null; @@ -60,29 +66,49 @@ class ConfigState extends ConfigGeneric { this.state.controlType === 'switch' ) { val = !!val; + if (this.state.stateValue !== val) { + this.setState({ stateValue: val }); + } } else if (val !== null && (this.state.controlType === 'slider' || this.state.controlType === 'number')) { val = parseFloat(val as unknown as string); + console.log(`${Date.now()} Received new value: ${val}`); + if (val !== this.state.stateValue) { + if (this.delayedUpdate.timer) { + clearTimeout(this.delayedUpdate.timer); + this.delayedUpdate.timer = null; + } + this.delayedUpdate.value = val; + this.delayedUpdate.timer = setTimeout(() => { + this.setState({ stateValue: this.delayedUpdate.value }); + }, 500); + } else if (this.delayedUpdate.timer) { + clearTimeout(this.delayedUpdate.timer); + this.delayedUpdate.timer = null; + } + } else if (this.state.stateValue.toString() !== val.toString()) { + this.setState({ stateValue: val }); } - - this.setState({ stateValue: val }); }; - async detectType() { + async detectType(obj: ioBroker.StateObject) { + obj = obj || {} as ioBroker.StateObject; + obj.common = obj.common || {} as ioBroker.StateCommon; + // read object - if (this.obj.common.type === 'boolean') { - if (this.obj.common.read === false) { + if (obj.common.type === 'boolean') { + if (obj.common.read === false) { return 'button'; } - if (this.obj.common.write) { + if (obj.common.write) { return 'switch'; } return 'text'; } - if (this.obj.common.type === 'number') { - if (this.obj.common.write) { - if (this.obj.common.max !== undefined) { + if (obj.common.type === 'number') { + if (obj.common.write) { + if (obj.common.max !== undefined) { return 'slider'; } return 'input'; @@ -90,7 +116,7 @@ class ConfigState extends ConfigGeneric { return 'text'; } - if (this.obj.common.write) { + if (obj.common.write) { return 'input'; } @@ -98,7 +124,11 @@ class ConfigState extends ConfigGeneric { } renderItem(/* error, disabled, defaultValue */) { - let content: React.JSX.Element | null = null; + if (!this.state.obj) { + return null; + } + + let content: React.JSX.Element; if (this.state.controlType === 'button') { let icon: React.JSX.Element | null = null; @@ -113,6 +143,7 @@ class ConfigState extends ConfigGeneric { ; } else { content =