mirror of
				https://github.com/home-assistant/frontend.git
				synced 2025-10-25 11:39:41 +00:00 
			
		
		
		
	Compare commits
	
		
			8 Commits
		
	
	
		
			20250526.0
			...
			card_edito
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 76380c189b | ||
|   | c8b7f373c3 | ||
|   | 4f2652abd2 | ||
|   | 6d8b7f6995 | ||
|   | bbf8a8e3e7 | ||
|   | 14308c9057 | ||
|   | b87f44ff74 | ||
|   | 36540aa8fb | 
| @@ -1,4 +1,5 @@ | |||||||
| import { Button } from "@material/mwc-button"; | import { Button } from "@material/mwc-button"; | ||||||
|  | import { Corner } from "@material/web/menu/menu"; | ||||||
| import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; | import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; | ||||||
| import { customElement, property, query } from "lit/decorators"; | import { customElement, property, query } from "lit/decorators"; | ||||||
| import { FOCUS_TARGET } from "../dialogs/make-dialog-manager"; | import { FOCUS_TARGET } from "../dialogs/make-dialog-manager"; | ||||||
| @@ -14,8 +15,20 @@ export class HaButtonMenuNew extends LitElement { | |||||||
|  |  | ||||||
|   @property() public positioning?: "fixed" | "absolute" | "popover"; |   @property() public positioning?: "fixed" | "absolute" | "popover"; | ||||||
|  |  | ||||||
|   @property({ type: Boolean, attribute: "has-overflow" }) public hasOverflow = |   @property({ type: Boolean, attribute: "no-horizontal-flip" }) | ||||||
|     false; |   public noHorizontalFlip = false; | ||||||
|  |  | ||||||
|  |   @property({ type: Boolean, attribute: "no-vertical-flip" }) | ||||||
|  |   public noVerticalFlip = false; | ||||||
|  |  | ||||||
|  |   @property({ attribute: "anchor-corner" }) | ||||||
|  |   public anchorCorner: Corner = Corner.END_START; | ||||||
|  |  | ||||||
|  |   @property({ attribute: "menu-corner" }) | ||||||
|  |   public menuCorner: Corner = Corner.START_START; | ||||||
|  |  | ||||||
|  |   @property({ type: Boolean, attribute: "has-overflow" }) | ||||||
|  |   public hasOverflow = false; | ||||||
|  |  | ||||||
|   @query("ha-menu", true) private _menu!: HaMenu; |   @query("ha-menu", true) private _menu!: HaMenu; | ||||||
|  |  | ||||||
| @@ -39,6 +52,10 @@ export class HaButtonMenuNew extends LitElement { | |||||||
|       <ha-menu |       <ha-menu | ||||||
|         .positioning=${this.positioning} |         .positioning=${this.positioning} | ||||||
|         .hasOverflow=${this.hasOverflow} |         .hasOverflow=${this.hasOverflow} | ||||||
|  |         .anchorCorner=${this.anchorCorner} | ||||||
|  |         .menuCorner=${this.menuCorner} | ||||||
|  |         .noVerticalFlip=${this.noVerticalFlip} | ||||||
|  |         .noHorizontalFlip=${this.noHorizontalFlip} | ||||||
|       > |       > | ||||||
|         <slot></slot> |         <slot></slot> | ||||||
|       </ha-menu> |       </ha-menu> | ||||||
|   | |||||||
							
								
								
									
										22
									
								
								src/components/ha-outlined-segmented-button-set.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/components/ha-outlined-segmented-button-set.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | import { MdOutlinedSegmentedButtonSet } from "@material/web/labs/segmentedbuttonset/outlined-segmented-button-set"; | ||||||
|  | import { css } from "lit"; | ||||||
|  | import { customElement } from "lit/decorators"; | ||||||
|  |  | ||||||
|  | @customElement("ha-outlined-segmented-button-set") | ||||||
|  | export class HaOutlinedSegmentedButtonSet extends MdOutlinedSegmentedButtonSet { | ||||||
|  |   static override styles = [ | ||||||
|  |     ...super.styles, | ||||||
|  |     css` | ||||||
|  |       :host { | ||||||
|  |         --ha-icon-display: block; | ||||||
|  |         --md-outlined-segmented-button-container-height: 32px; | ||||||
|  |       } | ||||||
|  |     `, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | declare global { | ||||||
|  |   interface HTMLElementTagNameMap { | ||||||
|  |     "ha-outlined-segmented-button-set": HaOutlinedSegmentedButtonSet; | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										34
									
								
								src/components/ha-outlined-segmented-button.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/components/ha-outlined-segmented-button.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | import { MdOutlinedSegmentedButton } from "@material/web/labs/segmentedbutton/outlined-segmented-button"; | ||||||
|  | import { css } from "lit"; | ||||||
|  | import { customElement } from "lit/decorators"; | ||||||
|  |  | ||||||
|  | @customElement("ha-outlined-segmented-button") | ||||||
|  | export class HaOutlinedSegmentedButton extends MdOutlinedSegmentedButton { | ||||||
|  |   static override styles = [ | ||||||
|  |     ...super.styles, | ||||||
|  |     css` | ||||||
|  |       :host { | ||||||
|  |         --ha-icon-display: block; | ||||||
|  |         --md-outlined-segmented-button-selected-container-color: var( | ||||||
|  |           --light-primary-color | ||||||
|  |         ); | ||||||
|  |         --md-outlined-segmented-button-container-height: 32px; | ||||||
|  |         --md-outlined-segmented-button-disabled-label-text-color: var( | ||||||
|  |           --disabled-text-color | ||||||
|  |         ); | ||||||
|  |         --md-outlined-segmented-button-disabled-icon-color: var( | ||||||
|  |           --disabled-text-color | ||||||
|  |         ); | ||||||
|  |         --md-outlined-segmented-button-disabled-outline-color: var( | ||||||
|  |           --disabled-text-color | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |     `, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | declare global { | ||||||
|  |   interface HTMLElementTagNameMap { | ||||||
|  |     "ha-outlined-segmented-button": HaOutlinedSegmentedButton; | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										25
									
								
								src/panels/lovelace/common/compute-card-name.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/panels/lovelace/common/compute-card-name.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | import { LocalizeFunc } from "../../../common/translations/localize"; | ||||||
|  | import { LovelaceCardConfig } from "../../../data/lovelace/config/card"; | ||||||
|  | import { | ||||||
|  |   getCustomCardEntry, | ||||||
|  |   isCustomType, | ||||||
|  |   stripCustomPrefix, | ||||||
|  | } from "../../../data/lovelace_custom_cards"; | ||||||
|  |  | ||||||
|  | export const computeCardName = ( | ||||||
|  |   config: LovelaceCardConfig, | ||||||
|  |   localize: LocalizeFunc | ||||||
|  | ): string | undefined => { | ||||||
|  |   if (isCustomType(config.type)) { | ||||||
|  |     // prettier-ignore | ||||||
|  |     let cardName = getCustomCardEntry( | ||||||
|  |           stripCustomPrefix(config.type) | ||||||
|  |         )?.name; | ||||||
|  |     // Trim names that end in " Card" so as not to redundantly duplicate it | ||||||
|  |     if (cardName?.toLowerCase().endsWith(" card")) { | ||||||
|  |       cardName = cardName.substring(0, cardName.length - 5); | ||||||
|  |     } | ||||||
|  |     return cardName; | ||||||
|  |   } | ||||||
|  |   return localize(`ui.panel.lovelace.editor.card.${config.type}.name`); | ||||||
|  | }; | ||||||
							
								
								
									
										155
									
								
								src/panels/lovelace/editor/card-editor/hui-card-editor.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								src/panels/lovelace/editor/card-editor/hui-card-editor.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,155 @@ | |||||||
|  | import "@material/mwc-tab-bar/mwc-tab-bar"; | ||||||
|  | import "@material/mwc-tab/mwc-tab"; | ||||||
|  | import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; | ||||||
|  | import { customElement, property, query, state } from "lit/decorators"; | ||||||
|  | import memoizeOne from "memoize-one"; | ||||||
|  | import { fireEvent } from "../../../../common/dom/fire_event"; | ||||||
|  | import { LovelaceCardConfig } from "../../../../data/lovelace/config/card"; | ||||||
|  | import { LovelaceSectionConfig } from "../../../../data/lovelace/config/section"; | ||||||
|  | import { LovelaceConfig } from "../../../../data/lovelace/config/types"; | ||||||
|  | import { LovelaceViewConfig } from "../../../../data/lovelace/config/view"; | ||||||
|  | import { HomeAssistant } from "../../../../types"; | ||||||
|  | import "./hui-card-element-editor"; | ||||||
|  | import type { HuiCardElementEditor } from "./hui-card-element-editor"; | ||||||
|  | import "./hui-card-layout-editor"; | ||||||
|  | import "./hui-card-visibility-editor"; | ||||||
|  |  | ||||||
|  | const TABS = ["config", "visibility", "layout"] as const; | ||||||
|  |  | ||||||
|  | @customElement("hui-card-editor") | ||||||
|  | class HuiCardEditor extends LitElement { | ||||||
|  |   @property({ attribute: false }) public hass!: HomeAssistant; | ||||||
|  |  | ||||||
|  |   @property({ attribute: false }) public lovelace!: LovelaceConfig; | ||||||
|  |  | ||||||
|  |   @property({ attribute: false }) public config!: LovelaceCardConfig; | ||||||
|  |  | ||||||
|  |   @property({ attribute: false }) public containerConfig!: | ||||||
|  |     | LovelaceViewConfig | ||||||
|  |     | LovelaceSectionConfig; | ||||||
|  |  | ||||||
|  |   @query("hui-card-element-editor") | ||||||
|  |   public elementEditor?: HuiCardElementEditor; | ||||||
|  |  | ||||||
|  |   @state() private _selectedTab: (typeof TABS)[number] = TABS[0]; | ||||||
|  |  | ||||||
|  |   private _tabs = memoizeOne( | ||||||
|  |     (containerType: string | undefined, cardType: string) => | ||||||
|  |       TABS.filter((tab) => { | ||||||
|  |         if (tab === "visibility") return cardType !== "conditional"; | ||||||
|  |         if (tab === "layout") return containerType === "grid"; | ||||||
|  |         return true; | ||||||
|  |       }) | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  |   private _elementConfig = memoizeOne((config: LovelaceCardConfig) => { | ||||||
|  |     const { visibility, layout_options, ...elementConfig } = config; | ||||||
|  |     return elementConfig; | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   private renderContent() { | ||||||
|  |     if (this._selectedTab === "config") { | ||||||
|  |       return html` | ||||||
|  |         <hui-card-element-editor | ||||||
|  |           .hass=${this.hass} | ||||||
|  |           .lovelace=${this.lovelace} | ||||||
|  |           .value=${this._elementConfig(this.config)} | ||||||
|  |           show-toggle-mode-button | ||||||
|  |           @config-changed=${this._elementConfigChanged} | ||||||
|  |         ></hui-card-element-editor> | ||||||
|  |       `; | ||||||
|  |     } | ||||||
|  |     if (this._selectedTab === "visibility") { | ||||||
|  |       return html` | ||||||
|  |         <hui-card-visibility-editor | ||||||
|  |           .hass=${this.hass} | ||||||
|  |           .config=${this.config} | ||||||
|  |           @value-changed=${this._configChanged} | ||||||
|  |         ></hui-card-visibility-editor> | ||||||
|  |       `; | ||||||
|  |     } | ||||||
|  |     if (this._selectedTab === "layout") { | ||||||
|  |       return html` | ||||||
|  |         <hui-card-layout-editor | ||||||
|  |           .hass=${this.hass} | ||||||
|  |           .config=${this.config} | ||||||
|  |           .sectionConfig=${this.containerConfig as LovelaceSectionConfig} | ||||||
|  |           @value-changed=${this._configChanged} | ||||||
|  |         > | ||||||
|  |         </hui-card-layout-editor> | ||||||
|  |       `; | ||||||
|  |     } | ||||||
|  |     return nothing; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private _configChanged(ev: CustomEvent): void { | ||||||
|  |     ev.stopPropagation(); | ||||||
|  |     fireEvent(this, "config-changed", { config: ev.detail.value }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private _elementConfigChanged(ev: CustomEvent): void { | ||||||
|  |     ev.stopPropagation(); | ||||||
|  |     const config = ev.detail.config; | ||||||
|  |     const newConfig = { | ||||||
|  |       ...config, | ||||||
|  |       visibility: this.config.visibility, | ||||||
|  |       layout_options: this.config.layout_options, | ||||||
|  |     }; | ||||||
|  |     fireEvent(this, "config-changed", { config: newConfig }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected render() { | ||||||
|  |     const cardType = this.config.type; | ||||||
|  |     const containerType = this.containerConfig.type; | ||||||
|  |     const tabs = this._tabs(containerType, cardType); | ||||||
|  |  | ||||||
|  |     if (tabs.length <= 1) { | ||||||
|  |       return this.renderContent(); | ||||||
|  |     } | ||||||
|  |     return html` | ||||||
|  |       <mwc-tab-bar | ||||||
|  |         .activeIndex=${tabs.indexOf(this._selectedTab)} | ||||||
|  |         @MDCTabBar:activated=${this._handleTabChanged} | ||||||
|  |       > | ||||||
|  |         ${tabs.map( | ||||||
|  |           (tab) => html` | ||||||
|  |             <mwc-tab | ||||||
|  |               .label=${this.hass.localize( | ||||||
|  |                 `ui.panel.lovelace.editor.edit_card.tab_${tab}` | ||||||
|  |               )} | ||||||
|  |             > | ||||||
|  |             </mwc-tab> | ||||||
|  |           ` | ||||||
|  |         )} | ||||||
|  |       </mwc-tab-bar> | ||||||
|  |       ${this.renderContent()} | ||||||
|  |     `; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private _handleTabChanged(ev: CustomEvent): void { | ||||||
|  |     const cardType = this.config.type; | ||||||
|  |     const containerType = this.containerConfig.type; | ||||||
|  |     const tabs = this._tabs(containerType, cardType); | ||||||
|  |     const newTab = tabs[ev.detail.index]; | ||||||
|  |     if (newTab === this._selectedTab) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     this._selectedTab = newTab; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static get styles(): CSSResultGroup { | ||||||
|  |     return css` | ||||||
|  |       mwc-tab-bar { | ||||||
|  |         text-transform: uppercase; | ||||||
|  |         margin-bottom: 16px; | ||||||
|  |         border-bottom: 1px solid var(--divider-color); | ||||||
|  |       } | ||||||
|  |     `; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | declare global { | ||||||
|  |   interface HTMLElementTagNameMap { | ||||||
|  |     "hui-card-editor": HuiCardEditor; | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -1,26 +1,11 @@ | |||||||
| import "@material/mwc-tab-bar/mwc-tab-bar"; | import { customElement } from "lit/decorators"; | ||||||
| import "@material/mwc-tab/mwc-tab"; |  | ||||||
| import { CSSResultGroup, TemplateResult, css, html, nothing } from "lit"; |  | ||||||
| import { customElement, property, state } from "lit/decorators"; |  | ||||||
| import { LovelaceCardConfig } from "../../../../data/lovelace/config/card"; | import { LovelaceCardConfig } from "../../../../data/lovelace/config/card"; | ||||||
| import { getCardElementClass } from "../../create-element/create-card-element"; | import { getCardElementClass } from "../../create-element/create-card-element"; | ||||||
| import type { LovelaceCardEditor, LovelaceConfigForm } from "../../types"; | import type { LovelaceCardEditor, LovelaceConfigForm } from "../../types"; | ||||||
| import { HuiElementEditor } from "../hui-element-editor"; | import { HuiElementEditor } from "../hui-element-editor"; | ||||||
| import "./hui-card-layout-editor"; |  | ||||||
| import "./hui-card-visibility-editor"; |  | ||||||
| import { LovelaceSectionConfig } from "../../../../data/lovelace/config/section"; |  | ||||||
|  |  | ||||||
| const tabs = ["config", "visibility", "layout"] as const; |  | ||||||
|  |  | ||||||
| @customElement("hui-card-element-editor") | @customElement("hui-card-element-editor") | ||||||
| export class HuiCardElementEditor extends HuiElementEditor<LovelaceCardConfig> { | export class HuiCardElementEditor extends HuiElementEditor<LovelaceCardConfig> { | ||||||
|   @property({ type: Boolean, attribute: "show-visibility-tab" }) |  | ||||||
|   public showVisibilityTab = false; |  | ||||||
|  |  | ||||||
|   @property({ attribute: false }) public sectionConfig?: LovelaceSectionConfig; |  | ||||||
|  |  | ||||||
|   @state() private _currTab: (typeof tabs)[number] = tabs[0]; |  | ||||||
|  |  | ||||||
|   protected async getConfigElement(): Promise<LovelaceCardEditor | undefined> { |   protected async getConfigElement(): Promise<LovelaceCardEditor | undefined> { | ||||||
|     const elClass = await getCardElementClass(this.configElementType!); |     const elClass = await getCardElementClass(this.configElementType!); | ||||||
|  |  | ||||||
| @@ -42,93 +27,6 @@ export class HuiCardElementEditor extends HuiElementEditor<LovelaceCardConfig> { | |||||||
|  |  | ||||||
|     return undefined; |     return undefined; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private _configChanged(ev: CustomEvent): void { |  | ||||||
|     ev.stopPropagation(); |  | ||||||
|     this.value = ev.detail.value; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   get _showLayoutTab(): boolean { |  | ||||||
|     return ( |  | ||||||
|       !!this.sectionConfig && |  | ||||||
|       (this.sectionConfig.type === undefined || |  | ||||||
|         this.sectionConfig.type === "grid") |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   protected renderConfigElement(): TemplateResult { |  | ||||||
|     const displayedTabs: string[] = ["config"]; |  | ||||||
|     if (this.showVisibilityTab) displayedTabs.push("visibility"); |  | ||||||
|     if (this._showLayoutTab) displayedTabs.push("layout"); |  | ||||||
|  |  | ||||||
|     if (displayedTabs.length === 1) return super.renderConfigElement(); |  | ||||||
|  |  | ||||||
|     let content: TemplateResult<1> | typeof nothing = nothing; |  | ||||||
|  |  | ||||||
|     switch (this._currTab) { |  | ||||||
|       case "config": |  | ||||||
|         content = html`${super.renderConfigElement()}`; |  | ||||||
|         break; |  | ||||||
|       case "visibility": |  | ||||||
|         content = html` |  | ||||||
|           <hui-card-visibility-editor |  | ||||||
|             .hass=${this.hass} |  | ||||||
|             .config=${this.value} |  | ||||||
|             @value-changed=${this._configChanged} |  | ||||||
|           ></hui-card-visibility-editor> |  | ||||||
|         `; |  | ||||||
|         break; |  | ||||||
|       case "layout": |  | ||||||
|         content = html` |  | ||||||
|           <hui-card-layout-editor |  | ||||||
|             .hass=${this.hass} |  | ||||||
|             .config=${this.value} |  | ||||||
|             .sectionConfig=${this.sectionConfig!} |  | ||||||
|             @value-changed=${this._configChanged} |  | ||||||
|           > |  | ||||||
|           </hui-card-layout-editor> |  | ||||||
|         `; |  | ||||||
|     } |  | ||||||
|     return html` |  | ||||||
|       <mwc-tab-bar |  | ||||||
|         .activeIndex=${tabs.indexOf(this._currTab)} |  | ||||||
|         @MDCTabBar:activated=${this._handleTabChanged} |  | ||||||
|       > |  | ||||||
|         ${displayedTabs.map( |  | ||||||
|           (tab) => html` |  | ||||||
|             <mwc-tab |  | ||||||
|               .label=${this.hass.localize( |  | ||||||
|                 `ui.panel.lovelace.editor.edit_card.tab_${tab}` |  | ||||||
|               )} |  | ||||||
|             > |  | ||||||
|             </mwc-tab> |  | ||||||
|           ` |  | ||||||
|         )} |  | ||||||
|       </mwc-tab-bar> |  | ||||||
|       ${content} |  | ||||||
|     `; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private _handleTabChanged(ev: CustomEvent): void { |  | ||||||
|     const newTab = tabs[ev.detail.index]; |  | ||||||
|     if (newTab === this._currTab) { |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|     this._currTab = newTab; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   static get styles(): CSSResultGroup { |  | ||||||
|     return [ |  | ||||||
|       HuiElementEditor.styles, |  | ||||||
|       css` |  | ||||||
|         mwc-tab-bar { |  | ||||||
|           text-transform: uppercase; |  | ||||||
|           margin-bottom: 16px; |  | ||||||
|           border-bottom: 1px solid var(--divider-color); |  | ||||||
|         } |  | ||||||
|       `, |  | ||||||
|     ]; |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| declare global { | declare global { | ||||||
|   | |||||||
| @@ -1,5 +1,14 @@ | |||||||
| import { mdiClose, mdiHelpCircle } from "@mdi/js"; | import "@material/mwc-list"; | ||||||
|  | import "@material/web/divider/divider"; | ||||||
|  | import { | ||||||
|  |   mdiCheck, | ||||||
|  |   mdiClose, | ||||||
|  |   mdiDotsVertical, | ||||||
|  |   mdiHelpCircle, | ||||||
|  |   mdiOpenInNew, | ||||||
|  | } from "@mdi/js"; | ||||||
| import deepFreeze from "deep-freeze"; | import deepFreeze from "deep-freeze"; | ||||||
|  | import { dump, load } from "js-yaml"; | ||||||
| import { | import { | ||||||
|   CSSResultGroup, |   CSSResultGroup, | ||||||
|   LitElement, |   LitElement, | ||||||
| @@ -12,32 +21,30 @@ import { customElement, property, query, state } from "lit/decorators"; | |||||||
| import memoizeOne from "memoize-one"; | import memoizeOne from "memoize-one"; | ||||||
| import type { HASSDomEvent } from "../../../../common/dom/fire_event"; | import type { HASSDomEvent } from "../../../../common/dom/fire_event"; | ||||||
| import { fireEvent } from "../../../../common/dom/fire_event"; | import { fireEvent } from "../../../../common/dom/fire_event"; | ||||||
| import { computeRTLDirection } from "../../../../common/util/compute_rtl"; | import "../../../../components/ha-button"; | ||||||
|  | import "../../../../components/ha-button-menu-new"; | ||||||
| import "../../../../components/ha-circular-progress"; | import "../../../../components/ha-circular-progress"; | ||||||
|  | import "../../../../components/ha-code-editor"; | ||||||
| import "../../../../components/ha-dialog"; | import "../../../../components/ha-dialog"; | ||||||
| import "../../../../components/ha-dialog-header"; | import "../../../../components/ha-dialog-header"; | ||||||
| import "../../../../components/ha-icon-button"; | import "../../../../components/ha-icon-button"; | ||||||
|  | import "../../../../components/ha-menu-item"; | ||||||
| import { LovelaceCardConfig } from "../../../../data/lovelace/config/card"; | import { LovelaceCardConfig } from "../../../../data/lovelace/config/card"; | ||||||
| import { LovelaceSectionConfig } from "../../../../data/lovelace/config/section"; | import { LovelaceSectionConfig } from "../../../../data/lovelace/config/section"; | ||||||
| import { LovelaceViewConfig } from "../../../../data/lovelace/config/view"; | import { LovelaceViewConfig } from "../../../../data/lovelace/config/view"; | ||||||
| import { |  | ||||||
|   getCustomCardEntry, |  | ||||||
|   isCustomType, |  | ||||||
|   stripCustomPrefix, |  | ||||||
| } from "../../../../data/lovelace_custom_cards"; |  | ||||||
| import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box"; | import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box"; | ||||||
| import type { HassDialog } from "../../../../dialogs/make-dialog-manager"; | import type { HassDialog } from "../../../../dialogs/make-dialog-manager"; | ||||||
| import { haStyleDialog } from "../../../../resources/styles"; | import { haStyleDialog } from "../../../../resources/styles"; | ||||||
| import type { HomeAssistant } from "../../../../types"; | import type { HomeAssistant } from "../../../../types"; | ||||||
| import { showSaveSuccessToast } from "../../../../util/toast-saved-success"; | import { showSaveSuccessToast } from "../../../../util/toast-saved-success"; | ||||||
| import "../../cards/hui-card"; | import "../../cards/hui-card"; | ||||||
|  | import { computeCardName } from "../../common/compute-card-name"; | ||||||
| import "../../sections/hui-section"; | import "../../sections/hui-section"; | ||||||
| import { addCard, replaceCard } from "../config-util"; | import { addCard, replaceCard } from "../config-util"; | ||||||
| import { getCardDocumentationURL } from "../get-dashboard-documentation-url"; | import { getCardDocumentationURL } from "../get-dashboard-documentation-url"; | ||||||
| import type { ConfigChangedEvent } from "../hui-element-editor"; | import type { ConfigChangedEvent } from "../hui-element-editor"; | ||||||
| import { findLovelaceContainer } from "../lovelace-path"; | import { findLovelaceContainer } from "../lovelace-path"; | ||||||
| import type { GUIModeChangedEvent } from "../types"; | import "./hui-card-editor"; | ||||||
| import "./hui-card-element-editor"; |  | ||||||
| import type { HuiCardElementEditor } from "./hui-card-element-editor"; | import type { HuiCardElementEditor } from "./hui-card-element-editor"; | ||||||
| import type { EditCardDialogParams } from "./show-edit-card-dialog"; | import type { EditCardDialogParams } from "./show-edit-card-dialog"; | ||||||
|  |  | ||||||
| @@ -73,12 +80,10 @@ export class HuiDialogEditCard | |||||||
|  |  | ||||||
|   @state() private _error?: string; |   @state() private _error?: string; | ||||||
|  |  | ||||||
|   @state() private _guiModeAvailable? = true; |   @query("hui-card-editor") | ||||||
|  |  | ||||||
|   @query("hui-card-element-editor") |  | ||||||
|   private _cardEditorEl?: HuiCardElementEditor; |   private _cardEditorEl?: HuiCardElementEditor; | ||||||
|  |  | ||||||
|   @state() private _GUImode = true; |   @state() private _yamlMode = false; | ||||||
|  |  | ||||||
|   @state() private _documentationURL?: string; |   @state() private _documentationURL?: string; | ||||||
|  |  | ||||||
| @@ -88,8 +93,7 @@ export class HuiDialogEditCard | |||||||
|  |  | ||||||
|   public async showDialog(params: EditCardDialogParams): Promise<void> { |   public async showDialog(params: EditCardDialogParams): Promise<void> { | ||||||
|     this._params = params; |     this._params = params; | ||||||
|     this._GUImode = true; |     this._yamlMode = false; | ||||||
|     this._guiModeAvailable = true; |  | ||||||
|  |  | ||||||
|     const containerConfig = findLovelaceContainer( |     const containerConfig = findLovelaceContainer( | ||||||
|       params.lovelaceConfig, |       params.lovelaceConfig, | ||||||
| @@ -168,21 +172,7 @@ export class HuiDialogEditCard | |||||||
|  |  | ||||||
|     let heading: string; |     let heading: string; | ||||||
|     if (this._cardConfig && this._cardConfig.type) { |     if (this._cardConfig && this._cardConfig.type) { | ||||||
|       let cardName: string | undefined; |       const cardName = computeCardName(this._cardConfig, this.hass!.localize); | ||||||
|       if (isCustomType(this._cardConfig.type)) { |  | ||||||
|         // prettier-ignore |  | ||||||
|         cardName = getCustomCardEntry( |  | ||||||
|           stripCustomPrefix(this._cardConfig.type) |  | ||||||
|         )?.name; |  | ||||||
|         // Trim names that end in " Card" so as not to redundantly duplicate it |  | ||||||
|         if (cardName?.toLowerCase().endsWith(" card")) { |  | ||||||
|           cardName = cardName.substring(0, cardName.length - 5); |  | ||||||
|         } |  | ||||||
|       } else { |  | ||||||
|         cardName = this.hass!.localize( |  | ||||||
|           `ui.panel.lovelace.editor.card.${this._cardConfig.type}.name` |  | ||||||
|         ); |  | ||||||
|       } |  | ||||||
|       heading = this.hass!.localize( |       heading = this.hass!.localize( | ||||||
|         "ui.panel.lovelace.editor.edit_card.typed_header", |         "ui.panel.lovelace.editor.edit_card.typed_header", | ||||||
|         { type: cardName } |         { type: cardName } | ||||||
| @@ -218,36 +208,99 @@ export class HuiDialogEditCard | |||||||
|             .path=${mdiClose} |             .path=${mdiClose} | ||||||
|           ></ha-icon-button> |           ></ha-icon-button> | ||||||
|           <span slot="title" @click=${this._enlarge}>${heading}</span> |           <span slot="title" @click=${this._enlarge}>${heading}</span> | ||||||
|  |           <ha-button-menu-new | ||||||
|  |             slot="actionItems" | ||||||
|  |             anchor-corner="end-end" | ||||||
|  |             menu-corner="start-end" | ||||||
|  |           > | ||||||
|  |             <ha-icon-button | ||||||
|  |               slot="trigger" | ||||||
|  |               .label=${this.hass.localize("ui.common.menu")} | ||||||
|  |               .path=${mdiDotsVertical} | ||||||
|  |             > | ||||||
|  |             </ha-icon-button> | ||||||
|  |             <ha-menu-item @click=${this._enableGuiMode}> | ||||||
|  |               ${!this._yamlMode | ||||||
|  |                 ? html` | ||||||
|  |                     <ha-svg-icon | ||||||
|  |                       class="selected_menu_item" | ||||||
|  |                       slot="start" | ||||||
|  |                       .path=${mdiCheck} | ||||||
|  |                     ></ha-svg-icon> | ||||||
|  |                   ` | ||||||
|  |                 : html`<span class="blank-icon" slot="start"></span>`} | ||||||
|  |               <div slot="headline"> | ||||||
|  |                 ${this.hass.localize( | ||||||
|  |                   "ui.panel.lovelace.editor.edit_card.edit_ui" | ||||||
|  |                 )} | ||||||
|  |               </div> | ||||||
|  |             </ha-menu-item> | ||||||
|  |             <ha-menu-item @click=${this._enableYamlMode}> | ||||||
|  |               ${this._yamlMode | ||||||
|  |                 ? html` | ||||||
|  |                     <ha-svg-icon | ||||||
|  |                       class="selected_menu_item" | ||||||
|  |                       slot="start" | ||||||
|  |                       .path=${mdiCheck} | ||||||
|  |                     ></ha-svg-icon> | ||||||
|  |                   ` | ||||||
|  |                 : html`<span class="blank-icon" slot="start"></span>`} | ||||||
|  |               <div slot="headline"> | ||||||
|  |                 ${this.hass.localize( | ||||||
|  |                   "ui.panel.lovelace.editor.edit_card.edit_yaml" | ||||||
|  |                 )} | ||||||
|  |               </div> | ||||||
|  |             </ha-menu-item> | ||||||
|             ${this._documentationURL !== undefined |             ${this._documentationURL !== undefined | ||||||
|               ? html` |               ? html` | ||||||
|                 <a |                   <md-divider role="separator" tabindex="-1"></md-divider> | ||||||
|                   slot="actionItems" |                   <ha-menu-item | ||||||
|  |                     type="link" | ||||||
|                     href=${this._documentationURL} |                     href=${this._documentationURL} | ||||||
|                   title=${this.hass!.localize("ui.panel.lovelace.menu.help")} |  | ||||||
|                     target="_blank" |                     target="_blank" | ||||||
|                     rel="noreferrer" |                     rel="noreferrer" | ||||||
|                   dir=${computeRTLDirection(this.hass)} |  | ||||||
|                   > |                   > | ||||||
|                   <ha-icon-button .path=${mdiHelpCircle}></ha-icon-button> |                     <ha-svg-icon | ||||||
|                 </a> |                       slot="start" | ||||||
|  |                       .path=${mdiHelpCircle} | ||||||
|  |                     ></ha-svg-icon> | ||||||
|  |                     <div slot="headline"> | ||||||
|  |                       ${this.hass!.localize("ui.panel.lovelace.menu.help")} | ||||||
|  |                     </div> | ||||||
|  |                     <ha-svg-icon slot="end" .path=${mdiOpenInNew}></ha-svg-icon> | ||||||
|  |                   </ha-menu-item> | ||||||
|                 ` |                 ` | ||||||
|               : nothing} |               : nothing} | ||||||
|  |           </ha-button-menu-new> | ||||||
|         </ha-dialog-header> |         </ha-dialog-header> | ||||||
|         <div class="content"> |         <div class="content"> | ||||||
|           <div class="element-editor"> |           <div class="element-editor"> | ||||||
|             <hui-card-element-editor |             ${this._yamlMode | ||||||
|               .showVisibilityTab=${this._cardConfig?.type !== "conditional"} |               ? html` | ||||||
|               .sectionConfig=${this._isInSection |                   <ha-code-editor | ||||||
|                 ? this._containerConfig |                     mode="yaml" | ||||||
|                 : undefined} |                     autofocus | ||||||
|  |                     autocomplete-entities | ||||||
|  |                     autocomplete-icons | ||||||
|  |                     .hass=${this.hass} | ||||||
|  |                     .value=${dump(this._cardConfig)} | ||||||
|  |                     @value-changed=${this._handleYAMLChanged} | ||||||
|  |                     @keydown=${this._ignoreKeydown} | ||||||
|  |                     dir="ltr" | ||||||
|  |                   ></ha-code-editor> | ||||||
|  |                 ` | ||||||
|  |               : html` | ||||||
|  |                   <hui-card-editor | ||||||
|  |                     .containerConfig=${this._containerConfig} | ||||||
|                     .hass=${this.hass} |                     .hass=${this.hass} | ||||||
|                     .lovelace=${this._params.lovelaceConfig} |                     .lovelace=${this._params.lovelaceConfig} | ||||||
|               .value=${this._cardConfig} |                     .config=${this._cardConfig} | ||||||
|                     @config-changed=${this._handleConfigChanged} |                     @config-changed=${this._handleConfigChanged} | ||||||
|               @GUImode-changed=${this._handleGUIModeChanged} |  | ||||||
|                     @editor-save=${this._save} |                     @editor-save=${this._save} | ||||||
|                     dialogInitialFocus |                     dialogInitialFocus | ||||||
|             ></hui-card-element-editor> |                   > | ||||||
|  |                   </hui-card-editor> | ||||||
|  |                 `} | ||||||
|           </div> |           </div> | ||||||
|           <div class="element-preview"> |           <div class="element-preview"> | ||||||
|             ${this._isInSection |             ${this._isInSection | ||||||
| @@ -277,29 +330,17 @@ export class HuiDialogEditCard | |||||||
|               : ``} |               : ``} | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|         ${this._cardConfig !== undefined |         <ha-button | ||||||
|           ? html` |           @click=${this._cancel} | ||||||
|               <mwc-button |  | ||||||
|           slot="secondaryAction" |           slot="secondaryAction" | ||||||
|                 @click=${this._toggleMode} |           dialogInitialFocus | ||||||
|                 .disabled=${!this._guiModeAvailable} |  | ||||||
|                 class="gui-mode-button" |  | ||||||
|         > |         > | ||||||
|                 ${this.hass!.localize( |  | ||||||
|                   !this._cardEditorEl || this._GUImode |  | ||||||
|                     ? "ui.panel.lovelace.editor.edit_card.show_code_editor" |  | ||||||
|                     : "ui.panel.lovelace.editor.edit_card.show_visual_editor" |  | ||||||
|                 )} |  | ||||||
|               </mwc-button> |  | ||||||
|             ` |  | ||||||
|           : ""} |  | ||||||
|         <div slot="primaryAction" @click=${this._save}> |  | ||||||
|           <mwc-button @click=${this._cancel} dialogInitialFocus> |  | ||||||
|           ${this.hass!.localize("ui.common.cancel")} |           ${this.hass!.localize("ui.common.cancel")} | ||||||
|           </mwc-button> |         </ha-button> | ||||||
|         ${this._cardConfig !== undefined && this._dirty |         ${this._cardConfig !== undefined && this._dirty | ||||||
|           ? html` |           ? html` | ||||||
|                 <mwc-button |               <ha-button | ||||||
|  |                 slot="primaryAction" | ||||||
|                 ?disabled=${!this._canSave || this._saving} |                 ?disabled=${!this._canSave || this._saving} | ||||||
|                 @click=${this._save} |                 @click=${this._save} | ||||||
|               > |               > | ||||||
| @@ -312,14 +353,21 @@ export class HuiDialogEditCard | |||||||
|                       ></ha-circular-progress> |                       ></ha-circular-progress> | ||||||
|                     ` |                     ` | ||||||
|                   : this.hass!.localize("ui.common.save")} |                   : this.hass!.localize("ui.common.save")} | ||||||
|                 </mwc-button> |               </ha-button> | ||||||
|             ` |             ` | ||||||
|             : ``} |           : nothing} | ||||||
|         </div> |  | ||||||
|       </ha-dialog> |       </ha-dialog> | ||||||
|     `; |     `; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   private _enableGuiMode() { | ||||||
|  |     this._yamlMode = false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private _enableYamlMode() { | ||||||
|  |     this._yamlMode = true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   private _enlarge() { |   private _enlarge() { | ||||||
|     this.large = !this.large; |     this.large = !this.large; | ||||||
|   } |   } | ||||||
| @@ -328,27 +376,21 @@ export class HuiDialogEditCard | |||||||
|     ev.stopPropagation(); |     ev.stopPropagation(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private _handleConfigChanged(ev: HASSDomEvent<ConfigChangedEvent>) { |   private _handleYAMLChanged(ev: CustomEvent) { | ||||||
|     this._cardConfig = deepFreeze(ev.detail.config); |     this._cardConfig = load(ev.detail.value) as LovelaceCardConfig; | ||||||
|     this._error = ev.detail.error; |  | ||||||
|     this._guiModeAvailable = ev.detail.guiModeAvailable; |  | ||||||
|     this._dirty = true; |     this._dirty = true; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private _handleGUIModeChanged(ev: HASSDomEvent<GUIModeChangedEvent>): void { |   private _handleConfigChanged(ev: HASSDomEvent<ConfigChangedEvent>) { | ||||||
|     ev.stopPropagation(); |     this._cardConfig = deepFreeze(ev.detail.config); | ||||||
|     this._GUImode = ev.detail.guiMode; |     this._error = ev.detail.error; | ||||||
|     this._guiModeAvailable = ev.detail.guiModeAvailable; |     this._dirty = true; | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private _toggleMode(): void { |  | ||||||
|     this._cardEditorEl?.toggleMode(); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private _opened() { |   private _opened() { | ||||||
|     window.addEventListener("dialog-closed", this._enableEscapeKeyClose); |     window.addEventListener("dialog-closed", this._enableEscapeKeyClose); | ||||||
|     window.addEventListener("hass-more-info", this._disableEscapeKeyClose); |     window.addEventListener("hass-more-info", this._disableEscapeKeyClose); | ||||||
|     this._cardEditorEl?.focusYamlEditor(); |     // this._cardEditorEl?.focusYamlEditor(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private get _isInSection() { |   private get _isInSection() { | ||||||
| @@ -551,11 +593,6 @@ export class HuiDialogEditCard | |||||||
|           width: 100%; |           width: 100%; | ||||||
|           box-sizing: border-box; |           box-sizing: border-box; | ||||||
|         } |         } | ||||||
|         .gui-mode-button { |  | ||||||
|           margin-right: auto; |  | ||||||
|           margin-inline-end: auto; |  | ||||||
|           margin-inline-start: initial; |  | ||||||
|         } |  | ||||||
|         .header { |         .header { | ||||||
|           display: flex; |           display: flex; | ||||||
|           align-items: center; |           align-items: center; | ||||||
| @@ -565,6 +602,12 @@ export class HuiDialogEditCard | |||||||
|           color: inherit; |           color: inherit; | ||||||
|           text-decoration: none; |           text-decoration: none; | ||||||
|         } |         } | ||||||
|  |         .selected_menu_item { | ||||||
|  |           color: var(--primary-color); | ||||||
|  |         } | ||||||
|  |         .blank-icon { | ||||||
|  |           width: 16px; | ||||||
|  |         } | ||||||
|       `, |       `, | ||||||
|     ]; |     ]; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import "@material/mwc-button"; | import { mdiCodeBraces, mdiListBox } from "@mdi/js"; | ||||||
| import { dump, load } from "js-yaml"; | import { dump, load } from "js-yaml"; | ||||||
| import { | import { | ||||||
|   CSSResultGroup, |   CSSResultGroup, | ||||||
| @@ -7,6 +7,7 @@ import { | |||||||
|   TemplateResult, |   TemplateResult, | ||||||
|   css, |   css, | ||||||
|   html, |   html, | ||||||
|  |   nothing, | ||||||
| } from "lit"; | } from "lit"; | ||||||
| import { property, query, state } from "lit/decorators"; | import { property, query, state } from "lit/decorators"; | ||||||
| import { fireEvent } from "../../../common/dom/fire_event"; | import { fireEvent } from "../../../common/dom/fire_event"; | ||||||
| @@ -16,14 +17,17 @@ import "../../../components/ha-alert"; | |||||||
| import "../../../components/ha-circular-progress"; | import "../../../components/ha-circular-progress"; | ||||||
| import "../../../components/ha-code-editor"; | import "../../../components/ha-code-editor"; | ||||||
| import type { HaCodeEditor } from "../../../components/ha-code-editor"; | import type { HaCodeEditor } from "../../../components/ha-code-editor"; | ||||||
|  | import "../../../components/ha-outlined-segmented-button"; | ||||||
|  | import "../../../components/ha-outlined-segmented-button-set"; | ||||||
|  | import { LovelaceBadgeConfig } from "../../../data/lovelace/config/badge"; | ||||||
| import { LovelaceCardConfig } from "../../../data/lovelace/config/card"; | import { LovelaceCardConfig } from "../../../data/lovelace/config/card"; | ||||||
| import { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy"; | import { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy"; | ||||||
| import { LovelaceConfig } from "../../../data/lovelace/config/types"; | import { LovelaceConfig } from "../../../data/lovelace/config/types"; | ||||||
| import type { HomeAssistant } from "../../../types"; | import type { HomeAssistant } from "../../../types"; | ||||||
| import { LovelaceCardFeatureConfig } from "../card-features/types"; | import { LovelaceCardFeatureConfig } from "../card-features/types"; | ||||||
|  | import { LovelaceElementConfig } from "../elements/types"; | ||||||
| import type { LovelaceRowConfig } from "../entity-rows/types"; | import type { LovelaceRowConfig } from "../entity-rows/types"; | ||||||
| import { LovelaceHeaderFooterConfig } from "../header-footer/types"; | import { LovelaceHeaderFooterConfig } from "../header-footer/types"; | ||||||
| import { LovelaceElementConfig } from "../elements/types"; |  | ||||||
| import type { | import type { | ||||||
|   LovelaceConfigForm, |   LovelaceConfigForm, | ||||||
|   LovelaceGenericElementEditor, |   LovelaceGenericElementEditor, | ||||||
| @@ -33,7 +37,6 @@ import type { HuiFormEditor } from "./config-elements/hui-form-editor"; | |||||||
| import "./config-elements/hui-generic-entity-row-editor"; | import "./config-elements/hui-generic-entity-row-editor"; | ||||||
| import { GUISupportError } from "./gui-support-error"; | import { GUISupportError } from "./gui-support-error"; | ||||||
| import { EditSubElementEvent, GUIModeChangedEvent } from "./types"; | import { EditSubElementEvent, GUIModeChangedEvent } from "./types"; | ||||||
| import { LovelaceBadgeConfig } from "../../../data/lovelace/config/badge"; |  | ||||||
|  |  | ||||||
| export interface ConfigChangedEvent { | export interface ConfigChangedEvent { | ||||||
|   config: |   config: | ||||||
| @@ -73,6 +76,9 @@ export abstract class HuiElementEditor<T, C = any> extends LitElement { | |||||||
|  |  | ||||||
|   @property({ attribute: false }) public context?: C; |   @property({ attribute: false }) public context?: C; | ||||||
|  |  | ||||||
|  |   @property({ type: Boolean, attribute: "show-toggle-mode-button" }) | ||||||
|  |   public showToggleModeButton = false; | ||||||
|  |  | ||||||
|   @state() private _yaml?: string; |   @state() private _yaml?: string; | ||||||
|  |  | ||||||
|   @state() private _config?: T; |   @state() private _config?: T; | ||||||
| @@ -208,8 +214,40 @@ export abstract class HuiElementEditor<T, C = any> extends LitElement { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   protected render(): TemplateResult { |   protected render(): TemplateResult { | ||||||
|  |     const guiModeAvailable = !( | ||||||
|  |       this.hasWarning || | ||||||
|  |       this.hasError || | ||||||
|  |       this._guiSupported === false | ||||||
|  |     ); | ||||||
|  |  | ||||||
|     return html` |     return html` | ||||||
|       <div class="wrapper"> |       <div class="wrapper"> | ||||||
|  |         ${this.showToggleModeButton | ||||||
|  |           ? html` | ||||||
|  |               <div class="header"> | ||||||
|  |                 <ha-outlined-segmented-button-set | ||||||
|  |                   @segmented-button-set-selection=${this._handleModeSelected} | ||||||
|  |                 > | ||||||
|  |                   <ha-outlined-segmented-button | ||||||
|  |                     .selected=${this._guiMode} | ||||||
|  |                     .disabled=${!guiModeAvailable} | ||||||
|  |                     no-checkmark | ||||||
|  |                   > | ||||||
|  |                     <ha-svg-icon slot="icon" .path=${mdiListBox}></ha-svg-icon> | ||||||
|  |                   </ha-outlined-segmented-button> | ||||||
|  |                   <ha-outlined-segmented-button | ||||||
|  |                     .selected=${!this._guiMode} | ||||||
|  |                     no-checkmark | ||||||
|  |                   > | ||||||
|  |                     <ha-svg-icon | ||||||
|  |                       slot="icon" | ||||||
|  |                       .path=${mdiCodeBraces} | ||||||
|  |                     ></ha-svg-icon> | ||||||
|  |                   </ha-outlined-segmented-button> | ||||||
|  |                 </ha-outlined-segmented-button-set> | ||||||
|  |               </div> | ||||||
|  |             ` | ||||||
|  |           : nothing} | ||||||
|         ${this.GUImode |         ${this.GUImode | ||||||
|           ? html` |           ? html` | ||||||
|               <div class="gui-editor"> |               <div class="gui-editor"> | ||||||
| @@ -241,43 +279,58 @@ export abstract class HuiElementEditor<T, C = any> extends LitElement { | |||||||
|             `} |             `} | ||||||
|         ${this._guiSupported === false && this.configElementType |         ${this._guiSupported === false && this.configElementType | ||||||
|           ? html` |           ? html` | ||||||
|               <div class="info"> |               <ha-alert | ||||||
|                 ${this.hass.localize("ui.errors.config.editor_not_available", { |                 alert-type="info" | ||||||
|                   type: this.configElementType, |                 .title=${this.hass.localize( | ||||||
|                 })} |                   "ui.errors.config.editor_not_supported" | ||||||
|               </div> |                 )} | ||||||
|  |               > | ||||||
|  |                 ${this.hass.localize( | ||||||
|  |                   "ui.errors.config.editor_not_supported_details", | ||||||
|  |                   { type: this.configElementType } | ||||||
|  |                 )} | ||||||
|  |                 <br /> | ||||||
|  |                 ${this.hass.localize("ui.errors.config.edit_in_yaml_supported")} | ||||||
|  |               </ha-alert> | ||||||
|             ` |             ` | ||||||
|           : ""} |           : nothing} | ||||||
|         ${this.hasError |         ${this.hasError | ||||||
|           ? html` |           ? html` | ||||||
|               <div class="error"> |               <ha-alert | ||||||
|                 ${this.hass.localize("ui.errors.config.error_detected")}: |                 alert-type="error" | ||||||
|                 <br /> |                 .title=${this.hass.localize( | ||||||
|  |                   "ui.errors.config.invalid_configuration" | ||||||
|  |                 )} | ||||||
|  |               > | ||||||
|  |                 ${this.hass.localize("ui.errors.config.error_details")} | ||||||
|                 <ul> |                 <ul> | ||||||
|                   ${this._errors!.map((error) => html`<li>${error}</li>`)} |                   ${this._errors!.map((error) => html`<li>${error}</li>`)} | ||||||
|                 </ul> |                 </ul> | ||||||
|               </div> |               </ha-alert> | ||||||
|             ` |             ` | ||||||
|           : ""} |           : nothing} | ||||||
|         ${this.hasWarning |         ${this.hasWarning | ||||||
|           ? html` |           ? html` | ||||||
|               <ha-alert |               <ha-alert | ||||||
|                 alert-type="warning" |                 alert-type="warning" | ||||||
|                 .title="${this.hass.localize( |                 .title=${this.hass.localize( | ||||||
|                   "ui.errors.config.editor_not_supported" |                   "ui.errors.config.editor_not_supported" | ||||||
|                 )}:" |                 )} | ||||||
|               > |               > | ||||||
|                 ${this._warnings!.length > 0 && this._warnings![0] !== undefined |                 ${this._warnings!.length > 0 && this._warnings![0] !== undefined | ||||||
|                   ? html` <ul> |                   ? html` | ||||||
|  |                       ${this.hass.localize("ui.errors.config.warning_details")} | ||||||
|  |                       <ul> | ||||||
|                         ${this._warnings!.map( |                         ${this._warnings!.map( | ||||||
|                           (warning) => html`<li>${warning}</li>` |                           (warning) => html`<li>${warning}</li>` | ||||||
|                         )} |                         )} | ||||||
|                     </ul>` |                       </ul> | ||||||
|                   : ""} |                     ` | ||||||
|  |                   : nothing} | ||||||
|                 ${this.hass.localize("ui.errors.config.edit_in_yaml_supported")} |                 ${this.hass.localize("ui.errors.config.edit_in_yaml_supported")} | ||||||
|               </ha-alert> |               </ha-alert> | ||||||
|             ` |             ` | ||||||
|           : ""} |           : nothing} | ||||||
|       </div> |       </div> | ||||||
|     `; |     `; | ||||||
|   } |   } | ||||||
| @@ -311,6 +364,10 @@ export abstract class HuiElementEditor<T, C = any> extends LitElement { | |||||||
|     this.value = config as unknown as T; |     this.value = config as unknown as T; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   private _handleModeSelected(ev) { | ||||||
|  |     this.GUImode = ev.detail.index === 0; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   private _handleYAMLChanged(ev: CustomEvent) { |   private _handleYAMLChanged(ev: CustomEvent) { | ||||||
|     ev.stopPropagation(); |     ev.stopPropagation(); | ||||||
|     const newYaml = ev.detail.value; |     const newYaml = ev.detail.value; | ||||||
| @@ -452,6 +509,10 @@ export abstract class HuiElementEditor<T, C = any> extends LitElement { | |||||||
|         display: block; |         display: block; | ||||||
|         margin: auto; |         margin: auto; | ||||||
|       } |       } | ||||||
|  |       .header { | ||||||
|  |         display: flex; | ||||||
|  |         justify-content: flex-end; | ||||||
|  |       } | ||||||
|     `; |     `; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1797,10 +1797,13 @@ | |||||||
|     "errors": { |     "errors": { | ||||||
|       "config": { |       "config": { | ||||||
|         "no_type_provided": "No type provided.", |         "no_type_provided": "No type provided.", | ||||||
|         "error_detected": "Configuration errors detected", |         "invalid_configuration": "Invalid configuration", | ||||||
|  |         "error_details": "The configuration contains the following errors:", | ||||||
|         "editor_not_available": "No visual editor available for type ''{type}''.", |         "editor_not_available": "No visual editor available for type ''{type}''.", | ||||||
|         "editor_not_supported": "Visual editor is not supported for this configuration", |         "editor_not_supported": "Visual editor not supported", | ||||||
|         "edit_in_yaml_supported": "You can still edit your config in YAML.", |         "editor_not_supported_details": "The visual editor is not supported for type ''{type}''.", | ||||||
|  |         "warning_details": "The configuration contains the following warnings:", | ||||||
|  |         "edit_in_yaml_supported": "You can still edit the configuration in YAML.", | ||||||
|         "key_missing": "Required key ''{key}'' is missing.", |         "key_missing": "Required key ''{key}'' is missing.", | ||||||
|         "key_not_expected": "Key ''{key}'' is not expected or not supported by the visual editor.", |         "key_not_expected": "Key ''{key}'' is not expected or not supported by the visual editor.", | ||||||
|         "key_wrong_type": "The provided value for ''{key}'' is not supported by the visual editor. We support ({type_correct}) but received ({type_wrong}).", |         "key_wrong_type": "The provided value for ''{key}'' is not supported by the visual editor. We support ({type_correct}) but received ({type_wrong}).", | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user