From f5dbea80ce7789f3e9df62e482c6850cf9901340 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Lov=C3=A9n?= Date: Sun, 14 Mar 2021 13:49:52 +0000 Subject: [PATCH] Various fixes. Update README --- README.md | 468 +++++++++++++--------------- layout-card.js | 6 +- src/layout-card.ts | 4 +- src/layouts/grid.ts | 38 ++- src/patches/hui-dialog-edit-view.ts | 2 +- 5 files changed, 253 insertions(+), 265 deletions(-) diff --git a/README.md b/README.md index 9df0ede..912444d 100644 --- a/README.md +++ b/README.md @@ -7,332 +7,286 @@ Get more control over the placement of lovelace cards For installation instructions [see this guide](https://github.com/thomasloven/hass-config/wiki/Lovelace-Plugins). -Install `layout-card.js` as a `module`. +## Quick Start -```yaml -resources: - - url: /local/layout-card.js - type: module -``` +- Go to one of your lovelace views and select "Edit Dashboard" +- Click the pencil symbol next to the view name to open up the view properties +- Go to the new "Layout" tab +- Select "Masonry" layout from the "Layout type" dropdown list +- Click "Save" -## Usage +Hopefully, you should see no difference at all now. -```yaml -type: custom:layout-card -layout: -min_height: -min_columns: -max_columns: -column_num: -column_width: -max_width: -min_width: -sidebar_column: -flex_grow: -gridcols: -gridrows: -gridgap: -gridplace: -justify_content: -rtl: -cards: - -card_options: - -``` +- Open up the view properties again and go to the "Layout" tab. +- Enter the following in the "Layout Options" box: + ```yaml + width: 300 + max_cols: 10 + ``` +- Click Save -## Options -- `` **Required** A list of lovelace cards to display. -- `` are options that are applied to all cards. -- `` The layout method to use. `auto`, `vertical`, `horizontal` or `grid`. See below. Default: `auto`. -- `` The minimum length of a column in `auto` layout. Default: `5`. -- `` The minimum number of columns to use. Default: `1`. -- `` The maximum number of columns to use. Default: `100`. -- `` Shorthand to set both `min_columns>` and ``to the same value. Try this first. -- `` Width of columns. Default: `300px`. -- ``, ``, `` Set the `max-width`, `min-width` and `flex-grow` CSS properties of the columns manually. Default: `column_width or 500px`, `undefined`, `undefined`. -- `` is used to mimic the default behavior of lovelace. See below. -- ``, ``, ``, `` Set the `grid-template-rows`, `grid-template-columns`, `grid-gap` and `place-items` CSS properties when using `layout: grid`. -- `` Set the `justify-content` CSS property of the column container. Default: `center`. +You should now have more, narrower, collumns of cards in your view. -## Layouts +![Quick Start](https://user-images.githubusercontent.com/1299821/111066590-11abef80-84c0-11eb-809b-2843fd8610d8.gif) + + +## Usage + +### View Layouts -The basic concept of this card is that it takes a number of other cards, and places them in the browser window, just like lovelace does normally, but allowing you a bit more control. +Layout-card adds four new view layout to lovelace. +- Masonry (`custom:masonry-layout`) +- Horizontal (`custom:horizontal-layout`) +- Vertical (`custom:vertical-layout`) +- Grid (`custom:grid-layout`) -Since `layout-card` is a card in itself its area of effect will be limited to the width of a card, and thus you will (almost) always want to use it in [panel mode](https://www.home-assistant.io/lovelace/views/#panel-mode): +The difference between the types of layout is described below. + +Those can be selected either via the GUI as in the Quick Start above, or in the lovelace configuration by setting `type` (and optionally `layout`): ```yaml views: - - title: My view - panel: true + - title: Home + type: custom:masonry-layout + layout: + width: 300 + max_cols: 10 cards: - - type: custom:layout-card - ... + ... ``` -### `auto` layout - -The auto layout works in the same way as the default lovelace layout. +### Layout-card -It follows a simple process. -- A number of columns are prepared based on the screen width and ``. -- If the sidebar is opened, the number of columns is decreased by 1. (**This is not done by layout-card unless `` is true.**) -- The number of columns is clamped between `` and `` -- Cards have a `cardHeight`, which is calculated from their content. One unit is roughly 50 pixels tall. -- Each new card is added to the first row which is less than `` units tall. -- If all columns are taller than ``, the card is added to the shortest column. -- Once all cards have been placed, any remaining empty columns are removed. +Any layout can also be used inside a lovelace-card by using `layout-card`: ```yaml type: custom:layout-card +layout_type: masonry +layout_options: + width: 300 + max_cols: 10 cards: - - type: entities - title: 1 - entities: - - light.bed_light - - type: entities - title: 2 - entities: - - light.bed_light - - type: entities - title: 3 - entities: - - light.bed_light - - type: entities - title: 4 - entities: - - light.bed_light - - light.bed_light - - light.bed_light - - light.bed_light - - type: entities - title: 5 - entities: - - light.bed_light - - type: entities - title: 6 - entities: - - light.bed_light - - type: entities - title: 7 - entities: - - light.bed_light - - type: entities - title: 8 - entities: - - light.bed_light + ... ``` -![layout-card 1 - auto](https://user-images.githubusercontent.com/1299821/48088464-62312500-e202-11e8-8ccc-0ef6ac10ec2e.png) - -> Note: To get *exactly* the same behavior as the default layout, you need to specify `sidebar_column: true` and `max_columns: 4`. This was given a higher default value to work better with the ridiculously huge screens some people have nowadays. -> Note: The same 8 cards will be used in the following examples and will be omitted for clarity. +`Layout-card` will take its `cards` and place them within itself according to the specified layout. -### `horizontal` layout +> NOTE: Please be aware that `layout-card` is itself a CARD, and cannot be wider than any other cards in the same view. \ +> All cards you specify within it must fit inside this same width. \ +> Thus `layout-card` is of limited use expect in [panel mode](https://www.home-assistant.io/lovelace/dashboards-and-views/#panel). -The horizontal layout calculates the number of columns just like the `auto` layout. -It then places the first card in the first column, the second card in the second columns, and so on. Once it reaches the last column, it starts over from the first. +### Card layout options +Some layout types accept options added to separate cards: ```yaml -type: custom:layout-card -layout: horizontal -cards: - - ... +type: entities +entities: + - light.bed_light +layout: + column: 2 ``` -![layout-card 2 - horizontal](https://user-images.githubusercontent.com/1299821/48088463-62312500-e202-11e8-875a-5ff836069017.png) - -### `vertical` layout -The vertical layout calculates the number of columns just like the `auto` layout. -It then places every card in the first column. +### Layout-break card +Layout card adds a special card called `layout-break` which can be used to change how some layouts work. ```yaml -type: custom:layout-card -layout: vertical -cards: - - ... +type: custom:layout-break ``` -![layout-card 3 - vertical](https://user-images.githubusercontent.com/1299821/48088462-62312500-e202-11e8-8e5e-7f4d1821eeb8.png) -It's OK to think I'm out of my mind at this point. And if you don't, you probably will once I claim that this is probably the most useful layout. +## Layouts +Layout-card introduces four layouts. +- Masonry +- Horizontal +- Vertical +- Grid -Still here? OK. Let me tell you about the `break`. +The first three are column based and work similarly: -### `- break` +- A number of evenly sized columns is prepared based on avaialble space, the `width` option and the `max_cols` option. +- The cards are placed into the columns one at a time in a method depending on the current layout. +- Any empty columns are removed. +- The remaining columns are placed centered on screen. -Just add `- break` to the list of `` to make card placer move on to the next column for the next card. +All column based layouts accept the following options: -This is most useful in the `vertical` layout, but will work in the `horizontal` and `auto` layouts too. +|Option|Values|Description|Default +|---|---|---|---| +|`width`| number | Size in pixels of each column | 300 | +|`max_width` | number | Maximum width of a card | 1.5 * `width` if specified
otherwise 500 | +|`max_cols` | number | Maximum number of columns to show | 4 if sidebar is hidden
3 if sidebar is shown | -```yaml -type: custom:layout-card -layout: vertical -cards: - - type: entities - title: 1 - ... - - type: entities - title: 2 - ... - - break - - type: entities - title: 3 - ... - - break - - type: entities - title: 4 - ... - - type: entities - title: 6 - ... - - break - - type: entities - title: 7 - ... -``` -![layout-card 4 - manual breaks](https://user-images.githubusercontent.com/1299821/48088461-62312500-e202-11e8-96ab-e4f560f8d4fc.png) +### Masonry layout -### `grid` layout (experimental) +The masonry layout immitates the default layout of lovelace. +- Each card is assigned a height based on their contents. One height unit corresponds to roughly 50 pixels, but this may varry. +- When a card is placed in the layout, it is put in the first column which has a total height of less than `min_height` units. \ +Otherwise it is put it the shortest column. -For maximum control, you can place every card manually in a [CSS grid](https://css-tricks.com/snippets/css/complete-guide-grid/) by using the `grid` layout. +![Masonry Layout](https://user-images.githubusercontent.com/1299821/111067510-f2639100-84c4-11eb-9ce1-b40cf1f13772.png) -To do this, you need to specify `gridrows` and `gridcols` with the settings for `grid-template-rows` and `grid-template-columns` respectively **and** also add `gridcol:` and `gridrow:` for *each card* with the settings for `grid-column` and `grid-row` respectively. +The masonry layout accepts the following options: +|Option|Values|Description|Default +|---|---|---|---| +|`min_height`| number | Minimum number of card height units in a column before the next one is considered | 5 | -> Hint: This may look better if you also have [card-mod](https://github.com/thomasloven/lovelace-card-mod) and set the card heights to `100 %`. +### Horizontal layout -```yaml -type: custom:layout-card -layout: vertical -column_width: 100% -cards: - - type: markdown - content: "# Grid" - - type: custom:layout-card - layout: grid - gridrows: 180px 200px auto - gridcols: 180px auto 180px - cards: - - type: glance - entities: - - sun.sun - gridrow: 1 / 2 - gridcol: 1 / 2 - style: "ha-card { height: 100%; }" - - type: entities - entities: &ents - - light.bed_light - - light.kitchen_lights - - light.ceiling_lights - gridrow: 1 / 3 - gridcol: 2 / 4 - style: "ha-card { height: 100%; }" - - type: markdown - content: test - gridrow: 2 / 4 - gridcol: 1 / 2 - style: "ha-card { height: 100%; }" - - type: entities - entities: *ents - gridrow: 3 / 4 - gridcol: 2 / 3 -``` -![layout-card - Grid](https://user-images.githubusercontent.com/1299821/71694902-e3f1f380-2db0-11ea-82f1-8f880a2fbb24.png) +The horizontal layout will add each card to the next column, looping back to the first one when necessary: -You can also omit `gridrows` or tweak `gridgap` and `gridplace` to get different results. I don't know how this works, but feel free to play around! +![Horizontal Layout](https://user-images.githubusercontent.com/1299821/111067632-7453ba00-84c5-11eb-942c-88dab6d1f19b.png) -## Tweaking layouts +A `layout-break` card will cause the next card to be places in the first column. -- First of all ``, which can be used to force the number of columns displayed: +The horizontal layout accepts the following **card** layout options: +|Option|Values|Description|Default +|---|---|---|---| +|`column`| number | Which column to place the card in. Following cards will be placed in the next column. | | -```yaml -type: custom:layout-card -layout: vertical -column_num: 7 -cards: - - ... -``` -![force-number](https://user-images.githubusercontent.com/1299821/68596569-be74f780-049b-11ea-97a7-1d591edf5a26.png) -> Note: See how squeezing cards too tight will make them look weird? Keep this in mind, and don't send me bug reports about it. -> Your toggles would pop out too if someone forced you into a 100 pixel box. +### Vertical layout -- The width of columns can be specified either all together...: +The vertical layout will add each card to the same column as the previous one. -```yaml -type: custom:layout-card -column_width: 200 -cards: - - ... -``` -![same-width](https://user-images.githubusercontent.com/1299821/68597185-d13bfc00-049c-11ea-8fd4-8d6327a9764a.png) +![Vertical Layout](https://user-images.githubusercontent.com/1299821/111067990-17f19a00-84c7-11eb-905a-2c687e85e972.png) -- ...or as a list of column widths: +A `layout-break` card will cause the next card to be placed in the next column. -```yaml -type: custom:layout-card -column_width: [200, 300, 150] -cards: - - ... -``` -![varied-width](https://user-images.githubusercontent.com/1299821/68597348-1829f180-049d-11ea-917a-c97be3f1e561.png) +The vertical layout accepts the following **card** layout options: +|Option|Values|Description|Default +|---|---|---|---| +|`column`| number | Which column to place the card in. Following cards will be placed in the same column. | | -If there are more column than values in the list, the last value in the list will be used for the remaining columns. +### Grid layout +The grid layout will give you full controll of your cards by leveraging [CSS Grid](https://css-tricks.com/snippets/css/complete-guide-grid/). -- Values can be specified either in pixels or in percentages: +The grid layout accepts any option starting with `grid-` that works for a Grid Container. +The grid layout also accepts any card layout option starting with `grid-` that works for a Grid Item. + +![Grid Layout](https://user-images.githubusercontent.com/1299821/111069100-cac3f700-84cb-11eb-904f-cb5661c5734b.png) +
+Screenshot source code ```yaml -type: custom:layout-card -column_width: 30% +title: Grid layout +type: custom:grid-layout +layout: + grid-template-columns: 25% 25% 25% 25% + grid-template-rows: auto + grid-template-areas: | + "header header header header" + "main main . sidebar" + "footer footer footer footer" +badges: [] cards: - - ... + - type: entities + entities: + - entity: light.bed_light + title: '1' + show_header_toggle: false + layout: + grid-area: header + - type: entities + entities: + - entity: light.bed_light + title: '2' + show_header_toggle: false + layout: + grid-area: footer + - type: entities + entities: + - entity: light.bed_light + title: '3' + show_header_toggle: false + layout: + grid-area: sidebar + - type: entities + entities: + - light.bed_light + - light.ceiling_lights + - light.kitchen_lights + title: '4' + show_header_toggle: false + layout: + grid-area: main ``` -![percentage](https://user-images.githubusercontent.com/1299821/68598310-d4d08280-049e-11ea-8ea7-48d8b14ffef0.png) - -- Further tweaks can be made in the same way using `` and ``, but most of the time `` should be enough. +
-- `` (single value or list) and `` (single value) can be used to tweak the CSS flexbox settings of the layout. See [this excellent guide](https://css-tricks.com/snippets/css/a-guide-to-flexbox/) for more info. -- `` will populate the columns from right to left instead of left to right. +## Card visibility +Individual cards can be displayed or hidden based on their `show:` layout option. +```yaml +- type: entities + title: Always show + ... + layout: + show: always +- type: entities + title: Never show + ... + layout: + show: never +``` -## A few tips -- `card_options` works really well together with [card-mod](https://github.com/thomasloven/lovelace-card-mod). - -- Layout-cards can be placed inside other layout-cards or in vertical-stack cards: -![stacked](https://user-images.githubusercontent.com/1299821/68598908-f0885880-049f-11ea-814f-b91ee6ee9eef.png) +The options `show: always` and `show: never` are honestly quite pointless... but there's a cooler option: -- [gap-card](https://github.com/thomasloven/lovelace-gap-card) can be used to leave a gap in the layout: -![gap](https://user-images.githubusercontent.com/1299821/68599474-eb77d900-04a0-11ea-9b89-f0d090c858b3.png) +```yaml +type: entities +title: Never show +... +layout: + show: + mediaquery: +``` -- The card list can be populated automatically using [auto-entities](https://github.com/thomasloven/lovelace-auto-entities) +This card will only be displayed if the [@media rule](https://www.w3schools.com/cssref/css3_pr_mediaquery.asp) `` is a match. +Example: ```yaml -type: custom:auto-entities -filter: - include: - - domain: light - options: - type: light # Make sure to specify a card type for every filter - - domain: climate - options: - type: thermostat - exclude: - - state: unavailable -sort: - method: name - ignore_case: true -card: - type: custom:layout-card +- type: markdown + content: | + This is only shown on screens more than 800 px wide + layout: + show: + mediaquery: "(min-width: 800px)" +- type: markdown + content: | + This is only shown on screens less than 400 px wide + layout: + show: + mediaquery: "(max-width: 400px)" +- type: markdown + content: | + This is only shown on touch screen devices + layout: + show: + mediaquery: "(pointer: coarse)" ``` -![auto-entities](https://user-images.githubusercontent.com/1299821/68600943-a86b3500-04a3-11ea-8d08-106e77262552.png) +## Use with entity filters +Layout card can be used with cards that populate an `entities:` list, like [Entity Filter](https://www.home-assistant.io/lovelace/entity-filter/) or [auto-entities](https://github.com/thomasloven/lovelace-auto-entities). + +If no card type is explicitly specified for the entries, the [Entity](https://www.home-assistant.io/lovelace/entity/) card will be used. -## Note for Home Assistant Cast users +Example: -Layout-card doesn't entirely work with Cast at this time. Specifically, the view may or may not load in if you start a Cast directly to a view which uses layout-card. +```yaml +- type: 'custom:auto-entities' + filter: + include: + - domain: light + options: + type: light + - domain: sensor + exclude: [] + card: + type: 'custom:layout-card' + cards: [] + layout_type: masonry +``` -If you instead load a different view, and then *switch* to the one using layout-card, things seem to be working better. I hope to be able to fix this soon. +![auto-entities](https://user-images.githubusercontent.com/1299821/111070882-019e0b00-84d4-11eb-8a00-86683d598c3e.png) --- Buy Me A Coffee diff --git a/layout-card.js b/layout-card.js index 64a1ea5..682015a 100644 --- a/layout-card.js +++ b/layout-card.js @@ -37,7 +37,7 @@ function t(t,e,i,s){var o,n=arguments.length,r=n<3?e:null===s?s=Object.getOwnPro display: block; margin: var(--masonry-view-card-margin, 4px 4px 8px); } - `]}}t([Q()],$t.prototype,"_columns",void 0);customElements.define("masonry-layout",class extends $t{async _placeColumnCards(t,e){var i;const s=(null===(i=this._config.layout)||void 0===i?void 0:i.min_height)||5;function o(){let e=0;for(let i=0;i{const i=this._config.cards[e];return{card:t,config:i,index:e,show:this._shouldShow(t,i,e)}}));for(const o of s){const s=this.getCardElement(o);for(const[i,n]of Object.entries(null!==(e=null===(t=o.config)||void 0===t?void 0:t.layout)&&void 0!==e?e:{}))i.startsWith("grid")&&s.style.setProperty(i,n);i.appendChild(s)}}render(){return j`
+ `]}}t([Q()],$t.prototype,"_columns",void 0);customElements.define("masonry-layout",class extends $t{async _placeColumnCards(t,e){var i;const s=(null===(i=this._config.layout)||void 0===i?void 0:i.min_height)||5;function o(){let e=0;for(let i=0;ithis._placeCards()))}else this._mediaQueries.push(null)}async updated(t){await super.updated(t),(t.has("cards")||t.has("_editMode"))&&this._placeCards()}async firstUpdated(){const t=this.shadowRoot.querySelector("#root");if(this._config.layout)for(const[e,i]of Object.entries(this._config.layout))e.startsWith("grid")&&t.style.setProperty(e,i)}_shouldShow(t,e,i){if(!super._shouldShow(t,e,i))return!1;const s=this._mediaQueries[i];return!s||!!s.matches}_placeCards(){var t,e;const i=this.shadowRoot.querySelector("#root");for(;i.firstChild;)i.removeChild(i.firstChild);let s=this.cards.map(((t,e)=>{const i=this._config.cards[e];return{card:t,config:i,index:e,show:this._shouldShow(t,i,e)}}));for(const o of s.filter((t=>{var e;return(null===(e=this.lovelace)||void 0===e?void 0:e.editMode)||t.show}))){const s=this.getCardElement(o);for(const[i,n]of Object.entries(null!==(e=null===(t=o.config)||void 0===t?void 0:t.layout)&&void 0!==e?e:{}))i.startsWith("grid")&&s.style.setProperty(i,n);i.appendChild(s)}}render(){return j`
${this._render_fab()}`}static get styles(){return[this._fab_styles,it` :host { padding-top: 4px; @@ -59,7 +59,7 @@ function t(t,e,i,s){var o,n=arguments.length,r=n<3?e:null===s?s=Object.getOwnPro :host(:not(:last-child)) { margin-bottom: 0 !important; } - `}static getConfigElement(){return document.createElement("layout-card-editor")}static getStubConfig(){return{layout:{type:"masonry",layout:{}},cards:[]}}}t([Q()],Nt.prototype,"hass",void 0),t([Q()],Nt.prototype,"editMode",void 0),t([Q()],Nt.prototype,"isPanel",void 0),t([Q()],Nt.prototype,"_config",void 0),t([Q()],Nt.prototype,"_cards",void 0),t([Q()],Nt.prototype,"_layoutElement",void 0),t([Q()],Nt.prototype,"_layoutType",void 0),customElements.define("layout-card",Nt),window.customCards=window.customCards||[],window.customCards.push({type:"layout-card",name:"Layout Card",preview:!1,description:"Like a stack card, but with way more control."});class Mt extends ot{constructor(){super(...arguments),this._selectedTab=0,this._selectedCard=0,this._cardGUIMode=!0,this._cardGUIModeAvailable=!0}setConfig(t){this._config=JSON.parse(JSON.stringify(t))}_handleSwitchTab(t){this._selectedTab=parseInt(t.detail.index,10)}_layoutChanged(t){t.stopPropagation();const e=t.detail.config.type?t.detail.config.type.substr("custom:".length).slice(0,-"-layout".length):"default";"default"!==e?this._config.layout_type=e:delete this._config.layout_type,t.detail.config.layout?this._config.layout_options=t.detail.config.layout:delete this._config.layout_options,this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}_editCard(t){t.stopPropagation(),"add-card"!==t.target.id?(this._cardGUIMode=!0,this._cardEditorEl&&(this._cardEditorEl.GUImode=!0),this._cardGUIModeAvailable=!0,this._selectedCard=parseInt(t.detail.selected,10)):this._selectedCard=this._config.cards.length}_addCard(t){t.stopPropagation(),this._config.cards.push(t.detail.config),this._selectedCard=this._config.cards.length-1,this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}_updateCard(t){t.stopPropagation();const e=[...this._config.cards];e[this._selectedCard]=t.detail.config,this._config=Object.assign(Object.assign({},this._config),{cards:e}),this._cardGUIModeAvailable=t.detail.guiModeAvailable,this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}_GUIModeChange(t){t.stopPropagation(),this._cardGUIMode=t.detail.guiMode,this._cardGUIModeAvailable=t.detail.guiModeAvailable}_toggleMode(t){this._cardEditorEl.toggleMode()}_moveCard(t){const e=this._selectedCard,i=e+t.currentTarget.move,s=[...this._config.cards],o=s.splice(e,1)[0];s.splice(i,0,o),this._config=Object.assign(Object.assign({},this._config),{cards:s}),this._selectedCard=i,this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}_deleteCard(){const t=[...this._config.cards];t.splice(this._selectedCard,1),this._config=Object.assign(Object.assign({},this._config),{cards:t}),this._selectedCard=Math.max(0,this._selectedCard-1),this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}render(){return this.hass&&this._config?j` + `}static getConfigElement(){return document.createElement("layout-card-editor")}static getStubConfig(){return{layout:{layout_type:"masonry",layout_options:{}},cards:[]}}}t([Q()],Nt.prototype,"hass",void 0),t([Q()],Nt.prototype,"editMode",void 0),t([Q()],Nt.prototype,"isPanel",void 0),t([Q()],Nt.prototype,"_config",void 0),t([Q()],Nt.prototype,"_cards",void 0),t([Q()],Nt.prototype,"_layoutElement",void 0),t([Q()],Nt.prototype,"_layoutType",void 0),customElements.define("layout-card",Nt),window.customCards=window.customCards||[],window.customCards.push({type:"layout-card",name:"Layout Card",preview:!1,description:"Like a stack card, but with way more control."});class Mt extends ot{constructor(){super(...arguments),this._selectedTab=0,this._selectedCard=0,this._cardGUIMode=!0,this._cardGUIModeAvailable=!0}setConfig(t){this._config=JSON.parse(JSON.stringify(t))}_handleSwitchTab(t){this._selectedTab=parseInt(t.detail.index,10)}_layoutChanged(t){t.stopPropagation();const e=t.detail.config.type?t.detail.config.type.substr("custom:".length).slice(0,-"-layout".length):"default";"default"!==e?this._config.layout_type=e:delete this._config.layout_type,t.detail.config.layout?this._config.layout_options=t.detail.config.layout:delete this._config.layout_options,this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}_editCard(t){t.stopPropagation(),"add-card"!==t.target.id?(this._cardGUIMode=!0,this._cardEditorEl&&(this._cardEditorEl.GUImode=!0),this._cardGUIModeAvailable=!0,this._selectedCard=parseInt(t.detail.selected,10)):this._selectedCard=this._config.cards.length}_addCard(t){t.stopPropagation(),this._config.cards.push(t.detail.config),this._selectedCard=this._config.cards.length-1,this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}_updateCard(t){t.stopPropagation();const e=[...this._config.cards];e[this._selectedCard]=t.detail.config,this._config=Object.assign(Object.assign({},this._config),{cards:e}),this._cardGUIModeAvailable=t.detail.guiModeAvailable,this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}_GUIModeChange(t){t.stopPropagation(),this._cardGUIMode=t.detail.guiMode,this._cardGUIModeAvailable=t.detail.guiModeAvailable}_toggleMode(t){this._cardEditorEl.toggleMode()}_moveCard(t){const e=this._selectedCard,i=e+t.currentTarget.move,s=[...this._config.cards],o=s.splice(e,1)[0];s.splice(i,0,o),this._config=Object.assign(Object.assign({},this._config),{cards:s}),this._selectedCard=i,this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}_deleteCard(){const t=[...this._config.cards];t.splice(this._selectedCard,1),this._config=Object.assign(Object.assign({},this._config),{cards:t}),this._selectedCard=Math.max(0,this._selectedCard-1),this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}render(){return this.hass&&this._config?j`
{const i={get(){return this.renderRoot.querySelector(mt)},enumerable:!0,configurable:!0};if(_t){const t="symbol"==typeof e?Symbol():`__${e}`;i.get=function(){return void 0===this[t]&&(this[t]=this.renderRoot.querySelector(mt)),this[t]}}return void 0!==e?Y(i,t,e):X(i,t)})],Mt.prototype,"_cardEditorEl",void 0),customElements.define("layout-card-editor",Mt),customElements.whenDefined("hui-dialog-edit-view").then((()=>{const t=customElements.get("hui-dialog-edit-view");{const t=document.createElement("hui-masonry-view");t.lovelace={editMode:!0},t.updated(new Map)}const e=t.prototype.firstUpdated;t.prototype.updated=function(t){var i,s;if(null==e||e.bind(this)(t),t.has("_params")&&void 0===t.get("_params")){const t=document.createElement("paper-tab");t.id="tab-layout",t.innerHTML="Layout",this.shadowRoot.querySelector("paper-tabs").appendChild(t)}if(t.has("_curTab"))if("tab-layout"===this._curTab){const t=document.createElement("view-layout-editor");t.config=this._config,t.addEventListener("view-layout-changed",this._viewConfigChanged.bind(this));const e=this.shadowRoot.querySelector("div[slot='heading']");null===(i=null==e?void 0:e.parentNode)||void 0===i||i.insertBefore(t,e.nextSibling)}else{const t=this.shadowRoot.querySelector("view-layout-editor");null===(s=null==t?void 0:t.parentNode)||void 0===s||s.removeChild(t)}t.has("_config")&&this.shadowRoot.querySelector("view-layout-editor")&&(this.shadowRoot.querySelector("view-layout-editor").config=this._config)}}));const kt=["default","masonry","vertical","horizontal","grid"];customElements.define("view-layout-editor",class extends ot{_typeChanged(t){t.stopPropagation();const e=kt[t.target.selected];"default"===e?delete this.config.type:this.config.type=`custom:${e}-layout`,this.dispatchEvent(new CustomEvent("view-layout-changed",{detail:{config:this.config}}))}_layoutChanged(t){t.stopPropagation(),this.config.layout=t.detail.value,this.dispatchEvent(new CustomEvent("view-layout-changed",{detail:{config:this.config}}))}render(){var t;const e=this.config.type?this.config.type.substr("custom:".length).slice(0,-"-layout".length):"default";return j` + `]}}t([Q()],Mt.prototype,"_config",void 0),t([Q()],Mt.prototype,"lovelace",void 0),t([Q()],Mt.prototype,"hass",void 0),t([K()],Mt.prototype,"_selectedTab",void 0),t([K()],Mt.prototype,"_selectedCard",void 0),t([K()],Mt.prototype,"_cardGUIMode",void 0),t([K()],Mt.prototype,"_cardGUIModeAvailable",void 0),t([(mt="hui-card-element-editor",(t,e)=>{const i={get(){return this.renderRoot.querySelector(mt)},enumerable:!0,configurable:!0};if(_t){const t="symbol"==typeof e?Symbol():`__${e}`;i.get=function(){return void 0===this[t]&&(this[t]=this.renderRoot.querySelector(mt)),this[t]}}return void 0!==e?Y(i,t,e):X(i,t)})],Mt.prototype,"_cardEditorEl",void 0),customElements.define("layout-card-editor",Mt),customElements.whenDefined("hui-dialog-edit-view").then((()=>{const t=customElements.get("hui-dialog-edit-view");{const t=document.createElement("hui-masonry-view");t.lovelace={editMode:!0},t.updated(new Map)}const e=t.prototype.firstUpdated;t.prototype.updated=function(t){var i,s;if(null==e||e.bind(this)(t),t.has("_params")&&void 0===t.get("_params")){const t=document.createElement("paper-tab");t.id="tab-layout",t.innerHTML="Layout",this.shadowRoot.querySelector("paper-tabs").appendChild(t)}if(t.has("_curTab"))if("tab-layout"===this._curTab){const t=document.createElement("view-layout-editor");t.config=this._config,t.addEventListener("view-layout-changed",this._viewConfigChanged.bind(this));const e=this.shadowRoot.querySelector("div[slot='heading']");null===(i=null==e?void 0:e.parentNode)||void 0===i||i.insertBefore(t,e.nextSibling)}else{const t=this.shadowRoot.querySelector("view-layout-editor");null===(s=null==t?void 0:t.parentNode)||void 0===s||s.removeChild(t)}t.has("_config")&&this.shadowRoot.querySelector("view-layout-editor")&&(this.shadowRoot.querySelector("view-layout-editor").config=this._config)}}));const kt=["default","masonry","horizontal","vertical","grid"];customElements.define("view-layout-editor",class extends ot{_typeChanged(t){t.stopPropagation();const e=kt[t.target.selected];"default"===e?delete this.config.type:this.config.type=`custom:${e}-layout`,this.dispatchEvent(new CustomEvent("view-layout-changed",{detail:{config:this.config}}))}_layoutChanged(t){t.stopPropagation(),this.config.layout=t.detail.value,this.dispatchEvent(new CustomEvent("view-layout-changed",{detail:{config:this.config}}))}render(){var t;const e=this.config.type?this.config.type.substr("custom:".length).slice(0,-"-layout".length):"default";return j` = []; + + async setConfig(config: ViewConfig) { + await super.setConfig(config); + + for (const card of this._config.cards) { + if ( + typeof card.layout?.show !== "string" && + card.layout?.show?.mediaquery + ) { + const mq = window.matchMedia(`${card.layout.show.mediaquery}`); + this._mediaQueries.push(mq); + mq.addEventListener("change", () => this._placeCards()); + } else { + this._mediaQueries.push(null); + } + } + } + async updated(changedProperties) { await super.updated(changedProperties); if (changedProperties.has("cards") || changedProperties.has("_editMode")) { @@ -19,6 +43,15 @@ class GridLayout extends BaseLayout { } } + _shouldShow(card: LovelaceCard, config: CardConfig, index: number) { + if (!super._shouldShow(card, config, index)) return false; + + const mq = this._mediaQueries[index]; + if (!mq) return true; + if (mq.matches) return true; + return false; + } + _placeCards() { const root = this.shadowRoot.querySelector("#root"); while (root.firstChild) root.removeChild(root.firstChild); @@ -31,7 +64,8 @@ class GridLayout extends BaseLayout { show: this._shouldShow(card, config, index), }; }); - for (const card of cards) { + + for (const card of cards.filter((c) => this.lovelace?.editMode || c.show)) { const el = this.getCardElement(card); for (const [key, value] of Object.entries(card.config?.layout ?? {})) { if (key.startsWith("grid")) el.style.setProperty(key, value as string); diff --git a/src/patches/hui-dialog-edit-view.ts b/src/patches/hui-dialog-edit-view.ts index 489b64f..25e68c1 100644 --- a/src/patches/hui-dialog-edit-view.ts +++ b/src/patches/hui-dialog-edit-view.ts @@ -52,7 +52,7 @@ customElements.whenDefined("hui-dialog-edit-view").then(() => { }; }); -const TYPE_OPTIONS = ["default", "masonry", "vertical", "horizontal", "grid"]; +const TYPE_OPTIONS = ["default", "masonry", "horizontal", "vertical", "grid"]; class ViewLayoutEditor extends LitElement { config;