diff --git a/package-lock.json b/package-lock.json index 81f98f6..cf665ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,11 @@ "name": "ha-combined-card", "version": "1.0.4", "license": "MIT", + "dependencies": { + "query-selector-shadow-dom": "^1.0.1" + }, "devDependencies": { + "@types/query-selector-shadow-dom": "^1.0.4", "custom-card-helpers": "^1.9.0", "esbuild": "^0.20.1", "lit": "^3.1.2" @@ -456,6 +460,13 @@ "@lit-labs/ssr-dom-shim": "^1.2.0" } }, + "node_modules/@types/query-selector-shadow-dom": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/query-selector-shadow-dom/-/query-selector-shadow-dom-1.0.4.tgz", + "integrity": "sha512-8jfGPD0wCMCdyBvrvOrWVn8bHL1UEjkPVJKsqNZpEXp+a7mIIvvGpJMd6n6dlNl7IkG2ryxIVqFI516RPE0uhQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -627,6 +638,12 @@ "@types/trusted-types": "^2.0.2" } }, + "node_modules/query-selector-shadow-dom": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/query-selector-shadow-dom/-/query-selector-shadow-dom-1.0.1.tgz", + "integrity": "sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==", + "license": "MIT" + }, "node_modules/rollup": { "version": "2.79.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", diff --git a/package.json b/package.json index 4499264..86965cd 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,11 @@ "keywords": [], "author": "", "license": "MIT", + "dependencies": { + "query-selector-shadow-dom": "^1.0.1" + }, "devDependencies": { + "@types/query-selector-shadow-dom": "^1.0.4", "custom-card-helpers": "^1.9.0", "esbuild": "^0.20.1", "lit": "^3.1.2" diff --git a/src/combined-card-editor.ts b/src/combined-card-editor.ts index ee2d50c..9529f88 100644 --- a/src/combined-card-editor.ts +++ b/src/combined-card-editor.ts @@ -1,88 +1,91 @@ import { html, LitElement } from "lit"; import { HomeAssistant, LovelaceCardConfig, LovelaceCardEditor, LovelaceConfig } from 'custom-card-helpers'; -import { NAME, EDITOR_NAME, LOG_EDITOR as LOG } from './utils'; - -class CombinedCardEditor extends LitElement implements LovelaceCardEditor { - private _hass?: HomeAssistant; - private _lovelace?: LovelaceConfig; - private _stackCardEditor?; - private _config = {}; - - private _setEditorConfig(config: LovelaceCardConfig) { - // @ts-ignore - if (this._stackCardEditor) { - this._stackCardEditor.setConfig({ - type: 'vertical-stack', - cards: config.cards || [] - }); +import { LOG_EDITOR as LOG } from './utils'; + +export const editorFactory = (NAME: string) => { + class CombinedCardEditor extends LitElement implements LovelaceCardEditor { + private _hass?: HomeAssistant; + private _lovelace?: LovelaceConfig; + private _stackCardEditor?; + private _config = {}; + + private _setEditorConfig(config: LovelaceCardConfig) { + // @ts-ignore + if (this._stackCardEditor) { + this._stackCardEditor.setConfig({ + type: 'vertical-stack', + cards: config.cards || [] + }); + } } - } - setConfig(config: LovelaceCardConfig): void { - this._config = { - // I think this won't allow removing the hidden values - // ...this._config, - ...config - }; + setConfig(config: LovelaceCardConfig): void { + this._config = { + // I think this won't allow removing the hidden values + // ...this._config, + ...config + }; - this._setEditorConfig(this._config as LovelaceCardConfig); - } + this._setEditorConfig(this._config as LovelaceCardConfig); + } - configChanged(newConfig: LovelaceCardConfig): void { - const event = new Event('config-changed', { - bubbles: true, - composed: true - }); + configChanged(newConfig: LovelaceCardConfig): void { + const event = new Event('config-changed', { + bubbles: true, + composed: true + }); - // @ts-ignore - event.detail = { config: newConfig }; + // @ts-ignore + event.detail = { config: newConfig }; - this.dispatchEvent(event); - } + this.dispatchEvent(event); + } - protected render() { - LOG('render', this._stackCardEditor); + protected render() { + LOG('render', this._stackCardEditor); - if (this._hass) { - this._stackCardEditor.hass = this._hass; - } + if (this._hass) { + this._stackCardEditor.hass = this._hass; + } - if (this._lovelace) { - this._stackCardEditor.lovelace = this._lovelace; - } + if (this._lovelace) { + this._stackCardEditor.lovelace = this._lovelace; + } - this._stackCardEditor.addEventListener('config-changed', ev => { - ev.stopPropagation(); + this._stackCardEditor.addEventListener('config-changed', ev => { + ev.stopPropagation(); - this.configChanged({ - ...this._config, - ...ev.detail.config, - type: `custom:${NAME}` + this.configChanged({ + ...this._config, + ...ev.detail.config, + type: `custom:${NAME}` + }); }); - }); - return html`
${this._stackCardEditor}
`; - } + return html`
${this._stackCardEditor}
`; + } - set hass(hass: HomeAssistant) { - this._hass = hass; + set hass(hass: HomeAssistant) { + this._hass = hass; - if (this._stackCardEditor) { - this._stackCardEditor.hass = hass; + if (this._stackCardEditor) { + this._stackCardEditor.hass = hass; + } } - } - set lovelace(ll: LovelaceConfig) { - this._lovelace = ll; + set lovelace(ll: LovelaceConfig) { + this._lovelace = ll; - if (this._stackCardEditor) { - this._stackCardEditor.lovelace = ll; + if (this._stackCardEditor) { + this._stackCardEditor.lovelace = ll; + } } - } - set cardEditor(editor) { - this._stackCardEditor = editor; + set cardEditor(editor) { + this._stackCardEditor = editor; + } } -} -customElements.define(EDITOR_NAME, CombinedCardEditor); + return CombinedCardEditor; +}; + diff --git a/src/combined-card.ts b/src/combined-card.ts index 2b403e3..66b38ee 100644 --- a/src/combined-card.ts +++ b/src/combined-card.ts @@ -1,7 +1,18 @@ -import { css, CSSResultGroup, html, LitElement } from "lit"; -import { state } from "lit/decorators.js"; +import { css, CSSResultGroup, html, LitElement } from 'lit'; +import { state } from 'lit/decorators.js'; import { HomeAssistant, LovelaceCardConfig, LovelaceCard } from 'custom-card-helpers'; -import { NAME, EDITOR_NAME, HELPERS, LOG, loadStackEditor, sleep } from './utils'; +import { HELPERS, LOG, loadStackEditor, sleep } from './utils'; + +import { editorFactory } from "./combined-card-editor"; + +const NAME = 'combined-card'; +const EDITOR_NAME = `${NAME}-editor`; + +export const card = { + type: NAME, + name: "Catdad: Combined Card", + description: "Combine a stack of cards into a single seamless card", +}; const getRandomId = (): string => Math.random().toString(36).slice(2); @@ -229,3 +240,4 @@ class CombinedCard extends LitElement implements LovelaceCard { } customElements.define(NAME, CombinedCard); +customElements.define(EDITOR_NAME, editorFactory(NAME)); diff --git a/src/kiosk-card.ts b/src/kiosk-card.ts new file mode 100644 index 0000000..9a0f538 --- /dev/null +++ b/src/kiosk-card.ts @@ -0,0 +1,147 @@ +import { css, CSSResultGroup, html, LitElement } from "lit"; +import { state } from "lit/decorators.js"; +import { querySelectorDeep } from "query-selector-shadow-dom"; +import { HomeAssistant, LovelaceCardConfig, LovelaceCard } from 'custom-card-helpers'; +import { LOG } from './utils'; + +const NAME = 'kiosk-card'; +const EDITOR_NAME = `${NAME}-editor`; + +export const card = { + type: NAME, + name: 'Catdad: Kiosk Card', + description: 'Hide the navigation UI for the dashboard where this card is rendered' +}; + +class KioskCard extends LitElement implements LovelaceCard { + @state() private _config?: LovelaceCardConfig; + @state() private _editMode: boolean = false; + + private _hass?: HomeAssistant; + + set hass(hass: HomeAssistant) { + this._hass = hass; + } + + set editMode(editMode: boolean) { + this._editMode = editMode; + + if (editMode) { + this.disable(); + } else { + this.enable(); + } + } + + public async getCardSize(): Promise { + return 4; + } + + public setConfig(config: LovelaceCardConfig): void { + this._config = Object.assign({}, KioskCard.getStubConfig(), config); + } + + private enable(): void { + try { + const header = querySelectorDeep('ha-panel-lovelace .header'); + const view = querySelectorDeep('ha-panel-lovelace hui-view-container'); + const thisCard = querySelectorDeep('.catdad-kiosk-card'); + + // LOG('kiosk mode got elements:', { header, view, thisCard }); + + // when this card is not being rendered, it should not apply kiosk mode + if (!thisCard) { + return; + } + + if (!header || !view) { + throw new Error('could not find necessary elements to apply kiosk mode'); + } + + if (this._editMode) { + header.style.removeProperty('display'); + view.style.removeProperty('padding-top'); + } else { + header.style.display = 'none'; + view.style.paddingTop = '0px'; + } + } catch (e) { + LOG('failed to connect kiosk mode', e); + } + } + + private disable(): void { + try { + const header = querySelectorDeep('ha-panel-lovelace .header'); + const view = querySelectorDeep('ha-panel-lovelace hui-view-container'); + + // LOG('kiosk mode got elements:', { header, view }); + + if (!header || !view) { + throw new Error('could not find necessary elements to disconnect kiosk mode'); + } + + header.style.removeProperty('display'); + view.style.removeProperty('padding-top'); + } catch (e) { + LOG('failed to disconnect kiosk mode', e); + } + } + + connectedCallback(): void { + super.connectedCallback() + // LOG('Kiosk card connected', this._editMode); + this.enable(); + } + + disconnectedCallback(): void { + super.disconnectedCallback(); + // LOG('Kiosk card disconnected'); + this.disable(); + } + + protected render() { + const styles = [ + 'padding: var(--spacing, 12px)', + 'display: flex', + 'align-items: center', + 'justify-content: center', + ]; + + return html` + +
Kiosk mode card
+
+ `; + } + + static get styles(): CSSResultGroup { + return css` + ha-card { + overflow: hidden; + } + `; + } + + public static getConfigElement() { + return document.createElement(EDITOR_NAME); + } + + static getStubConfig() { + return { + type: `custom:${NAME}`, + }; + } +} + +class KioskCardEditor extends LitElement { + setConfig() {} + configChanged() {} + + render() { + return html`
This card has no options!
`; + } +} + +customElements.define(NAME, KioskCard); +customElements.define(EDITOR_NAME, KioskCardEditor); diff --git a/src/main.ts b/src/main.ts index 237afa1..f4b6270 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,12 +1,6 @@ -import { NAME } from './utils'; - -import "./combined-card"; -import "./combined-card-editor"; +import { card as combinedCard } from "./combined-card"; +import { card as kioskCard } from "./kiosk-card"; // Note: this is what adds the card to the UI card selector (window as any).customCards = (window as any).customCards || []; -(window as any).customCards.push({ - type: NAME, - name: "Combined Card", - description: "Combine a stack of cards into a single seamless card", -}); +(window as any).customCards.push(combinedCard, kioskCard);