Last active
October 24, 2022 18:20
-
-
Save kinghat/7bdcea005cfc9da17f1b1c9b62749043 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { LitElement, html } from "lit"; | |
import { customElement, state, property, query } from "lit/decorators.js"; | |
import { HomeAssistant, fireEvent, LovelaceConfig } from "custom-card-helpers"; | |
import { BoilerplateCardConfig } from "./types"; | |
@customElement("tabbed-card-editor") | |
export class TabbedCardEditor extends LitElement { | |
@property() public hass!: HomeAssistant; | |
@property() public lovelace!: LovelaceConfig; | |
@state() protected _config?: BoilerplateCardConfig; | |
@state() private _selectedTab = 0; | |
@state() private _cardGUIMode = true; | |
@state() private _cardGUIModeAvailable = true; | |
@query("hui-card-element-editor") protected _cardEditorElement; | |
setConfig(config) { | |
if (!config) throw new Error("No configuration."); | |
this._config = config; | |
} | |
private _handleSelectedTab(ev) { | |
if (ev.target.id === "add-card") { | |
this._selectedTab = this._config?.tabs.length; | |
return; | |
} | |
this._setMode(true); | |
this._cardGUIModeAvailable = true; | |
this._selectedTab = ev.detail.selected; | |
} | |
protected _handleConfigChanged(ev) { | |
ev.stopPropagation(); | |
if (!this._config) return; | |
const cardConfig = ev.detail.config; | |
const tab = { card: cardConfig }; | |
const tabs = [...this._config.tabs]; | |
tabs[this._selectedTab] = tab; | |
this._config = { ...this._config, tabs }; | |
this._cardGUIModeAvailable = ev.detail.guiModeAvailable; | |
fireEvent(this, "config-changed", { config: this._config }); | |
} | |
protected _handleGUIModeChanged(ev): void { | |
ev.stopPropagation(); | |
this._cardGUIMode = ev.detail.guiMode; | |
this._cardGUIModeAvailable = ev.detail.guiModeAvailable; | |
} | |
protected _handleCardPicked(ev) { | |
ev.stopPropagation(); | |
if (!this._config) return; | |
const cardConfig = ev.detail.config; | |
const tabs = [...this._config.tabs, { card: cardConfig }]; | |
this._config = { ...this._config, tabs }; | |
fireEvent(this, "config-changed", { config: this._config }); | |
} | |
protected _setMode(value: boolean): void { | |
this._cardGUIMode = value; | |
if (this._cardEditorElement) { | |
this._cardEditorElement!.GUImode = value; | |
} | |
} | |
protected _toggleMode(): void { | |
this._cardEditorElement?.toggleMode(); | |
} | |
public focusYamlEditor() { | |
this._cardEditorElement?.focusYamlEditor(); | |
} | |
render() { | |
if (!this.hass || !this._config) return html``; | |
return html` | |
<div class="card-config"> | |
<div class="toolbar"> | |
<paper-tabs | |
scrollable | |
.selected=${this._selectedTab} | |
@iron-activate=${this._handleSelectedTab} | |
> | |
${this._config.tabs.map( | |
(_tab, tabIndex) => html` <paper-tab> ${tabIndex} </paper-tab> `, | |
)} | |
></paper-tabs | |
> | |
<paper-tabs | |
id="add-card" | |
.selected=${this._selectedTab === this._config.tabs.length | |
? "0" | |
: undefined} | |
@iron-activate=${this._handleSelectedTab} | |
> | |
<paper-tab> | |
<ha-icon icon="mdi:plus"></ha-icon> | |
</paper-tab> | |
</paper-tabs> | |
</div> | |
<div id="editor"> | |
${this._selectedTab < this._config.tabs.length | |
? html` | |
<div id="card-options"> | |
<mwc-button | |
@click=${this._toggleMode} | |
.disabled=${!this._cardGUIModeAvailable} | |
class="gui-mode-button" | |
> | |
${this.hass!.localize( | |
!this._cardEditorElement || this._cardGUIMode | |
? "ui.panel.lovelace.editor.edit_card.show_code_editor" | |
: "ui.panel.lovelace.editor.edit_card.show_visual_editor", | |
)} | |
</mwc-button> | |
</div> | |
<hui-card-element-editor | |
.hass=${this.hass} | |
.value=${this._config.tabs[this._selectedTab].card} | |
.lovelace=${this.lovelace} | |
@config-changed=${this._handleConfigChanged} | |
@GUImode-changed=${this._handleGUIModeChanged} | |
></hui-card-element-editor> | |
` | |
: html` | |
<hui-card-picker | |
.hass=${this.hass} | |
.lovelace=${this.lovelace} | |
@config-changed=${this._handleCardPicked} | |
></hui-card-picker> | |
`} | |
</div> | |
</div> | |
`; | |
} | |
} | |
declare global { | |
interface HTMLElementTagNameMap { | |
"tabbed-card-editor": TabbedCardEditor; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { LitElement, html, PropertyValueMap, nothing } from "lit"; | |
import { customElement, state, property } from "lit/decorators.js"; | |
import { styleMap } from "lit/directives/style-map.js"; | |
import { ifDefined } from "lit/directives/if-defined.js"; | |
import { | |
getLovelace, | |
hasConfigOrEntityChanged, | |
HomeAssistant, | |
LovelaceCard, | |
LovelaceCardConfig, | |
LovelaceCardEditor, | |
LovelaceConfig, | |
} from "custom-card-helpers"; | |
import "./tabbed-card-editor"; | |
interface mwcTabBarEvent extends Event { | |
detail: { | |
index: number; | |
}; | |
} | |
interface TabbedCardConfig extends LovelaceCardConfig { | |
options?: TabbedCardOptions; | |
styles?: {}; | |
attributes?: {}; | |
tabs: TabConfig[]; | |
} | |
interface TabbedCardOptions { | |
defaultTabIndex?: number; | |
} | |
interface TabConfig { | |
styles?: {}; | |
attributes?: { | |
label?: string; | |
icon?: string; | |
isFadingIndicator?: boolean; | |
minWidth?: boolean; | |
isMinWidthIndicator?: boolean; | |
stacked?: boolean; | |
}; | |
card: LovelaceCardConfig; | |
} | |
interface Tab extends Omit<TabConfig, "card"> { | |
card: LovelaceCard; | |
} | |
@customElement("tabbed-card") | |
export class TabbedCard extends LitElement { | |
@property({ attribute: false }) public hass!: HomeAssistant; | |
@property() protected selectedTabIndex = 0; | |
@property() private _helpers: any; | |
@state() private _config!: TabbedCardConfig; | |
@state() private _tabs!: Tab[]; | |
@property() protected _styles = { | |
"--mdc-theme-primary": "var(--primary-text-color)", // Color of the activated tab's text, indicator, and ripple. | |
"--mdc-tab-text-label-color-default": | |
"rgba(var(--rgb-primary-text-color), 0.8)", // Color of an unactivated tab label. | |
"--mdc-typography-button-font-size": "14px", | |
}; | |
private async loadCardHelpers() { | |
this._helpers = await (window as any).loadCardHelpers(); | |
} | |
static async getConfigElement(): Promise<LovelaceCardEditor> { | |
return document.createElement("tabbed-card-editor"); | |
} | |
static getStubConfig() { | |
return { | |
tabs: [], | |
}; | |
} | |
public setConfig(config: TabbedCardConfig) { | |
if (!config) { | |
throw new Error("No configuration."); | |
} | |
console.log("tabbed-card: setConfig: ", config); | |
this._config = config; | |
this._styles = { | |
...this._styles, | |
...this._config.styles, | |
}; | |
this.loadCardHelpers(); | |
} | |
protected willUpdate( | |
_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>, | |
): void { | |
if (_changedProperties.has("_helpers")) { | |
this._createTabs(this._config); | |
} | |
if (_changedProperties.has("hass") && this._tabs?.length) { | |
this._tabs.forEach((tab) => (tab.card.hass = this.hass)); | |
} | |
} | |
private async _createTabs(config: TabbedCardConfig) { | |
const tabs = await Promise.all( | |
config.tabs.map(async (tab) => { | |
return { | |
styles: tab?.styles, | |
attributes: { ...config?.attributes, ...tab?.attributes }, | |
card: await this._createCard(tab.card), | |
}; | |
}), | |
); | |
this._tabs = tabs; | |
} | |
private async _createCard(cardConfig: LovelaceCardConfig) { | |
const cardElement = await this._helpers.createCardElement(cardConfig); | |
cardElement.hass = this.hass; | |
cardElement.addEventListener( | |
"ll-rebuild", | |
(ev: Event) => { | |
console.log("_createCard: ll-rebuild: ", cardElement, cardConfig); | |
ev.stopPropagation(); | |
this._rebuildCard(cardElement, cardConfig); | |
}, | |
{ once: true }, | |
); | |
return cardElement; | |
} | |
private async _rebuildCard( | |
cardElement: LovelaceCard, | |
cardConfig: LovelaceCardConfig, | |
) { | |
console.log("_rebuildCard: ", cardElement, cardConfig); | |
const newCardElement = await this._createCard(cardConfig); | |
cardElement.replaceWith(newCardElement); | |
this._tabs = this._tabs.map((tab) => | |
tab.card === cardElement ? { ...tab, card: newCardElement } : tab, | |
); | |
} | |
protected render() { | |
if (!this.hass || !this._config || !this._helpers || !this._tabs?.length) { | |
return html``; | |
} | |
console.log("render: tabbed-card: _config: ", this._config); | |
return html` | |
<mwc-tab-bar | |
@MDCTabBar:activated=${(ev: mwcTabBarEvent) => | |
(this.selectedTabIndex = ev.detail.index)} | |
style=${styleMap(this._styles)} | |
activeIndex=${ifDefined(this._config?.options?.defaultTabIndex)} | |
> | |
<!-- no horizontal scrollbar shown when tabs overflow in chrome --> | |
${this._tabs.map( | |
(tab) => | |
html` | |
<mwc-tab | |
style=${ifDefined(styleMap(tab?.styles || {}))} | |
label="${tab?.attributes?.label || nothing}" | |
?hasImageIcon=${tab?.attributes?.icon} | |
?isFadingIndicator=${tab?.attributes?.isFadingIndicator} | |
?minWidth=${tab?.attributes?.minWidth} | |
?isMinWidthIndicator=${tab?.attributes?.isMinWidthIndicator} | |
?stacked=${tab?.attributes?.stacked} | |
> | |
${tab?.attributes?.icon | |
? html`<ha-icon | |
slot="icon" | |
icon="${tab?.attributes?.icon}" | |
></ha-icon>` | |
: html``} | |
</mwc-tab> | |
`, | |
)} | |
</mwc-tab-bar> | |
<section> | |
<article> | |
${this._tabs.find((_, index) => index == this.selectedTabIndex)?.card} | |
</article> | |
</section> | |
`; | |
} | |
} | |
declare global { | |
interface HTMLElementTagNameMap { | |
"tabbed-card": TabbedCard; | |
} | |
} | |
(window as any).customCards = (window as any).customCards || []; | |
(window as any).customCards.push({ | |
type: "tabbed-card", | |
name: "Tabbed Card", | |
description: "A tabbed card of cards.", | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment