diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..bc51442 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,4 @@ +github: [brunosabot] +patreon: brunosabot +buy_me_a_coffee: brunosabot1 +custom: ['https://paypal.me/BrunoSabot?country.x=FR&locale.x=fr_FR'] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..d1ab2b3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,49 @@ +--- +name: Bug report +about: Help me improving Streamline Card by reporting any issue you encounter +title: "" +labels: tracking issue, needs triage +assignees: brunosabot +--- + +For any feature request you can open a new discussion here: +https://github.com/brunosabot/streamline-card/discussions/categories/ideas + +For any question you can open a new discussion here: +https://github.com/brunosabot/streamline-card/discussions/categories/q-a + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: + +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**YAML** +If applicable, add any relevant YAML code. + +```yaml +Your code here +``` + +**Informations (please complete the following information):** + +- OS: [e.g. iOS] +- Browser/App: [e.g. chrome, safari] +- Bubble Card version: [e.g. 2.0.4] +- Home Assistant version: [e.g. 2024.6.4] + +**Additional context** +Add any other context about the problem here. + +Thank you! 🍻 diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..e5e6789 --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,19 @@ +name: Validation of the plugin + +on: + push: + pull_request: + schedule: + - cron: "0 0 * * *" + workflow_dispatch: + +jobs: + validate-plugin: + runs-on: "ubuntu-latest" + steps: + - name: Code Checkout + uses: "actions/checkout@v4" + - name: HACS Validation + uses: "hacs/action@main" + with: + category: "plugin" \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..caad6b0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +.env.*.local \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/.prettierrc @@ -0,0 +1 @@ +{} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6898d0e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Bruno Sabot + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..9459b93 --- /dev/null +++ b/README.md @@ -0,0 +1,140 @@ +# Streamline Card + +![readme-images-streamline-card](streamline-card.jpg) + +Streamline your Lovelace configuration with with a card template system. + +This card is for [Lovelace](https://www.home-assistant.io/lovelace) on [Home Assistant](https://www.home-assistant.io/). + +We all use multiple times the same block of configuration across our lovelace configuration and we don't want to change the same things in a hundred places across our configuration each time we want to modify something. + +`streamline-card` to the rescue! This card allows you to reuse multiple times the same configuration in your lovelace configuration to avoid repetition and supports variables and default values. + +`streamline-card` is an adaptation of `decluttering-card` by [@brunosabot](https://github.com/brunosabot) which is not maintained anymore. + +## Installation + +### With HACS (Recommended) + +This method allows you to get updates directly on the HACS main page + +1. If HACS is not installed yet, download it following the instructions on [https://hacs.xyz/docs/setup/download](https://hacs.xyz/docs/setup/download/) +2. Proceed to the HACS initial configuration following the instructions on [https://hacs.xyz/docs/configuration/basic](https://hacs.xyz/docs/configuration/basic) +3. On your sidebar go to `HACS` > `Frontend` +4. Click on the `+` button at the bottom right corner +5. Now search for `Streamline Card` and then click on the button at the bottom right corner to download it +6. Go back on your dashboard and click on the icon at the right top corner then on `Edit dashboard` +7. You can now click on `Add card` in the bottom right corner and search for `Streamline Card` + +If it's not working, try to clear your browser cache. + +### Without HACS + +1. Download these files: [streamline-card.js](https://raw.githubusercontent.com/brunosabot/streamline-card/main/dist/streamline-card.js) +2. Add these files to your `/www` folder +3. On your dashboard click on the icon at the right top corner then on `Edit dashboard` +4. Click again on that icon and then click on `Manage resources` +5. Click on `Add resource` +6. Copy and paste this: `/local/streamline-card.js?v=1` +7. Click on `JavaScript Module` then `Create` +8. Go back and refresh your page +9. You can now click on `Add card` in the bottom right corner and search for `streamline Card` +10. After any update of the file you will have to edit `/local/streamline-card.js?v=1` and change the version to any higher number + +If it's not working, just try to clear your browser cache.` + +## Configuration + +### Defining your templates + +First, you need to define your templates. + +The templates are defined in an object at the root of your lovelace configuration. This object needs to be named `streamline_templates`. + +This object needs to contains your templates declaration, each template has a name and can contain variables. A variable needs to be enclosed in double square brackets `[[variable_name]]`. It will later be replaced by a real value when you instantiate a card which uses this template. If a variable is alone on it's line, enclose it in single quotes: `'[[variable_name]]'`. + +You can also define default values for your variables in the `default` object. + +For a card: + +```yaml +streamline_templates: + + default: # This is optional + - : + - : + [...] + card: # This is where you put your card config (it can be a card embedding other cards) + type: custom:my-super-card + [...] +``` + +For a Picture-Element: + +```yaml +streamline_templates: + + default: # This is optional + - : + - : + [...] + element: # This is where you put your element config + type: icon + [...] +``` + +Example in your `lovelace-ui.yaml`: + +```yaml +resources: + - url: /local/streamline-card.js + type: module + +streamline_templates: + my_first_template: # This is the name of a template + default: + - icon: fire + card: + type: custom:button-card + name: "[[name]]" + icon: "mdi:[[icon]]" + + my_second_template: # This is the name of another template + card: + type: custom:vertical-stack-in-card + cards: + - type: horizontal-stack + cards: + - type: custom:button-card + entity: "[[entity_1]]" + - type: custom:button-card + entity: "[[entity_2]]" +``` + +### Using the card + +| Name | Type | Requirement | Description | +| --------- | ------ | ------------ | -------------------------------------------------------------- | +| type | string | **Required** | `custom:streamline-card` | +| template | object | **Required** | The template to use from `streamline_templates` | +| variables | list | **Optional** | List of variables and their value to replace in the `template` | + +Example which references the previous templates: + +```yaml +- type: custom:streamline-card + template: my_first_template + variables: + - name: Test Button + - icon: arrow-up + +- type: custom:streamline-card + template: my_first_template + variables: Default Icon Button + +- type: custom:streamline-card + template: my_second_template + variables: + - entity_1: switch.my_switch + - entity_2: light.my_light +``` diff --git a/dist/streamline-card.js b/dist/streamline-card.js new file mode 100644 index 0000000..7b3dad2 --- /dev/null +++ b/dist/streamline-card.js @@ -0,0 +1,124 @@ +var l = Object.defineProperty; +var h = (e, a, s) => a in e ? l(e, a, { enumerable: !0, configurable: !0, writable: !0, value: s }) : e[a] = s; +var n = (e, a, s) => h(e, typeof a != "symbol" ? a + "" : a, s); +function u(e, a) { + if (!e && !a.default) + return a.card; + let s = []; + e && (s = e.slice(0)), a.default && (s = s.concat(a.default)); + let i = a.card ? JSON.stringify(a.card) : JSON.stringify(a.element); + return s.forEach((t) => { + const r = Object.keys(t)[0], o = Object.values(t)[0]; + if (typeof o == "number" || typeof o == "boolean") { + const d = new RegExp(`"\\[\\[${r}\\]\\]"`, "gm"); + i = i.replace(d, o); + } + if (typeof o == "object") { + const d = new RegExp(`"\\[\\[${r}\\]\\]"`, "gm"), c = JSON.stringify(o); + i = i.replace(d, c); + } else { + const d = new RegExp(`\\[\\[${r}\\]\\]`, "gm"); + i = i.replace(d, o); + } + }), JSON.parse(i); +} +function p() { + let e = document.querySelector("hc-main"); + if (e = e && e.shadowRoot, e = e && e.querySelector("hc-lovelace"), e = e && e.shadowRoot, e = e && e.querySelector("hui-view"), e) { + const a = e.lovelace; + return a.current_view = e.___curView, a; + } + return null; +} +function f() { + let e = document.querySelector("home-assistant"); + if (e = e && e.shadowRoot, e = e && e.querySelector("home-assistant-main"), e = e && e.shadowRoot, e = e && e.querySelector("app-drawer-layout partial-panel-resolver, ha-drawer partial-panel-resolver"), e = e && e.shadowRoot || e, e = e && e.querySelector("ha-panel-lovelace"), e = e && e.shadowRoot, e = e && e.querySelector("hui-root"), e) { + const a = e.lovelace; + return a.current_view = e.___curView, a; + } + return null; +} +const _ = "0.0.1"; +(async function() { + const e = window.loadCardHelpers ? await window.loadCardHelpers() : void 0; + class a extends HTMLElement { + constructor() { + super(); + n(this, "_editMode", !1); + n(this, "_isConnected", !1); + n(this, "_config", {}); + n(this, "_hass", {}); + n(this, "_card"); + n(this, "_shadow"); + n(this, "_accessedProperties", /* @__PURE__ */ new Set()); + this._shadow = this.shadowRoot || this.attachShadow({ mode: "open" }); + } + updateCardHass() { + this._isConnected && this._card && this._hass && (this._card.hass = this._hass); + } + updateCardEditMode() { + this._isConnected && this._card && (this._card.editMode = this._editMode); + } + updateCardConfig() { + var t, r; + this._isConnected && this._card && this._config.card && ((r = (t = this._card).setConfig) == null || r.call(t, this._config.card)); + } + connectedCallback() { + this._isConnected = !0, this.updateCardConfig(), this.updateCardEditMode(), this.updateCardHass(); + } + disconnectedCallback() { + this._isConnected = !1; + } + set editMode(t) { + t !== this._editMode && (this._editMode = t, this.updateCardEditMode()); + } + set hass(t) { + this._hass = t, this.updateCardHass(); + } + parseConfig(t) { + const r = f() || p(); + if (!r.config && !r.config.streamline_templates) + throw new Error( + "The object streamline_templates doesn't exist in your main lovelace config." + ); + const o = r.config.streamline_templates[this._config.template]; + if (o) + if (o.card || o.element) { + if (o.card && o.element) + throw new Error("You can define a card and an element in the template"); + } else throw new Error( + "You should define either a card or an element in the template" + ); + else throw new Error( + `The template "${t.template}" doesn't exist in streamline_templates` + ); + this._config = u(t.variables, o); + } + async setConfig(t) { + if (this._config = t, this.parseConfig(t), this._card === void 0) { + if (this._config.type === void 0) + throw new Error("[Streamline Card] You need to define a type"); + this._card = e.createCardElement(this._config), this._shadow.appendChild(this._card); + } + this.updateCardConfig(); + } + getCardSize() { + var t, r; + return ((r = (t = this._card) == null ? void 0 : t.getCardSize) == null ? void 0 : r.call(t)) ?? 1; + } + getLayoutOptions() { + var t, r; + return (r = (t = this._card) == null ? void 0 : t.getLayoutOptions) == null ? void 0 : r.call(t); + } + } + customElements.define("streamline-card", a), window.customCards = window.customCards || [], window.customCards.push({ + type: "streamline-card", + name: "Streamline Card", + preview: !1, + description: "A config simplifier." + }), console.info( + `%c Streamline Card %c ${_}`, + "background-color:#c2b280;color:#242424;padding:4px 4px 4px 8px;border-radius:20px 0 0 20px;font-family:sans-serif;", + "background-color:#5297ff;color:#242424;padding:4px 8px 4px 4px;border-radius:0 20px 20px 0;font-family:sans-serif;" + ); +})(); diff --git a/hacs.json b/hacs.json new file mode 100644 index 0000000..883b12b --- /dev/null +++ b/hacs.json @@ -0,0 +1,7 @@ +{ + "name": "Streamline Card", + "content_in_root": false, + "render_readme": true, + "filename": "streamline-card.js", + "homeassistant": "2023.9.0" +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..66971dc --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "streamline-card", + "version": "0.0.1", + "description": "", + "type": "module", + "main": "src/streamline-card.js", + "scripts": { + "dev": "vite build --watch --mode development", + "build": "vite build", + "preview": "vite preview" + }, + "author": "", + "license": "MIT", + "dependencies": { + "vite": "^5.3.5" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..ca7532d --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,487 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + vite: + specifier: ^5.3.5 + version: 5.3.5 + +packages: + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@rollup/rollup-android-arm-eabi@4.19.2': + resolution: {integrity: sha512-OHflWINKtoCFSpm/WmuQaWW4jeX+3Qt3XQDepkkiFTsoxFc5BpF3Z5aDxFZgBqRjO6ATP5+b1iilp4kGIZVWlA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.19.2': + resolution: {integrity: sha512-k0OC/b14rNzMLDOE6QMBCjDRm3fQOHAL8Ldc9bxEWvMo4Ty9RY6rWmGetNTWhPo+/+FNd1lsQYRd0/1OSix36A==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.19.2': + resolution: {integrity: sha512-IIARRgWCNWMTeQH+kr/gFTHJccKzwEaI0YSvtqkEBPj7AshElFq89TyreKNFAGh5frLfDCbodnq+Ye3dqGKPBw==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.19.2': + resolution: {integrity: sha512-52udDMFDv54BTAdnw+KXNF45QCvcJOcYGl3vQkp4vARyrcdI/cXH8VXTEv/8QWfd6Fru8QQuw1b2uNersXOL0g==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-linux-arm-gnueabihf@4.19.2': + resolution: {integrity: sha512-r+SI2t8srMPYZeoa1w0o/AfoVt9akI1ihgazGYPQGRilVAkuzMGiTtexNZkrPkQsyFrvqq/ni8f3zOnHw4hUbA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.19.2': + resolution: {integrity: sha512-+tYiL4QVjtI3KliKBGtUU7yhw0GMcJJuB9mLTCEauHEsqfk49gtUBXGtGP3h1LW8MbaTY6rSFIQV1XOBps1gBA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.19.2': + resolution: {integrity: sha512-OR5DcvZiYN75mXDNQQxlQPTv4D+uNCUsmSCSY2FolLf9W5I4DSoJyg7z9Ea3TjKfhPSGgMJiey1aWvlWuBzMtg==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.19.2': + resolution: {integrity: sha512-Hw3jSfWdUSauEYFBSFIte6I8m6jOj+3vifLg8EU3lreWulAUpch4JBjDMtlKosrBzkr0kwKgL9iCfjA8L3geoA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.19.2': + resolution: {integrity: sha512-rhjvoPBhBwVnJRq/+hi2Q3EMiVF538/o9dBuj9TVLclo9DuONqt5xfWSaE6MYiFKpo/lFPJ/iSI72rYWw5Hc7w==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.19.2': + resolution: {integrity: sha512-EAz6vjPwHHs2qOCnpQkw4xs14XJq84I81sDRGPEjKPFVPBw7fwvtwhVjcZR6SLydCv8zNK8YGFblKWd/vRmP8g==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.19.2': + resolution: {integrity: sha512-IJSUX1xb8k/zN9j2I7B5Re6B0NNJDJ1+soezjNojhT8DEVeDNptq2jgycCOpRhyGj0+xBn7Cq+PK7Q+nd2hxLA==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.19.2': + resolution: {integrity: sha512-OgaToJ8jSxTpgGkZSkwKE+JQGihdcaqnyHEFOSAU45utQ+yLruE1dkonB2SDI8t375wOKgNn8pQvaWY9kPzxDQ==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.19.2': + resolution: {integrity: sha512-5V3mPpWkB066XZZBgSd1lwozBk7tmOkKtquyCJ6T4LN3mzKENXyBwWNQn8d0Ci81hvlBw5RoFgleVpL6aScLYg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.19.2': + resolution: {integrity: sha512-ayVstadfLeeXI9zUPiKRVT8qF55hm7hKa+0N1V6Vj+OTNFfKSoUxyZvzVvgtBxqSb5URQ8sK6fhwxr9/MLmxdA==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.19.2': + resolution: {integrity: sha512-Mda7iG4fOLHNsPqjWSjANvNZYoW034yxgrndof0DwCy0D3FvTjeNo+HGE6oGWgvcLZNLlcp0hLEFcRs+UGsMLg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.19.2': + resolution: {integrity: sha512-DPi0ubYhSow/00YqmG1jWm3qt1F8aXziHc/UNy8bo9cpCacqhuWu+iSq/fp2SyEQK7iYTZ60fBU9cat3MXTjIQ==} + cpu: [x64] + os: [win32] + + '@types/estree@1.0.5': + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + picocolors@1.0.1: + resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + + postcss@8.4.40: + resolution: {integrity: sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==} + engines: {node: ^10 || ^12 || >=14} + + rollup@4.19.2: + resolution: {integrity: sha512-6/jgnN1svF9PjNYJ4ya3l+cqutg49vOZ4rVgsDKxdl+5gpGPnByFXWGyfH9YGx9i3nfBwSu1Iyu6vGwFFA0BdQ==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + source-map-js@1.2.0: + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + engines: {node: '>=0.10.0'} + + vite@5.3.5: + resolution: {integrity: sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + +snapshots: + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@rollup/rollup-android-arm-eabi@4.19.2': + optional: true + + '@rollup/rollup-android-arm64@4.19.2': + optional: true + + '@rollup/rollup-darwin-arm64@4.19.2': + optional: true + + '@rollup/rollup-darwin-x64@4.19.2': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.19.2': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.19.2': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.19.2': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.19.2': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.19.2': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.19.2': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.19.2': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.19.2': + optional: true + + '@rollup/rollup-linux-x64-musl@4.19.2': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.19.2': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.19.2': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.19.2': + optional: true + + '@types/estree@1.0.5': {} + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + fsevents@2.3.3: + optional: true + + nanoid@3.3.7: {} + + picocolors@1.0.1: {} + + postcss@8.4.40: + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.1 + source-map-js: 1.2.0 + + rollup@4.19.2: + dependencies: + '@types/estree': 1.0.5 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.19.2 + '@rollup/rollup-android-arm64': 4.19.2 + '@rollup/rollup-darwin-arm64': 4.19.2 + '@rollup/rollup-darwin-x64': 4.19.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.19.2 + '@rollup/rollup-linux-arm-musleabihf': 4.19.2 + '@rollup/rollup-linux-arm64-gnu': 4.19.2 + '@rollup/rollup-linux-arm64-musl': 4.19.2 + '@rollup/rollup-linux-powerpc64le-gnu': 4.19.2 + '@rollup/rollup-linux-riscv64-gnu': 4.19.2 + '@rollup/rollup-linux-s390x-gnu': 4.19.2 + '@rollup/rollup-linux-x64-gnu': 4.19.2 + '@rollup/rollup-linux-x64-musl': 4.19.2 + '@rollup/rollup-win32-arm64-msvc': 4.19.2 + '@rollup/rollup-win32-ia32-msvc': 4.19.2 + '@rollup/rollup-win32-x64-msvc': 4.19.2 + fsevents: 2.3.3 + + source-map-js@1.2.0: {} + + vite@5.3.5: + dependencies: + esbuild: 0.21.5 + postcss: 8.4.40 + rollup: 4.19.2 + optionalDependencies: + fsevents: 2.3.3 diff --git a/src/deepReplace-helper.js b/src/deepReplace-helper.js new file mode 100644 index 0000000..8eb795e --- /dev/null +++ b/src/deepReplace-helper.js @@ -0,0 +1,30 @@ +export default function deepReplace(variables, templateConfig) { + if (!variables && !templateConfig.default) { + return templateConfig.card; + } + let variableArray = []; + if (variables) { + variableArray = variables.slice(0); + } + if (templateConfig.default) { + variableArray = variableArray.concat(templateConfig.default); + } + let jsonConfig = templateConfig.card ? JSON.stringify(templateConfig.card) : JSON.stringify(templateConfig.element); + variableArray.forEach(variable => { + const key = Object.keys(variable)[0]; + const value = Object.values(variable)[0]; + if (typeof value === 'number' || typeof value === 'boolean') { + const rxp2 = new RegExp(`"\\[\\[${key}\\]\\]"`, 'gm'); + jsonConfig = jsonConfig.replace(rxp2, value); + } + if (typeof value === 'object') { + const rxp2 = new RegExp(`"\\[\\[${key}\\]\\]"`, 'gm'); + const valueString = JSON.stringify(value); + jsonConfig = jsonConfig.replace(rxp2, valueString); + } else { + const rxp = new RegExp(`\\[\\[${key}\\]\\]`, 'gm'); + jsonConfig = jsonConfig.replace(rxp, value); + } + }); + return JSON.parse(jsonConfig); +} diff --git a/src/getLovelace.helper.js b/src/getLovelace.helper.js new file mode 100644 index 0000000..f038bec --- /dev/null +++ b/src/getLovelace.helper.js @@ -0,0 +1,35 @@ +export function getLovelaceCast() { + let root = document.querySelector('hc-main'); + root = root && root.shadowRoot; + root = root && root.querySelector('hc-lovelace'); + root = root && root.shadowRoot; + root = root && root.querySelector('hui-view'); + + if (root) { + const ll = root.lovelace; + ll.current_view = root.___curView; + return ll; + } + + return null; +} + +export function getLovelace() { + let root = document.querySelector('home-assistant'); + root = root && root.shadowRoot; + root = root && root.querySelector('home-assistant-main'); + root = root && root.shadowRoot; + root = root && root.querySelector('app-drawer-layout partial-panel-resolver, ha-drawer partial-panel-resolver'); + root = (root && root.shadowRoot) || root; + root = root && root.querySelector('ha-panel-lovelace'); + root = root && root.shadowRoot; + root = root && root.querySelector('hui-root'); + + if (root) { + const ll = root.lovelace; + ll.current_view = root.___curView; + return ll; + } + + return null; +} diff --git a/src/streamline-card.js b/src/streamline-card.js new file mode 100644 index 0000000..23fe95a --- /dev/null +++ b/src/streamline-card.js @@ -0,0 +1,133 @@ +import deepReplace from "./deepReplace-helper"; +import { getLovelace, getLovelaceCast } from "./getLovelace.helper"; +import { version } from "../package.json"; + +export {}; + +(async function () { + const HELPERS = window.loadCardHelpers + ? await window.loadCardHelpers() + : undefined; + + class StreamlineCard extends HTMLElement { + _editMode = false; + _isConnected = false; + _config = {}; + _hass = {}; + _card; + _shadow; + _accessedProperties = new Set(); + + constructor() { + super(); + this._shadow = this.shadowRoot || this.attachShadow({ mode: "open" }); + } + + updateCardHass() { + if (this._isConnected && this._card && this._hass) { + this._card.hass = this._hass; + } + } + + updateCardEditMode() { + if (this._isConnected && this._card) { + this._card.editMode = this._editMode; + } + } + + updateCardConfig() { + if (this._isConnected && this._card && this._config.card) { + this._card.setConfig?.(this._config.card); + } + } + + connectedCallback() { + this._isConnected = true; + + this.updateCardConfig(); + this.updateCardEditMode(); + this.updateCardHass(); + } + + disconnectedCallback() { + this._isConnected = false; + } + + set editMode(editMode) { + if (editMode !== this._editMode) { + this._editMode = editMode; + this.updateCardEditMode(); + } + } + + set hass(hass) { + this._hass = hass; + this.updateCardHass(); + } + + parseConfig(config) { + const lovelace = getLovelace() || getLovelaceCast(); + if (!lovelace.config && !lovelace.config.streamline_templates) { + throw new Error( + "The object streamline_templates doesn't exist in your main lovelace config." + ); + } + + const templateConfig = + lovelace.config.streamline_templates[this._config.template]; + if (!templateConfig) { + throw new Error( + `The template "${config.template}" doesn't exist in streamline_templates` + ); + } else if (!(templateConfig.card || templateConfig.element)) { + throw new Error( + "You should define either a card or an element in the template" + ); + } else if (templateConfig.card && templateConfig.element) { + throw new Error("You can define a card and an element in the template"); + } + + this._config = deepReplace(config.variables, templateConfig); + } + + async setConfig(config) { + this._config = config; + + this.parseConfig(config); + + if (this._card === undefined) { + if (this._config.type === undefined) { + throw new Error("[Streamline Card] You need to define a type"); + } + this._card = HELPERS.createCardElement(this._config); + this._shadow.appendChild(this._card); + } + + this.updateCardConfig(); + } + + getCardSize() { + return this._card?.getCardSize?.() ?? 1; + } + + getLayoutOptions() { + return this._card?.getLayoutOptions?.(); + } + } + + customElements.define("streamline-card", StreamlineCard); + + window.customCards = window.customCards || []; + window.customCards.push({ + type: "streamline-card", + name: "Streamline Card", + preview: false, + description: "A config simplifier.", + }); + + console.info( + `%c Streamline Card %c ${version}`, + "background-color:#c2b280;color:#242424;padding:4px 4px 4px 8px;border-radius:20px 0 0 20px;font-family:sans-serif;", + "background-color:#5297ff;color:#242424;padding:4px 8px 4px 4px;border-radius:0 20px 20px 0;font-family:sans-serif;" + ); +})(); diff --git a/streamline-card.jpg b/streamline-card.jpg new file mode 100644 index 0000000..0b06de6 Binary files /dev/null and b/streamline-card.jpg differ diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..012ea76 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,17 @@ +import path from "path"; +import { defineConfig, loadEnv } from "vite"; + +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, process.cwd(), ""); + const targetDirectory = path.resolve(env.TARGET_DIRECTORY || "dist"); + + return { + build: { + outDir: path.resolve(targetDirectory), + lib: { + entry: "src/streamline-card.js", + formats: ["es"], + }, + }, + }; +});