Skip to content

Instantly share code, notes, and snippets.

@kinghat
Last active October 24, 2022 18:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kinghat/7bdcea005cfc9da17f1b1c9b62749043 to your computer and use it in GitHub Desktop.
Save kinghat/7bdcea005cfc9da17f1b1c9b62749043 to your computer and use it in GitHub Desktop.
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;
}
}
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