diff --git a/README.md b/README.md index 99906ee..c294293 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,8 @@ resources: type: custom:state-switch entity: default: +transition: +transition_time: states: : @@ -41,6 +43,8 @@ If the state of `` doesn't match any ``, the `` for the `` State to use as default fallback - `` The state to match - `` Lovelace card configuration +- `` Animated transition to use (`slide-left`, `slide-right`, `swap-left`, `swap_right` or `flip`). Default: `none` +- `` The time for an animated transition in ms. Default: 500 ## State matching @@ -205,6 +209,12 @@ states: content: "Really small" ``` +## Transitions +The switch from one card to another can be animated by setting the `` option. +The speed of the transition is set by `` (milliseconds). Note that some animations do two things, and thus take two times `` to complete. + +![jVbI15cMvT](https://user-images.githubusercontent.com/1299821/70644405-396c3200-1c43-11ea-95cb-c6ffa0b818f8.gif) + ## A few tips - To replace more than one card at a time, use e.g. [`vertical-stack`](https://www.home-assistant.io/lovelace/vertical-stack/), [`horizontal-stack`](https://www.home-assistant.io/lovelace/horizontal-stack/) or [`layout-card`](https://github.com/thomasloven/lovelace-layout-card). diff --git a/src/main.js b/src/main.js index ec3077c..50471dd 100644 --- a/src/main.js +++ b/src/main.js @@ -1,7 +1,8 @@ -import { LitElement, html } from "card-tools/src/lit-element"; +import { LitElement, html, css } from "card-tools/src/lit-element"; import { hass } from "card-tools/src/hass"; import { createCard } from "card-tools/src/lovelace-element"; import { deviceID } from "card-tools/src/deviceID"; +import {fireEvent} from "card-tools/src/event.js"; class StateSwitch extends LitElement { @@ -72,13 +73,35 @@ class StateSwitch extends LitElement { if(!changedProperties.has("state")) { this.update_state(); + } else { + const oldState = changedProperties.get("state"); + if(this.cards[oldState]) { + this.cards[oldState].classList.remove("visible"); + this.cards[oldState].classList.add("out"); + window.setTimeout(() => { + this.cards[oldState].classList.remove("out"); + }, this._config.transition_time || 500); + } + if(this.cards[this.state]) { + this.cards[this.state].classList.add("visible"); + } } } render() { return html` -
- ${this.cards[this.state]} +
+ ${Object.keys(this.cards).map((k) => + html` + ${this.cards[k]} + `)}
`; } @@ -92,6 +115,130 @@ class StateSwitch extends LitElement { return sz; } + static get styles() { + return css` + :host { + perspective: 1000px; + } + #root { + display: grid; + } + #root * { + display: none; + grid-column: 1; + grid-row: 1; + } + #root .visible { + display: block; + } + + + #root.slide-right *, + #root.slide-left * { + display: block; + opacity: 0; + height: 0; + transition-property: transform; + transition-timing-function: linear; + transition-duration: inherit; + transform: translate(-110%); + } + #root.slide-left * { + transform: translate(110%); + } + #root.slide-right .visible, + #root.slide-left .visible { + opacity: 1; + height: auto; + transform: translate(0%); + } + #root.slide-right .out, + #root.slide-left .out { + opacity: 1; + height: auto; + transform: translate(110%); + } + #root.slide-left .out { + transform: translate(-110%); + } + + + #root.swap-right *, + #root.swap-left * { + display: block; + opacity: 0; + height: 0; + transition-property: transform; + transition-timing-function: linear; + transition-duration: inherit; + transform: translate(110%); + } + #root.swap-left *{ + transform: translate(-110%); + } + #root.swap-right .visible, + #root.swap-left .visible { + opacity: 1; + height: auto; + transition-delay: inherit; + transform: translate(0%); + } + #root.swap-right .out, + #root.swap-left .out { + opacity: 1; + height: auto; + } + + + #root.flip { + width: 100%; + height: 100%; + position: relative; + + } + #root.flip * { + display: block; + opacity: 0; + height: 0; + transform: rotateY(-180deg); + transition-property: transform; + transition-timing-function: linear; + transition-duration: inherit; + transform-style: preserve-3d; + backface-visibility: hidden; + z-index: 100; + } + #root.flip .visible { + opacity: 1; + height: auto; + backface-visibility: hidden; + transform: rotateY(0deg); + } + #root.flip .out { + opacity: 1; + height: auto; + transform: rotateY(180deg); + } + `; + } } customElements.define("state-switch", StateSwitch); + +// Monkey patch hui-view to avoid scroll bars in columns +customElements.whenDefined("hui-view").then( () => { +const HuiView = customElements.get("hui-view").prototype; +const oldRenderStyles = HuiView.renderStyles; +HuiView.renderStyles = function() { + let original = oldRenderStyles(); + original.strings = [original.strings[0] + ` + + `]; + return original; +} +fireEvent('ll-rebuild', {}); +}); diff --git a/state-switch.js b/state-switch.js index 95a1cc6..49463ed 100644 --- a/state-switch.js +++ b/state-switch.js @@ -1,5 +1,117 @@ -!function(e){var t={};function s(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,s),o.l=!0,o.exports}s.m=e,s.c=t,s.d=function(e,t,r){s.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},s.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},s.t=function(e,t){if(1&t&&(e=s(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(s.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)s.d(r,o,function(t){return e[t]}.bind(null,o));return r},s.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return s.d(t,"a",t),t},s.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},s.p="",s(s.s=0)}([function(e,t,s){"use strict";s.r(t);const r=customElements.get("home-assistant-main")?Object.getPrototypeOf(customElements.get("home-assistant-main")):Object.getPrototypeOf(customElements.get("hui-view")),o=r.prototype.html;r.prototype.css;function a(){return document.querySelector("home-assistant").hass}const i="custom:";function n(e,t){const s=document.createElement("hui-error-card");return s.setConfig({type:"error",error:e,origConfig:t}),s}function c(e,t){if(!t||"object"!=typeof t||!t.type)return n(`No ${e} type configured`,t);let s=t.type;if(s=s.startsWith(i)?s.substr(i.length):`hui-${s}-${e}`,customElements.get(s))return function(e,t){const s=document.createElement(e);try{s.setConfig(t)}catch(e){return n(e,t)}return s}(s,t);const r=n(`Custom element doesn't exist: ${s}.`,t);r.style.display="None";const o=setTimeout(()=>{r.style.display=""},2e3);return customElements.whenDefined(s).then(()=>{clearTimeout(o),function(e,t,s=null){if((e=new Event(e,{bubbles:!0,cancelable:!1,composed:!0})).detail=t||{},s)s.dispatchEvent(e);else{var r=document.querySelector("home-assistant");(r=(r=(r=(r=(r=(r=(r=(r=(r=(r=(r=r&&r.shadowRoot)&&r.querySelector("home-assistant-main"))&&r.shadowRoot)&&r.querySelector("app-drawer-layout partial-panel-resolver"))&&r.shadowRoot||r)&&r.querySelector("ha-panel-lovelace"))&&r.shadowRoot)&&r.querySelector("hui-root"))&&r.shadowRoot)&&r.querySelector("ha-app-layout #view"))&&r.firstElementChild)&&r.dispatchEvent(e)}}("ll-rebuild",{},r)}),r}function u(e){return c("card",e)}let d=function(){if(window.fully&&"function"==typeof fully.getDeviceId)return fully.getDeviceId();if(!localStorage["lovelace-player-device-id"]){const e=()=>Math.floor(1e5*(1+Math.random())).toString(16).substring(1);localStorage["lovelace-player-device-id"]=`${e()}${e()}-${e()}${e()}`}return localStorage["lovelace-player-device-id"]}();customElements.define("state-switch",class extends r{static get properties(){return{hass:{},state:{}}}setConfig(e){this._config=e,this.state=void 0,this.cards={};for(let t in e.states)this.cards[t]=u(e.states[t]),this.cards[t].hass=a();if("hash"===e.entity&&window.addEventListener("location-changed",()=>this.updated(new Map)),"mediaquery"===e.entity)for(const e in this.cards)window.matchMedia(e).addEventListener("change",this.update_state.bind(this))}update_state(){let e=void 0;switch(this._config.entity){case"user":e=this.hass&&this.hass.user&&this.hass.user.name||void 0;break;case"group":e=this.hass&&this.hass.user&&this.hass.user.is_admin?"admin":"user";case"deviceID":case"browser":e=d;break;case"hash":e=location.hash.substr(1);break;case"mediaquery":for(const t in this.cards)if(window.matchMedia(t).matches){e=t;break}break;default:e=this.hass.states[this._config.entity],e=e?e.state:void 0}void 0!==e&&this.cards.hasOwnProperty(e)||(e=this._config.default),this.state=e}updated(e){if(e.has("hass"))for(let e in this.cards)this.cards[e].hass=this.hass;e.has("state")||this.update_state()}render(){return o` -
- ${this.cards[this.state]} +!function(t){var e={};function i(s){if(e[s])return e[s].exports;var r=e[s]={i:s,l:!1,exports:{}};return t[s].call(r.exports,r,r.exports,i),r.l=!0,r.exports}i.m=t,i.c=e,i.d=function(t,e,s){i.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:s})},i.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},i.t=function(t,e){if(1&e&&(t=i(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var s=Object.create(null);if(i.r(s),Object.defineProperty(s,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var r in t)i.d(s,r,function(e){return t[e]}.bind(null,r));return s},i.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return i.d(e,"a",e),e},i.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},i.p="",i(i.s=0)}([function(t,e,i){"use strict";i.r(e);const s=customElements.get("home-assistant-main")?Object.getPrototypeOf(customElements.get("home-assistant-main")):Object.getPrototypeOf(customElements.get("hui-view")),r=s.prototype.html,o=s.prototype.css;function a(){return document.querySelector("home-assistant").hass}function n(t,e,i=null){if((t=new Event(t,{bubbles:!0,cancelable:!1,composed:!0})).detail=e||{},i)i.dispatchEvent(t);else{var s=document.querySelector("home-assistant");(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=s&&s.shadowRoot)&&s.querySelector("home-assistant-main"))&&s.shadowRoot)&&s.querySelector("app-drawer-layout partial-panel-resolver"))&&s.shadowRoot||s)&&s.querySelector("ha-panel-lovelace"))&&s.shadowRoot)&&s.querySelector("hui-root"))&&s.shadowRoot)&&s.querySelector("ha-app-layout #view"))&&s.firstElementChild)&&s.dispatchEvent(t)}}const l="custom:";function c(t,e){const i=document.createElement("hui-error-card");return i.setConfig({type:"error",error:t,origConfig:e}),i}function d(t,e){if(!e||"object"!=typeof e||!e.type)return c(`No ${t} type configured`,e);let i=e.type;if(i=i.startsWith(l)?i.substr(l.length):`hui-${i}-${t}`,customElements.get(i))return function(t,e){const i=document.createElement(t);try{i.setConfig(e)}catch(t){return c(t,e)}return i}(i,e);const s=c(`Custom element doesn't exist: ${i}.`,e);s.style.display="None";const r=setTimeout(()=>{s.style.display=""},2e3);return customElements.whenDefined(i).then(()=>{clearTimeout(r),n("ll-rebuild",{},s)}),s}function u(t){return d("card",t)}let h=function(){if(window.fully&&"function"==typeof fully.getDeviceId)return fully.getDeviceId();if(!localStorage["lovelace-player-device-id"]){const t=()=>Math.floor(1e5*(1+Math.random())).toString(16).substring(1);localStorage["lovelace-player-device-id"]=`${t()}${t()}-${t()}${t()}`}return localStorage["lovelace-player-device-id"]}();customElements.define("state-switch",class extends s{static get properties(){return{hass:{},state:{}}}setConfig(t){this._config=t,this.state=void 0,this.cards={};for(let e in t.states)this.cards[e]=u(t.states[e]),this.cards[e].hass=a();if("hash"===t.entity&&window.addEventListener("location-changed",()=>this.updated(new Map)),"mediaquery"===t.entity)for(const t in this.cards)window.matchMedia(t).addEventListener("change",this.update_state.bind(this))}update_state(){let t=void 0;switch(this._config.entity){case"user":t=this.hass&&this.hass.user&&this.hass.user.name||void 0;break;case"group":t=this.hass&&this.hass.user&&this.hass.user.is_admin?"admin":"user";case"deviceID":case"browser":t=h;break;case"hash":t=location.hash.substr(1);break;case"mediaquery":for(const e in this.cards)if(window.matchMedia(e).matches){t=e;break}break;default:t=this.hass.states[this._config.entity],t=t?t.state:void 0}void 0!==t&&this.cards.hasOwnProperty(t)||(t=this._config.default),this.state=t}updated(t){if(t.has("hass"))for(let t in this.cards)this.cards[t].hass=this.hass;if(t.has("state")){const e=t.get("state");this.cards[e]&&(this.cards[e].classList.remove("visible"),this.cards[e].classList.add("out"),window.setTimeout(()=>{this.cards[e].classList.remove("out")},this._config.transition_time||500)),this.cards[this.state]&&this.cards[this.state].classList.add("visible")}else this.update_state()}render(){return r` +
+ ${Object.keys(this.cards).map(t=>r` + ${this.cards[t]} + `)}
- `}getCardSize(){let e=1;for(let t in this.cards)this.cards[t]&&this.cards[t].getCardSize&&(e=Math.max(e,this.cards[t].getCardSize()));return e}})}]); \ No newline at end of file + `}getCardSize(){let t=1;for(let e in this.cards)this.cards[e]&&this.cards[e].getCardSize&&(t=Math.max(t,this.cards[e].getCardSize()));return t}static get styles(){return o` + :host { + perspective: 1000px; + } + #root { + display: grid; + } + #root * { + display: none; + grid-column: 1; + grid-row: 1; + } + #root .visible { + display: block; + } + + + #root.slide-right *, + #root.slide-left * { + display: block; + opacity: 0; + height: 0; + transition-property: transform; + transition-timing-function: linear; + transition-duration: inherit; + transform: translate(-110%); + } + #root.slide-left * { + transform: translate(110%); + } + #root.slide-right .visible, + #root.slide-left .visible { + opacity: 1; + height: auto; + transform: translate(0%); + } + #root.slide-right .out, + #root.slide-left .out { + opacity: 1; + height: auto; + transform: translate(110%); + } + #root.slide-left .out { + transform: translate(-110%); + } + + + #root.swap-right *, + #root.swap-left * { + display: block; + opacity: 0; + height: 0; + transition-property: transform; + transition-timing-function: linear; + transition-duration: inherit; + transform: translate(110%); + } + #root.swap-left *{ + transform: translate(-110%); + } + #root.swap-right .visible, + #root.swap-left .visible { + opacity: 1; + height: auto; + transition-delay: inherit; + transform: translate(0%); + } + #root.swap-right .out, + #root.swap-left .out { + opacity: 1; + height: auto; + } + + + #root.flip { + width: 100%; + height: 100%; + position: relative; + + } + #root.flip * { + display: block; + opacity: 0; + height: 0; + transform: rotateY(-180deg); + transition-property: transform; + transition-timing-function: linear; + transition-duration: inherit; + transform-style: preserve-3d; + backface-visibility: hidden; + z-index: 100; + } + #root.flip .visible { + opacity: 1; + height: auto; + backface-visibility: hidden; + transform: rotateY(0deg); + } + #root.flip .out { + opacity: 1; + height: auto; + transform: rotateY(180deg); + } + `}}),customElements.whenDefined("hui-view").then(()=>{const t=customElements.get("hui-view").prototype,e=t.renderStyles;t.renderStyles=function(){let t=e();return t.strings=[t.strings[0]+"\n \n "],t},n("ll-rebuild",{})})}]); \ No newline at end of file