diff --git a/ui/AGENTS.md b/ui/AGENTS.md index 7f80a3a554..cd57f4d34b 100644 --- a/ui/AGENTS.md +++ b/ui/AGENTS.md @@ -55,7 +55,7 @@ Pick the approach by how much of the base component you need to keep. The base + ### Registering an input type in the input pipeline -To make an input type usable through the input pipeline, register it in `or-vaadin-input.ts` (`TEMPLATES` map + a `getXTemplate`, and `nativeValue` if it is a boolean that exposes `checked` instead of `value`) and update `value-input-provider.ts` (boolean/checked types use the `checked` attribute, not `value`). Where the Vaadin component represents the value differently than the attribute does, such as the date time picker taking a local ISO string instead of a timestamp, convert the value and any min/max there and convert back through `valueConverter`. +To make an input type usable through the input pipeline, register it in `or-vaadin-input.ts` (`TEMPLATES` map + a `getXTemplate`) and update `value-input-provider.ts`. Where the Vaadin component represents the value differently than the attribute does, convert at both ends: `nativeValue` and `checkValidity` read and validate what the component holds, while each consumer serializes the value and any min/max into the attributes and converts back through `valueConverter`. Booleans expose `checked` instead of `value`, JSON types carry their value as formatted text, and the date time picker takes a local ISO string instead of a timestamp. ## Storybook diff --git a/ui/app/shared/locales/en/or.json b/ui/app/shared/locales/en/or.json index c869cc36ad..38ac87939f 100644 --- a/ui/app/shared/locales/en/or.json +++ b/ui/app/shared/locales/en/or.json @@ -765,6 +765,7 @@ "addItem": "Add item", "array": "Array", "agentId": "Agent ID", + "selectAgent": "Select agent", "agentNotFound": "Agent not found", "agentTypeMismatch": "Agent type mismatch", "loadFailedRoles": "Failed to load roles", diff --git a/ui/app/shared/locales/nl/or.json b/ui/app/shared/locales/nl/or.json index 904e309ffd..d381a777f7 100644 --- a/ui/app/shared/locales/nl/or.json +++ b/ui/app/shared/locales/nl/or.json @@ -756,6 +756,7 @@ "addItem": "Voeg item toe", "array": "Array", "agentId": "Agent ID", + "selectAgent": "Selecteer agent", "agentNotFound": "Agent niet gevonden", "agentTypeMismatch": "Agent type mismatch", "loadFailedRoles": "Rollen ophalen mislukt", diff --git a/ui/component/or-attribute-input/src/renderers/agent-link.ts b/ui/component/or-attribute-input/src/renderers/agent-link.ts index d89840f97e..2c20320d3a 100644 --- a/ui/component/or-attribute-input/src/renderers/agent-link.ts +++ b/ui/component/or-attribute-input/src/renderers/agent-link.ts @@ -31,9 +31,10 @@ import { uiTypeIs, formatIs, } from "@openremote/or-json-forms"; -import { InputType, type OrInputChangedEvent } from "@openremote/or-mwc-components/or-mwc-input"; +import type { SelectItem } from "@openremote/or-vaadin-components/or-vaadin-select"; +import type { OrVaadinComboBox } from "@openremote/or-vaadin-components/or-vaadin-combo-box"; import { html } from "lit"; -import "@openremote/or-mwc-components/or-mwc-input"; +import "@openremote/or-vaadin-components/or-vaadin-combo-box"; import { i18next } from "@openremote/or-translate"; import { until } from "lit/directives/until.js"; @@ -118,19 +119,24 @@ const agentIdRenderer = (state: JsonFormsStateContext, props: ControlProps) => { }; const loadedTemplatePromise = loadAgents().then((agents) => { - const options: [string, string][] = agents.map((agent) => [agent.id!, agent.name + " (" + agent.id + ")"]); + const options: SelectItem[] = agents.map((agent) => ({ + value: agent.id!, + label: agent.name + " (" + agent.id + ")", + })); + // A combo box rather than a select, so the list can be filtered and the agent id copied out of the field return html` - + @change="${(ev: Event) => + onAgentChanged(agents.find((agent) => agent.id === (ev.currentTarget as OrVaadinComboBox).value))}" + placeholder="${i18next.t("selectAgent")}" + .items="${options}" + .value="${props.data ?? ""}" + > + + `; }); @@ -142,7 +148,7 @@ const agentIdRenderer = (state: JsonFormsStateContext, props: ControlProps) => { width: 100%; } - ${until(loadedTemplatePromise, html``)} + ${until(loadedTemplatePromise, html``)} `; return getTemplateWrapper(template, undefined); diff --git a/ui/component/or-json-forms/package.json b/ui/component/or-json-forms/package.json index b13380315e..ee889fd9ae 100644 --- a/ui/component/or-json-forms/package.json +++ b/ui/component/or-json-forms/package.json @@ -32,7 +32,6 @@ "@openremote/core": "workspace:*", "@openremote/or-components": "workspace:*", "@openremote/or-element": "workspace:*", - "@openremote/or-mwc-components": "workspace:*", "@openremote/or-translate": "workspace:*", "@openremote/or-vaadin-components": "workspace:*", "ajv": "^8.20.0", diff --git a/ui/component/or-json-forms/src/controls/control-array-element.ts b/ui/component/or-json-forms/src/controls/control-array-element.ts index 420ec895a8..07940227d8 100644 --- a/ui/component/or-json-forms/src/controls/control-array-element.ts +++ b/ui/component/or-json-forms/src/controls/control-array-element.ts @@ -36,11 +36,14 @@ import { showJsonEditor, } from "../util"; import { i18next } from "@openremote/or-translate"; -import { OrMwcDialog, showDialog } from "@openremote/or-mwc-components/or-mwc-dialog"; -import "@openremote/or-mwc-components/or-mwc-list"; +import { type OrVaadinDialog, showDialog } from "@openremote/or-vaadin-components/or-vaadin-dialog"; import { addItemOrParameterDialogStyle, baseStyle, panelStyle } from "../styles"; -import type { ListItem, OrMwcListChangedEvent } from "@openremote/or-mwc-components/or-mwc-list"; +import type { ListItem } from "@openremote/or-vaadin-components/or-vaadin-list-box"; import type { OrVaadinButton } from "@openremote/or-vaadin-components/or-vaadin-button"; +import { createRef, type Ref, ref } from "lit/directives/ref.js"; +import "@openremote/or-vaadin-components/or-vaadin-dialog"; +import "@openremote/or-vaadin-components/or-vaadin-list-box"; +import "@openremote/or-vaadin-components/or-vaadin-item"; import "@openremote/or-vaadin-components/or-vaadin-button"; import { DefaultColor4, DefaultColor5 } from "@openremote/core"; import { ControlBaseElement } from "./control-base-element"; @@ -133,7 +136,7 @@ export class ControlArrayElement extends ControlBaseElement { protected moveItem!: (fromIndex: number, toIndex: number) => void; public static get styles() { - return [baseStyle, panelStyle, style]; + return [baseStyle, panelStyle, addItemOrParameterDialogStyle, style]; } shouldUpdate(_changedProperties: PropertyValues): boolean { @@ -319,7 +322,7 @@ export class ControlArrayElement extends ControlBaseElement { protected _showJson(ev: Event) { ev.stopPropagation(); - showJsonEditor(this.title || this.schema.title || "", this.data, (newValue) => { + showJsonEditor(this.shadowRoot!, this.title || this.schema.title || "", this.data, (newValue) => { this.handleChange(this.path || "", newValue); }); } @@ -338,65 +341,61 @@ export class ControlArrayElement extends ControlBaseElement { protected showAddDialog() { let selectedItemInfo: CombinatorInfo | undefined; + let dialog: OrVaadinDialog | undefined; + const descRef: Ref = createRef(); + const addBtnRef: Ref = createRef(); - const listItems: ListItem[] = this.itemInfos!.map((itemInfo, index) => { + const listItems: ListItem[] = this.itemInfos!.map((itemInfo) => { const labelStr = itemInfo.title ? computeLabel(itemInfo.title, false, true) : ""; return { text: labelStr, value: labelStr, data: itemInfo, }; - }); + }).sort((a, b) => a.text!.localeCompare(b.text!)); const onParamChanged = (itemInfo: CombinatorInfo) => { selectedItemInfo = itemInfo; - const descElem = dialog.shadowRoot!.getElementById("parameter-desc") as HTMLDivElement; - descElem.innerHTML = itemInfo.description || ""; - (dialog.shadowRoot!.getElementById("add-btn") as OrVaadinButton).disabled = false; + descRef.value!.innerHTML = itemInfo.description || ""; + addBtnRef.value!.disabled = false; + }; + + const onAdd = () => { + if (selectedItemInfo) { + this.addItem(selectedItemInfo.defaultValueCreator()); + } + dialog?.close(); }; - const dialog = showDialog( - new OrMwcDialog() - .setContent(html` -
-
-
- -
-
-
+ dialog = showDialog( + this.shadowRoot!, + html` + +

+ ${(this.label ? computeLabel(this.label, this.required, false) + " - " : "") + i18next.t("addItem")} +

+
+ + ${listItems.map((item) => html`${item.text}`)} + +
- `) - .setStyles(addItemOrParameterDialogStyle) - .setHeading((this.label ? computeLabel(this.label, this.required, false) + " - " : "") + i18next.t("addItem")) - .setActions([ - { - actionName: "cancel", - content: "cancel", - }, - { - default: true, - actionName: "add", - action: () => { - if (selectedItemInfo) { - const value = selectedItemInfo.defaultValueCreator(); - this.addItem(value); - } - }, - content: html` - - - - `, - }, - ]) - .setDismissAction(null) + +
+ ` ); } } diff --git a/ui/component/or-json-forms/src/controls/control-input-element.ts b/ui/component/or-json-forms/src/controls/control-input-element.ts index 8f82b111b7..564c538f43 100644 --- a/ui/component/or-json-forms/src/controls/control-input-element.ts +++ b/ui/component/or-json-forms/src/controls/control-input-element.ts @@ -16,9 +16,16 @@ * * SPDX-License-Identifier: AGPL-3.0-or-later */ -import { InputType, type OrInputChangedEvent } from "@openremote/or-mwc-components/or-mwc-input"; -import { css, html } from "lit"; +import { InputType, inputTypeIsJson, stringifyJson } from "@openremote/or-vaadin-components/util"; +import type { OrVaadinInput } from "@openremote/or-vaadin-components/or-vaadin-input"; +import type { SelectItem } from "@openremote/or-vaadin-components/or-vaadin-select"; +import type { OrVaadinComboBox } from "@openremote/or-vaadin-components/or-vaadin-combo-box"; +import type { OrVaadinMultiSelectComboBox } from "@openremote/or-vaadin-components/or-vaadin-multi-select-combo-box"; +import "@openremote/or-vaadin-components/or-vaadin-combo-box"; +import "@openremote/or-vaadin-components/or-vaadin-multi-select-combo-box"; +import { css, html, type TemplateResult } from "lit"; import { customElement } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; import { ControlBaseElement } from "./control-base-element"; import { baseStyle } from "../styles"; import { @@ -36,7 +43,13 @@ let defaultTz: string; // language=CSS const style = css` - or-mwc-input { + or-vaadin-input { + display: block; + } + + or-vaadin-input, + or-vaadin-combo-box, + or-vaadin-multi-select-combo-box { width: 100%; } `; @@ -65,9 +78,7 @@ export class ControlInputElement extends ControlBaseElement { let options: [string, string][] | undefined; let multiple = false; let value: any = this.data ?? schema.default; - let searchable: boolean | undefined; - let searchProvider!: (search?: string) => [any, string][] | undefined; - let onValueChanged = (e: OrInputChangedEvent) => this.onValueChanged(e); + let searchable = false; if (Array.isArray(schema.type)) { this.inputType = InputType.JSON; @@ -95,35 +106,13 @@ export class ControlInputElement extends ControlBaseElement { this.inputType = InputType.SELECT; if (isEnumControl(uischema, schema, context)) { - options = schema.enum!.map((enm) => { - return [JSON.stringify(enm), String(enm)]; - }); + options = ControlInputElement.getOptions(schema.enum!); } else if (isOneOfEnumControl(uischema, schema, context)) { - options = (schema.oneOf as JsonSchema[]).map((s) => { - return [JSON.stringify(s.const), String(s.const)]; - }); + options = ControlInputElement.getOptions((schema.oneOf as JsonSchema[]).map((s) => s.const)); } else { multiple = true; - - if ((schema.items! as JsonSchema).oneOf!) { - options = (schema.items! as JsonSchema).oneOf!.map((s) => { - return [JSON.stringify(s.const), String(s.const)]; - }); - } else { - options = (schema.items! as JsonSchema).enum!.map((enm) => { - return [JSON.stringify(enm), String(enm)]; - }); - } - } - - if (multiple) { - value = Array.isArray(value) - ? value.map((v) => JSON.stringify(v)) - : value !== undefined - ? [JSON.stringify(value)] - : undefined; - } else { - value = value !== undefined ? JSON.stringify(value) : undefined; + const items = schema.items as JsonSchema; + options = ControlInputElement.getOptions(items.oneOf ? items.oneOf.map((s) => s.const) : items.enum!); } } else if (isStringControl(uischema, schema, context)) { minLength = schema.minLength; @@ -146,58 +135,139 @@ export class ControlInputElement extends ControlBaseElement { this.inputType = InputType.PASSWORD; } else if (format === "timezone") { this.inputType = InputType.SELECT; - options = Intl.supportedValuesOf("timeZone").map((z) => [z, z]); + options = Intl.supportedValuesOf("timeZone").map((z) => [JSON.stringify(z), z]); if (!(defaultTz && value)) { defaultTz = Intl.DateTimeFormat().resolvedOptions().timeZone; this.handleChange(this.path, defaultTz); } + // There are hundreds of zones to pick from, so the list needs to be filterable searchable = true; - onValueChanged = (e: OrInputChangedEvent) => this.handleChange(this.path, e.detail.value); - searchProvider = (search?: string) => { - if (search) { - return options?.filter(([, name]) => name.toLowerCase().includes(search.toLowerCase())); - } - return options?.filter(([, name]) => - name.toLowerCase().includes((value ?? defaultTz).toLowerCase().split("/")[0]) - ); - }; } } - return html` v !== undefined).map((v) => JSON.stringify(v)); + } else { + value = value === undefined ? undefined : JSON.stringify(value); + } + } + + if (multiple || searchable) { + return this.getOptionsTemplate(options, value, multiple); + } + + const isCheckbox = this.inputType === InputType.CHECKBOX; + const displayValue = inputTypeIsJson(this.inputType) ? stringifyJson(value) : (value ?? undefined); + return html``; + type="${this.inputType}" + label="${ifDefined(this.label || undefined)}" + value="${ifDefined(isCheckbox ? undefined : displayValue)}" + ?checked="${isCheckbox && !!value}" + ?disabled="${!this.enabled}" + ?required="${!!this.required}" + .items="${ControlInputElement.getInputOptions(options)}" + minlength="${ifDefined(minLength)}" + maxlength="${ifDefined(maxLength)}" + pattern="${ifDefined(pattern)}" + error-message="${ifDefined(this.errors || undefined)}" + step="${ifDefined(step)}" + min="${ifDefined(min)}" + max="${ifDefined(max)}" + @change="${(e: Event) => this.onVaadinValueChanged(e)}" + >`; } - protected onValueChanged(e: OrInputChangedEvent) { - if (this.inputType === InputType.SELECT) { - if (Array.isArray(e.detail.value)) { - this.handleChange( - this.path!, - (e.detail.value as []).map((v: string) => JSON.parse(v)) - ); - } else { - this.handleChange(this.path!, JSON.parse(e.detail.value as string)); + /** + * Template for an enum, which a combo box renders so that long lists can be filtered and several values picked. + */ + protected getOptionsTemplate(options: [string, string][] | undefined, value: any, multiple: boolean): TemplateResult { + const items = ControlInputElement.getInputOptions(options); + + if (multiple) { + const selected: string[] = value ?? []; + return html``; + } + + return html``; + } + + /** + * Vaadin inputs expose their value as a string, so it is parsed back into the type the schema expects. + */ + protected onVaadinValueChanged(e: Event) { + const input = e.currentTarget as OrVaadinInput; + const value = input.nativeValue; + const empty = value === undefined || value === ""; + + switch (this.inputType) { + case InputType.SELECT: { + this.handleChange(this.path!, empty ? undefined : JSON.parse(value)); + break; + } + case InputType.NUMBER: + case InputType.RANGE: { + this.handleChange(this.path!, empty ? undefined : Number(value)); + break; + } + case InputType.DATE: + case InputType.DATETIME: + case InputType.TIME: { + this.handleChange(this.path!, empty ? undefined : value); + break; + } + case InputType.JSON: + case InputType.JSON_OBJECT: { + // Text that does not parse has no value, so keep what is there and let the field show it is invalid + if (input.checkValidity()) { + this.handleChange(this.path!, value); + } + break; + } + default: { + this.handleChange(this.path!, value); } - } else { - this.handleChange(this.path!, e.detail.value); } } + + /** Reads the single option a combo box holds, whose value is the JSON of the schema value. */ + protected onOptionChanged(e: Event) { + const value = (e.currentTarget as OrVaadinComboBox).value; + this.handleChange(this.path!, value ? JSON.parse(value) : undefined); + } + + /** Reads the options a multi select combo box holds, whose values are the JSON of the schema values. */ + protected onOptionsChanged(e: Event) { + const items = (e.currentTarget as OrVaadinMultiSelectComboBox).selectedItems as SelectItem[] | undefined; + this.handleChange(this.path!, items?.map((item) => JSON.parse(item.value!)) ?? []); + } + + /** Builds the options of an enum, whose values are JSON so that every enum type round-trips through them. */ + protected static getOptions(values: unknown[]): [string, string][] { + return values.map((value) => [JSON.stringify(value), String(value)]); + } + + /** Turns the options into the shape a Vaadin select or combo box takes. */ + protected static getInputOptions(options?: [string, string][]): SelectItem[] | undefined { + return options?.map(([value, label]) => ({ value, label })); + } } diff --git a/ui/component/or-json-forms/src/layouts/layout-vertical-element.ts b/ui/component/or-json-forms/src/layouts/layout-vertical-element.ts index b0fdfc4766..2ae89062db 100644 --- a/ui/component/or-json-forms/src/layouts/layout-vertical-element.ts +++ b/ui/component/or-json-forms/src/layouts/layout-vertical-element.ts @@ -32,7 +32,7 @@ import { type StatePropsOfControl, type VerticalLayout, } from "@jsonforms/core"; -import { css, html, PropertyValues, type TemplateResult } from "lit"; +import { css, html, PropertyValues, render, type TemplateResult } from "lit"; import { customElement, property } from "lit/decorators.js"; import { LayoutBaseElement } from "./layout-base-element"; import { @@ -43,16 +43,21 @@ import { getTemplateFromProps, showJsonEditor, } from "../util"; -import { InputType, OrInputChangedEvent, type OrMwcInput } from "@openremote/or-mwc-components/or-mwc-input"; import type { OrVaadinTextField } from "@openremote/or-vaadin-components/or-vaadin-text-field"; +import type { OrVaadinButton } from "@openremote/or-vaadin-components/or-vaadin-button"; +import type { ListItem } from "@openremote/or-vaadin-components/or-vaadin-list-box"; +import { type OrVaadinDialog, showDialog } from "@openremote/or-vaadin-components/or-vaadin-dialog"; import "@openremote/or-vaadin-components/or-vaadin-text-field"; import "@openremote/or-vaadin-components/or-vaadin-button"; +import "@openremote/or-vaadin-components/or-vaadin-dialog"; +import "@openremote/or-vaadin-components/or-vaadin-list-box"; +import "@openremote/or-vaadin-components/or-vaadin-item"; import { i18next } from "@openremote/or-translate"; -import { OrMwcDialog, showDialog } from "@openremote/or-mwc-components/or-mwc-dialog"; -import "@openremote/or-mwc-components/or-mwc-list"; import "@openremote/or-components/or-collapsible-panel"; import { addItemOrParameterDialogStyle, baseStyle, panelStyle } from "../styles"; -import type { ListItem, OrMwcListChangedEvent } from "@openremote/or-mwc-components/or-mwc-list"; +import { createRef, type Ref, ref } from "lit/directives/ref.js"; +import { ifDefined } from "lit/directives/if-defined.js"; +import { when } from "lit/directives/when.js"; import type { AdditionalProps } from "../base-element"; // language=CSS @@ -86,11 +91,6 @@ const style = css` vertical-align: top; } - .key-container or-mwc-input, - .value-container or-mwc-input { - display: block; - } - .value-container > .item-container { margin: 0; } @@ -142,7 +142,7 @@ export class LayoutVerticalElement extends LayoutBaseElement void; public static get styles() { - return [baseStyle, panelStyle, style]; + return [baseStyle, panelStyle, addItemOrParameterDialogStyle, style]; } render() { @@ -322,7 +322,7 @@ export class LayoutVerticalElement extends LayoutBaseElement { + showJsonEditor(this.shadowRoot!, this.title || this.schema.title || "", this.data, (newValue) => { this.handleChange(this.path || "", newValue); }); } @@ -335,7 +335,11 @@ export class LayoutVerticalElement extends LayoutBaseElement = createRef(); + const addBtnRef: Ref = createRef(); const listItems: ListItem[] = optionalProps.map((props) => { const labelStr = computeLabel(props.label, !!props.required, false); @@ -346,155 +350,120 @@ export class LayoutVerticalElement extends LayoutBaseElement render(getDescriptionTemplate(), descRef.value!); + + const onSchemaSelected = (selectedSchema: CombinatorInfo) => { + selectedOneOf = selectedSchema; + addBtnRef.value!.disabled = !selectedOneOf; + refreshDescription(); + }; + const onParamChanged = (selected: StatePropsOfControl) => { selectedParameter = selected; - const isOneOf = !!(selectedParameter && selectedParameter.schema && selectedParameter.schema.oneOf); - (dialog.shadowRoot!.getElementById("add-btn") as OrMwcInput).disabled = isOneOf; - dialog.requestUpdate(); + selectedOneOf = undefined; + addBtnRef.value!.disabled = !!selected.schema?.oneOf; + refreshDescription(); }; - // const keyValue: [any, any] = [undefined, undefined]; - // const onKeyValueChanged = (value: any, index: 0 | 1) => { - // keyValue[index] = value; - // const valid = keyValue[0] && keyValue[1] !== undefined; - // (dialog.shadowRoot!.getElementById("add-btn") as OrMwcInput).disabled = !valid; - // }; - let keyValue: string | undefined; - const onKeyChanged = (event: KeyboardEvent) => { - const keyInput = event.currentTarget as OrMwcInput; - keyInput.setCustomValidity(undefined); - keyValue = keyInput.currentValue as string; - let valid = keyInput.valid; - - if (this.data[keyValue] !== undefined) { - valid = false; - keyInput.setCustomValidity(i18next.t("validation.keyAlreadyExists")); - } - (dialog.shadowRoot!.getElementById("add-btn") as OrMwcInput).disabled = !valid; + const onKeyChanged = (ev: Event) => { + const keyInput = ev.currentTarget as OrVaadinTextField; + keyValue = keyInput.value; + const duplicate = !!keyValue && this.data?.[keyValue] !== undefined; + keyInput.errorMessage = duplicate ? i18next.t("validation.keyAlreadyExists") : undefined; + const valid = keyInput.validate() && !duplicate; + keyInput.invalid = !valid; + addBtnRef.value!.disabled = !valid; + }; + + const onAdd = () => { + const key = dynamic ? (keyValue as string) : selectedParameter!.path.split(".").pop()!; + const data = { ...this.data }; + const schema = dynamic ? dynamicValueSchema! : selectedParameter!.schema; + data[key] = Array.isArray(schema.type) + ? null + : (selectedOneOf ? selectedOneOf.defaultValueCreator() : undefined) || createDefaultValue(schema, rootSchema); + this.handleChange(this.path || "", data); + dialog?.close(); }; - const dialogContentProvider: () => TemplateResult = () => { - // Only set when !dynamic - let schemaPicker: TemplateResult | undefined; - - if (selectedParameter && selectedParameter.schema && selectedParameter.schema.oneOf) { - const handleChange = (selectedSchema: CombinatorInfo) => { - selectedOneOf = selectedSchema; - (dialog.shadowRoot!.getElementById("add-btn") as OrMwcInput).disabled = !selectedOneOf; - (dialog.shadowRoot!.getElementById("schema-description") as HTMLParagraphElement).innerHTML = - (selectedOneOf ? selectedOneOf.description : i18next.t("schema.selectTypeMessage")) || - i18next.t("schema.noDescriptionAvailable"); - }; - schemaPicker = getSchemaPicker( - rootSchema, - selectedParameter.schema.oneOf, - selectedParameter.label, - handleChange - ); + const getDescriptionTemplate: () => TemplateResult = () => { + if (dynamic) { + return html` + + + + `; } + if (!selectedParameter) { + return html``; + } + + const oneOf = selectedParameter.schema?.oneOf as JsonSchema[] | undefined; + return html` -
-
- ${ - dynamic - ? `` - : html` -
- -
- ` - } -
- ${ - !selectedParameter - ? `` - : html` -

${selectedParameter.description}

` - } + +

${selectedParameter.description}

+ ${when( + oneOf, + () => html` +
+ ${getSchemaPicker(rootSchema, oneOf!, selectedParameter!.label, onSchemaSelected)} +
+

${ - !dynamic - ? !schemaPicker - ? `` - : html` -

- - ${schemaPicker} -
-

${i18next.t("schema.selectTypeMessage")}

` - : html` - -
- -
- ` + selectedOneOf + ? selectedOneOf.description || i18next.t("schema.noDescriptionAvailable") + : i18next.t("schema.selectTypeMessage") } -
-
-
+

+ ` + )} `; }; - const dialog = showDialog( - new OrMwcDialog() - .setContent(dialogContentProvider) - .setStyles(addItemOrParameterDialogStyle) - .setHeading( - (this.label ? computeLabel(this.label, this.required, false) + " - " : "") + i18next.t("addParameter") - ) - .setActions([ - { - actionName: "cancel", - content: "cancel", - }, - { - default: true, - actionName: "add", - action: () => { - const key = dynamic ? (keyValue as string) : selectedParameter!.path.split(".").pop()!; - const data = { ...this.data }; - const schema = dynamic ? dynamicValueSchema! : selectedParameter!.schema; - data[key] = Array.isArray(schema.type) - ? null - : (selectedOneOf ? selectedOneOf.defaultValueCreator() : undefined) || - createDefaultValue(schema, rootSchema); - this.handleChange(this.path || "", data); - }, - content: html``, - }, - ]) - .setDismissAction(null) + dialog = showDialog( + this.shadowRoot!, + html` + +

+ ${(this.label ? computeLabel(this.label, this.required, false) + " - " : "") + i18next.t("addParameter")} +

+
+ ${when( + !dynamic, + () => html` + + ${listItems.map((item) => html`${item.text}`)} + + ` + )} +
+
+ +
+ ` ); + + refreshDescription(); } } diff --git a/ui/component/or-json-forms/src/standard-renderers.ts b/ui/component/or-json-forms/src/standard-renderers.ts index 4408ad07e7..aff1752f72 100644 --- a/ui/component/or-json-forms/src/standard-renderers.ts +++ b/ui/component/or-json-forms/src/standard-renderers.ts @@ -57,7 +57,7 @@ import { uiTypeIs, } from "@jsonforms/core"; import { html, type TemplateResult } from "lit"; -import "@openremote/or-mwc-components/or-mwc-input"; +import { i18next } from "@openremote/or-translate"; import "@openremote/or-vaadin-components/or-vaadin-input"; import type { JsonFormsStateContext } from "./index"; import { @@ -247,7 +247,7 @@ export const anyOfOneOfControlRenderer = (state: JsonFormsStateContext, props: C // }; // return html` - //
${label}:
+ //
${label}:json
// `; // } else { // We have no data so show a schema picker @@ -342,7 +342,7 @@ export function getTemplateWrapper(elementTemplate: TemplateResult, deleteHandle const deleteTemplate = !deleteHandler ? `` : html` -