├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── .gitignore ├── hacs.json ├── src ├── konnected_vn_color_picker.png ├── konnected_vn_vertical-slider-cover-card-color-guideline.png ├── konnected_vn_Vertical-Slider-Cover-Card-Panel-Mode-7-2-2020.png └── konnected_vn_Vertical-Slider-Cover-Card-Normal-Mode-7-2-2020.png ├── package.json ├── LICENSE ├── README.md └── vertical-slider-cover-card.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /.rpt2_cache/ 3 | package-lock.json 4 | .DS_Store 5 | /dist 6 | -------------------------------------------------------------------------------- /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Vertical Slider Cover Card", 3 | "render_readme": true, 4 | "filename": "vertical-slider-cover-card.js" 5 | } 6 | -------------------------------------------------------------------------------- /src/konnected_vn_color_picker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/konnectedvn/lovelace-vertical-slider-cover-card/HEAD/src/konnected_vn_color_picker.png -------------------------------------------------------------------------------- /src/konnected_vn_vertical-slider-cover-card-color-guideline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/konnectedvn/lovelace-vertical-slider-cover-card/HEAD/src/konnected_vn_vertical-slider-cover-card-color-guideline.png -------------------------------------------------------------------------------- /src/konnected_vn_Vertical-Slider-Cover-Card-Panel-Mode-7-2-2020.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/konnectedvn/lovelace-vertical-slider-cover-card/HEAD/src/konnected_vn_Vertical-Slider-Cover-Card-Panel-Mode-7-2-2020.png -------------------------------------------------------------------------------- /src/konnected_vn_Vertical-Slider-Cover-Card-Normal-Mode-7-2-2020.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/konnectedvn/lovelace-vertical-slider-cover-card/HEAD/src/konnected_vn_Vertical-Slider-Cover-Card-Normal-Mode-7-2-2020.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vertical-slider-cover-card", 3 | "version": "0.0.1", 4 | "description": "Lovelace vertical-slider-cover-card", 5 | "keywords": [ 6 | "home-assistant", 7 | "homeassistant", 8 | "hass", 9 | "lovelace", 10 | "custom-cards", 11 | "cover" 12 | ], 13 | "module": "vertical-slider-cover-card.js", 14 | "repository": "git@github.com:konnectedvn/lovelace-vertical-slider-cover-card.git", 15 | "author": "Duy Truong ", 16 | "license": "MIT", 17 | "dependencies": { 18 | "custom-card-helpers": "^1.6.3", 19 | "home-assistant-js-websocket": "^4.4.0", 20 | "lit-element": "^2.2.1", 21 | "lit-html": "^1.1.2" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: feature request 6 | assignees: '' 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Custom cards for Home Assistant 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | 14 | 15 | **Checklist:** 16 | 17 | - [ ] I updated to the latest version available 18 | - [ ] I cleared the cache of my browser 19 | 20 | **Release with the issue:** 21 | 22 | **Last working release (if known):** 23 | 24 | **Home Assistant version:** 25 | 26 | **Browser and Operating System:** 27 | 28 | 31 | 32 | **Description of problem:** 33 | 34 | 37 | 38 | **Javascript errors shown in the web inspector (if applicable):** 39 | 40 | ``` 41 | 42 | ``` 43 | 44 | **Additional information:** 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vertical Slider Cover Card by konnected.vn (https://konnected.vn -VI) 2 | 3 | [![GitHub Release][releases-shield]][releases] 4 | [![License][license-shield]](LICENSE.md) 5 | [![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg?style=for-the-badge)](https://github.com/hacs/integration) 6 | 7 | ![Project Maintenance][maintenance-shield] 8 | 9 | ## SCREENSHOTS 10 | 11 | Panel View screenshot 12 | 13 | ![panel card screenshot](https://github.com/konnectedvn/lovelace-vertical-slider-cover-card/blob/master/src/konnected_vn_Vertical-Slider-Cover-Card-Panel-Mode-7-2-2020.png?raw=true "Desktop screenshot") 14 | 15 | Card screenshot 16 | 17 | ![card screenshot](https://github.com/konnectedvn/lovelace-vertical-slider-cover-card/blob/master/src/konnected_vn_Vertical-Slider-Cover-Card-Normal-Mode-7-2-2020.png?raw=true "Desktop screenshot") 18 | 19 | 20 | Color Guidelines 21 | 22 | ![card screenshot](https://github.com/konnectedvn/lovelace-vertical-slider-cover-card/blob/master/src/konnected_vn_vertical-slider-cover-card-color-guideline.png?raw=true "Desktop screenshot") 23 | 24 | 25 | Color Picker Helper (Google) 26 | 27 | ![card screenshot](https://github.com/konnectedvn/lovelace-vertical-slider-cover-card/blob/master/src/konnected_vn_color_picker.png?raw=true "Desktop screenshot") 28 | 29 | ## MINIMUM REQUIREMENTS 30 | 31 | 32 | Your covers have to support `close_cover`, `open_cover` and `set_cover_position` services. 33 | Normally, this means `attributes.supported_features` is at least 7 or greater. 34 | 35 | 36 | # Options 37 | 38 | | Name | Type | Requirement | Description | Default | 39 | | ------------------------------ | ------- | ------------ | ------------------------------------------------------ | ---------------------- | 40 | | type | string | **Required** | `custom:vertical-slider-cover-card` | | 41 | | title | string | **Required** | Title | | 42 | | entities | list | **Required** | Cover entities to show as slider in card | | 43 | |   - entity | string | **Required** | Cover's `entity_id` |   | 44 | |     name | string | **Optional** | Custom name for every cover | `friendly_name` | 45 | |     script | string | **Optional** | Call additional script on every position change (3) | `null` | 46 | | **Sidebar** area | | | | | 47 | | showSidebar | boolean | **Optional** | Show or hide side bar (1) | `true` | 48 | | sideColor1 | string | **Optional** | Upper-left color of sidebar (~) | `#ffcccc` | 49 | | sideColor2 | string | **Optional** | Lower-right color of sidebar (~) | `#b30000` | 50 | | icon | string | **Optional** | Icon to show on side bar | `mdi:blinds` | 51 | | iconSize | string | **Optional** | Font size of icon on side bar | `28px` | 52 | | iconColor | string | **Optional** | Color of icon on side bar | theme | 53 | | titleSize | string | **Optional** | Font size of title | `40px` | 54 | | titleFontColor | string | **Optional** | Font color of title | theme | 55 | | countText | string | **Optional** | Text to show follow number of covers open | `covers open` | 56 | | countTextFontColor | string | **Optional** | Font color of text to show follow number | theme | 57 | | showButton | boolean | **Optional** | Show Home button at bottom of side bar | `false` | 58 | | closedBaseline | byte | **Optional** | Cover with positon higher is counted as open | `0` | 59 | | buttonText | string | **Optional** | Text to show on button | `Home` | 60 | | buttonFontColor | string | **Optional** | Font color of button | theme | 61 | | buttonPath | string | **Optional** | Path of Lovelace View when click button | `/lovelace/0` | 62 | | buttonService | string | **Optional** | Service to call (overide buttonPath if any) | `null` | 63 | | buttonData | string | **Optional** | Service data to call | `null` | 64 | | **Cover** area | | | | | 65 | | background | string | **Optional** | Card background in hex (# or hsl with opacity) | `transparent` | 66 | | panelType | boolean | **Optional** | Try to center all sliders (`gapWidth` will be ignored) | `true` | 67 | | gapWidth | string | **Optional** | Width of Space between 2 cover sliders | `50px` | 68 | | positionHeight | string | **Optional** | Height of each slider in px | `300px` | 69 | | positionWidth | string | **Optional** | Width of each slider in px | `100px` | 70 | | openColor | string | **Optional** | Color of lower slider bar (~) | `hsl(0, 0%, 90%, 0.8)` | 71 | | closedColor | string | **Optional** | Color of upper slider bar (~) | `hsl(0, 0%, 20%)` | 72 | | openBaseline | integer | **Optional** | (2) | `0` | 73 | | showSwitch | boolean | **Optional** | Show STOP switch under every covers | `true` | 74 | | switchWidth | string | **Optional** | Width of Stop button at bottom | `positionWidth` | 75 | | switchHeight | string | **Optional** | Height of Stop button at bottom | `switchWidth` | 76 | | switchColor | string | **Optional** | Background color of Stop button (~) | `sideColor2` | 77 | | switchFontColor | string | **Optional** | Font color of Stop button at bottom | theme | 78 | | showName | boolean | **Optional** | Show/hide the name of cover | `true` | 79 | | showPosition | boolean | **Optional** | Show/hide the position (number) of cover | `true` | 80 | 81 | ## STARTING A NEW CARD 82 | 83 | ### INSTALL USING HACS (recommended) 84 | 85 | Add this repo to HACS custom repositories. 86 | Card is in HACS default. 87 | 88 | **repo**: https://github.com/konnectedvn/lovelace-vertical-slider-cover-card 89 | 90 | *Category*: Frontend 91 | 92 | Hướng dẫn cài đặt và sử dụng HACS trong Home Assistant có thể xem ở đây (VI - HACS Guide): 93 | https://konnected.vn/home-assistant/home-assistant-cai-dat-hacs-va-theme-2020-03-27 94 | 95 | ### MANUAL INSTALL 96 | 97 | Download vertical-slider-cover-card.js and add it to your /config/wwww/vertical-slider-cover-card (make new dir if it does not exist). 98 | 99 | In Home Assistant Dashboard **Resource**, add resource path /local/vertical-slider-cover-card/vertical-slider-cover-card.js, type: module. 100 | 101 | `resources`: 102 | ```yaml 103 | - url: /local/vertical-slider-cover-card/vertical-slider-cover-card.js 104 | type: module 105 | ``` 106 | 107 | ### ADD NEW CARD 108 | 109 | In Lovelace, add new Manual card. Replace new card content with sample configuration below. Change the entities. 110 | 111 | In View option, check Panel mode if you want do display card in full width. 112 | 113 | #example configuration 114 | 115 | ##minimum configuration 116 | 117 | ```yaml 118 | type: 'custom:vertical-slider-cover-card' 119 | title: Garage 120 | entities: 121 | - entity: cover.garage_shutter 122 | ``` 123 | 124 | ##more customized 125 | 126 | ```yaml 127 | type: 'custom:vertical-slider-cover-card' 128 | background: 'rgba(0, 0, 0, 0.4)' 129 | showButton: true 130 | buttonPath: /lovelace/0 131 | buttonText: Home 132 | #Call service instead of navigating -> comment 1 line above & uncomment all belows 133 | #buttonText: CLOSE 134 | #buttonService: cover.close_cover 135 | #buttonData: 'cover.office_left_blinds,cover.office_right_blinds,cover.basement_shutter' 136 | countText: 'covers open' 137 | closedBaseline: 5 138 | icon: 'mdi:blinds' 139 | iconSize: 40px 140 | panelType: true 141 | showSidebar: true 142 | positionHeight: 300px 143 | positionWidth: 100px 144 | gapWidth: 50px 145 | switchHeight: 80px 146 | switchWidth: 100px 147 | showSwitch: true 148 | sideColor1: '#ffcccc' 149 | sideColor2: '#b30000' 150 | openColor: 'hsl(0, 0%, 20%, 0.8)' 151 | closedColor: 'hsl(0, 0%, 90%)' 152 | title: Covers 153 | titleSize: 40px 154 | entities: 155 | - entity: cover.office_left_blinds 156 | name: Left Blinds 157 | - entity: cover.office_right_blinds 158 | name: Right Blinds 159 | - entity: cover.basement_shutter 160 | name: Basement Shutter 161 | script: script.disable_alarm_for_5_min 162 | ``` 163 | 164 | **(1)** You might want to hide side bar (showSidebar: false) to have 2 or 3 covers in same card on mobile. 165 | 166 | **(2)** openBaseline - some covers stop at position 2 or 1 instead of 0. When set, any cover with current_position 167 | greater than *openBaseline* will be counted as open. 168 | 169 | **(3)** when declared, script will be called *simutaneously* with `cover.set_position` service (which is used to set cover to desired position). You might want to use this script option to, for example, temporarily disable mid-night open door alarm. 170 | 171 | Example of script: 172 | 173 | ```yaml 174 | disable_alarm_for_5_min: 175 | alias: Disarm alarm panel for 5 mins 176 | mode: single 177 | sequence: #Home Assistant >= 0.113 178 | #try to save current state of alarm_panel 179 | - service: scene.create 180 | data: 181 | scene_id: door_alarm_panel_state 182 | snapshot_entities: 183 | - alarm_control_panel.door_alarm 184 | - data: {} 185 | entity_id: alarm_control_panel.door_alarm 186 | service: alarm_control_panel.alarm_disarm 187 | - delay: '00:05:00' 188 | #set it back 189 | - scene: scene.door_alarm_panel_state 190 | icon: mdi:alarm-note-off 191 | ``` 192 | 193 | 194 | 195 | ## ISSUE AND SUGGESTION? 196 | 197 | Customize to suit your needs and contribute it back to the community. 198 | 199 | Found issue? Please raise an issue in this repository or send me email to 200 | 201 | Any suggestion and comment are warmly welcome and appreciated! 202 | 203 | ### MIGHT NOT WORK ON FIREFOX 204 | 205 | Have some appearance issues on Firefox. Please try with caution! 206 | 207 | ## MORE WORKS TO DO 208 | 209 | 1. Change hard-coded styles and clean codes (done somehow) 210 | 211 | 2. Remove unnecessary css and js blocks 212 | 213 | 3. Support input_number and light entities in Home Assistant 214 | 215 | ### Many Thanks to DBuit! 216 | 217 | This card is based on his lights-card at: https://github.com/DBuit/hass-smart-home-panel-card. 218 | 219 | ## Support (just for fun!) 220 | 221 | Hey dude! Help me out for a couple of :beers: or a :coffee: (:coffee: is preferred, have enough beers this year)! 222 | 223 | [![coffee](https://www.buymeacoffee.com/assets/img/custom_images/black_img.png)](https://www.buymeacoffee.com/wolverinevn) 224 | 225 | [maintenance-shield]: https://img.shields.io/maintenance/yes/2020.svg?style=for-the-badge 226 | [twitter]: https://twitter.com/KonnectedVN 227 | [site]: https://konnected.vn/home-assistant 228 | [license-shield]: https://img.shields.io/github/license/konnectedvn/lovelace-vertical-slider-cover-card.svg?style=for-the-badge&color=red 229 | [maintenance-shield]: https://img.shields.io/maintenance/yes/2020.svg?style=for-the-badge 230 | [releases-shield]: https://img.shields.io/github/v/release/konnectedvn/lovelace-vertical-slider-cover-card.svg?style=for-the-badge&color=red 231 | [releases]: https://github.com/konnectedvn/lovelace-vertical-slider-cover-card/releases 232 | -------------------------------------------------------------------------------- /vertical-slider-cover-card.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Author : duytruong 3 | * Github : https://github.com/konnectedvn 4 | * Description : 5 | * Date : 01 Feb 2021 08:44:30+07:00 6 | * Based on : github.com/DBuit/hass-smart-home-panel-card (Thanks to DBuit!) 7 | */ 8 | console.info("%c [konnected.vn] Vertical Slider Cover Card \n%c Version v0.1.5","color: red; font-weight: bold; background: black", "color: white; font-weight: bold; background: dimgray"); 9 | import { 10 | LitElement, 11 | html, 12 | css 13 | } from "https://cdn.jsdelivr.net/gh/lit/dist@3/core/lit-core.min.js"; 14 | //"https://unpkg.com/lit-element@2.0.1/lit-element.js?module" 15 | class VerticalSliderCoverCard extends LitElement { 16 | 17 | static get properties() { 18 | return { 19 | hass: {}, 20 | config: {}, 21 | active: {}, 22 | sliderVal: { type: Array } 23 | }; 24 | } 25 | 26 | constructor() { 27 | super(); 28 | this.sliderVal = []; 29 | } 30 | 31 | render() { 32 | var primaryTextColor = "var(--primary-text-color)"; 33 | var icon = this.config.icon ? this.config.icon : "mdi:blinds"; 34 | var iconSize = this.config.iconSize ? this.config.iconSize: "28px"; 35 | var iconColor = this.config.iconColor ? this.config.iconColor : primaryTextColor; 36 | 37 | var positionWidth = this.config.positionWidth ? this.config.positionWidth : "100px"; 38 | var positionHeight = this.config.positionHeight ? this.config.positionHeight : "300px"; 39 | var showName = this.config.showName ? this.config.showName : true; 40 | var showPosition = this.config.showPosition ? this.config.showPosition : true; 41 | var switchWidth = this.config.switchWidth ? this.config.switchWidth : positionWidth; 42 | var switchHeight = this.config.switchHeight ? this.config.switchHeight : switchWidth; 43 | var showSwitch = this.config.showSwitch; 44 | var switchFontColor = this.config.switchFontColor ? this.config.switchFontColor : primaryTextColor; 45 | var gapWidth = this.config.gapWidth ? this.config.gapWidth : "50px"; 46 | 47 | var countText = this.config.countText ? this.config.countText : "covers open"; 48 | var countTextFontColor = this.config.countTextFontColor ? this.config.countTextFontColor : primaryTextColor; 49 | var openBaseline = this.config.closedBaseline ? this.config.closedBaseline : 0; 50 | var entityCounter = 0; 51 | 52 | var showButton = this.config.showButton ? this.config.showButton : false; 53 | var buttonText = this.config.buttonText ? this.config.buttonText : "Home"; 54 | var buttonPath = this.config.buttonPath ? this.config.buttonPath : "/lovelace/0"; 55 | var buttonService = this.config.buttonService ? this.config.buttonService: ""; 56 | var buttonData = this.config.buttonData ? this.config.buttonData : ""; 57 | var buttonFontColor = this.config.buttonFontColor ? this.config.buttonFontColor : primaryTextColor; 58 | 59 | var background = this.config.background ? this.config.background : "transparent"; 60 | var sideColor1 = this.config.sideColor1 ? this.config.sideColor1 : "#ffcccc"; 61 | var sideColor2 = this.config.sideColor2 ? this.config.sideColor2 : '#b30000'; 62 | var switchColor = this.config.switchColor ? this.config.switchColor : sideColor2; 63 | var closedColor = this.config.closedColor ? this.config.closedColor : "hsl(0, 0%, 20%)"; 64 | var openColor = this.config.openColor ? this.config.openColor : "hsl(0, 0%, 90%, 0.6)"; 65 | var panelType = this.config.panelType; 66 | var showSidebar = this.config.showSidebar; 67 | var titleSize = this.config.titleSize ? this.config.titleSize : "40px"; 68 | var titleFontColor = this.config.titleFontColor ? this.config.titleFontColor : primaryTextColor; 69 | 70 | return html` 71 | 72 |
73 | 74 |
75 |
76 | 77 |
78 |
79 |
80 | 81 |
82 |

${this.config.title}

83 |

${this._stateCount(openBaseline)} ${countText}

84 |
85 |
86 | ${showButton ? html`` : html``} 87 |
88 |
89 | 90 |
91 |
92 | ${this.config.entities.map(ent => { 93 | entityCounter++; 94 | var switchValue = 0; 95 | const stateObj = this.hass.states[ent.entity]; 96 | switch(stateObj.state) { 97 | case 'open': 98 | switchValue = 100; 99 | break; 100 | case 'closed': 101 | switchValue = 0; 102 | break; 103 | default: 104 | switchValue = 0; 105 | } 106 | return stateObj ? html` 107 |
108 |
109 |

${ent.name || stateObj.attributes.friendly_name}

110 | ${stateObj.attributes.supported_features > 6 ? html` 111 |

${this._coverPosition(stateObj.state, stateObj.attributes.current_position, stateObj.entity_id)}

112 |
113 | this._sliderChange(e.target.value, stateObj.entity_id)}} @change=${e => this._setPosition(stateObj.entity_id, e.target.value, ent.script)}> 114 |
115 | ` : html` 116 |

${stateObj.state}

117 |
118 | this._switch(stateObj)}> 119 |
120 | `} 121 |
122 | this._switch(stateObj)} /> 123 | 124 |
125 |
126 |
127 | `: html``; 128 | })} 129 |
130 |
131 |
132 |
133 | `; 134 | } 135 | 136 | updated() {} 137 | 138 | _sliderChange(value, entity_id){ 139 | this.sliderVal[entity_id] = {val: value, active: true}; 140 | this.requestUpdate(); 141 | } 142 | 143 | _coverPosition(coverState, coverPos, entity_id){ 144 | if (coverState === "closed") { 145 | return '0'; 146 | } else if (typeof this.sliderVal[entity_id] === 'undefined' || !this.sliderVal[entity_id]['active']) { 147 | return Math.round(coverPos); 148 | } else { 149 | return this.sliderVal[entity_id]['val']; 150 | } 151 | } 152 | 153 | _setPosition(entity_id, value, script) { 154 | if (this.hass.states[entity_id].attributes.current_position === value) { 155 | return; 156 | } 157 | this.hass.callService("cover", "set_cover_position", { 158 | entity_id: entity_id, 159 | position: value 160 | }); 161 | this.sliderVal[entity_id]['active'] = false; 162 | if (script) { 163 | this.hass.callService("script", "turn_on", { 164 | entity_id: script 165 | }); 166 | } 167 | } 168 | 169 | _stateCount(baseline) { 170 | let count = 0; 171 | this.config.entities.map(ent => { 172 | const stateObj = this.hass.states[ent.entity]; 173 | if(stateObj.state === "open" && baseline === 0) { 174 | count++; 175 | } else if (stateObj.attributes.current_position >= baseline && baseline > 0) { 176 | count++; 177 | } 178 | }) 179 | return count; 180 | } 181 | 182 | _panelSize(panelType) { 183 | let sideWidth = 40; 184 | if (panelType === true) { 185 | sideWidth = 30; 186 | } 187 | return "--side-width:" + sideWidth + "px"; 188 | } 189 | 190 | _showFlex(showSidebar) { 191 | if (showSidebar === false) { 192 | return "none"; 193 | } 194 | return "flex"; 195 | } 196 | 197 | _showBlock(confValue) { 198 | if(confValue === false) { 199 | return "none"; 200 | } 201 | return "block"; 202 | } 203 | 204 | _coverSize(positionWidth, gapWidth, panelType) { 205 | if (panelType === false) { 206 | return (parseInt(positionWidth.replace(/px/,"")) + parseInt(gapWidth.replace(/px/,""))) + "px"; 207 | } else { 208 | return "50%"; 209 | } 210 | } 211 | 212 | _buttonFont(titleSize,buttonText) { 213 | let fieldSize = parseInt(titleSize.replace(/px/,"")) * this.config.title.length; 214 | let buttonSize = fieldSize / buttonText.length; 215 | return buttonSize * 0.5; 216 | } 217 | 218 | _centerSliders(panelType) { 219 | if (panelType === true) { 220 | return "0 auto"; 221 | } else { 222 | return "0"; 223 | } 224 | } 225 | 226 | _coverNameFont(positionWidth, gapWidth) { 227 | let maxLength = 0; 228 | this.config.entities.map(ent => { 229 | const stateObj = this.hass.states[ent.entity]; 230 | let name = ent.name || stateObj.attributes.friendly_name; 231 | if(name.length > maxLength) { 232 | maxLength = name.length; 233 | } 234 | }) 235 | let fontsize = parseInt(positionWidth.replace(/px/,"")); 236 | if (parseInt(gapWidth.replace(/px/,"")) > 50) { 237 | fontsize = fontsize + (parseInt(gapWidth.replace(/px/,"")) / 2); 238 | } else { 239 | fontsize = fontsize + parseInt(gapWidth.replace(/px/,"")); 240 | } 241 | return ((( fontsize - 4 ) / maxLength) * 1.8) | 0; 242 | } 243 | 244 | _switch(state) { 245 | this.hass.callService("cover", "stop_cover", { 246 | entity_id: state.entity_id 247 | }); 248 | } 249 | 250 | _navigate(path,service,data) { 251 | if (service.length === 0) { 252 | window.location.href = path; 253 | } else { 254 | let domain = service.split(".",2)[0]; 255 | let ser = service.split(".",2)[1]; 256 | this.hass.callService(domain,ser, { 257 | entity_id: data 258 | }); 259 | } 260 | } 261 | 262 | setConfig(config) { 263 | if (!config.entities) { 264 | throw new Error("You need to define entities"); 265 | } 266 | if (!config.title) { 267 | throw new Error("You need to define a title"); 268 | } 269 | for (var i = 0, len = config.entities.length; i < len; i++) { 270 | if (config.entities[i].entity === undefined) { 271 | throw new Error(config.entities[i] + " is INVALID! Should be object: - entity: " + config.entities[i] + "."); 272 | } 273 | } 274 | this.config = config; 275 | } 276 | getCardSize() { 277 | return this.config.entities.length + 1; 278 | } 279 | 280 | static get styles() { 281 | return css` 282 | :host([is-panel]) ha-card { 283 | left: 50; 284 | top: 0; 285 | width: 100%; 286 | height: 100%; 287 | position: absolute; 288 | } 289 | ha-card { 290 | overflow: hidden; 291 | width: 100%; 292 | height: 100%; 293 | display: flex; 294 | justify-content: center; 295 | } 296 | .page { 297 | width:100%; 298 | height:100%; 299 | display:flex; 300 | flex-direction: row; 301 | } 302 | .page > .side { 303 | padding: 10px; 304 | width: var(--side-width)%; 305 | display:var(--show-sidebar); 306 | flex-direction:column; 307 | background: rgb(28,122,226); 308 | background: linear-gradient(145deg, var(--sideColor-1) 0%, var(--sideColor-2) 90%); 309 | justify-content:space-between; 310 | } 311 | .side .header { 312 | } 313 | .side .center { 314 | display:flex; 315 | flex-direction:column; 316 | } 317 | .side .center .icon { 318 | display:block; 319 | overflow:hidden; 320 | text-align:center; 321 | } 322 | .side .center .icon ha-icon { 323 | color:var(--icon-color); 324 | } 325 | .side .center h1 { 326 | color:var(--title-font-color); 327 | margin:10px 0 0 5px; 328 | font-weight:400; 329 | font-size: var(--title-size); 330 | line-height: var(--title-size); 331 | text-align: center; 332 | } 333 | .side .center h3 { 334 | color:var(--count-font-color); 335 | margin:5px 0 5px 0; 336 | font-size: 120%; 337 | font-weight: 400; 338 | line-height: 100%; 339 | text-align: center; 340 | } 341 | 342 | .side .bottom { 343 | } 344 | 345 | .back-btn { 346 | border:2px solid var(--button-font-color); 347 | color:var(--button-font-color); 348 | background:transparent; 349 | font-size:var(--button-size); 350 | border-radius: 4px; 351 | width:100%; 352 | display:block; 353 | padding: 10px 0; 354 | } 355 | 356 | .page > .main { 357 | width:100%; 358 | overflow-x:scroll; 359 | padding-bottom: 0px; 360 | -ms-overflow-style: none; /* IE and Edge */ 361 | scrollbar-width: none; /* Firefox */ 362 | } 363 | .page > .main::-webkit-scrollbar { 364 | display: none; 365 | } 366 | .page > .main > .inner-main { 367 | display:flex; 368 | flex-direction:row; 369 | align-items:center; 370 | height:100%; 371 | margin: auto; 372 | padding-right: 0px; 373 | } 374 | .page > .main > .inner-main > .cover { 375 | width: var(--cover-width); 376 | display:inline-block; 377 | margin: var(--center-slider); 378 | padding-bottom: 0px; 379 | } 380 | 381 | .cover .icon { 382 | margin: 0 auto; 383 | text-align:center; 384 | display:block; 385 | height: 50px; 386 | width: 50px; 387 | color: rgba(255,255,255,0.3); 388 | font-size: 30px; 389 | padding-top:5px; 390 | } 391 | .cover .icon ha-icon { 392 | width: 30px; 393 | height: 30px; 394 | text-align:center; 395 | } 396 | .cover .icon.on ha-icon { 397 | fill: #f7d959; 398 | } 399 | h2 { 400 | color: #FFF; 401 | display: block; 402 | font-weight: 300; 403 | margin-bottom: 10px; 404 | text-align: center; 405 | font-size:20px; 406 | margin-top:0; 407 | } 408 | 409 | h3 { 410 | color: #FFF; 411 | display: block; 412 | font-weight: 300; 413 | margin-top: 5px; 414 | margin-bottom: 5px; 415 | text-align: center; 416 | font-size:18px; 417 | } 418 | 419 | .cover-name { 420 | display: var(--show-name); 421 | font-weight: 300; 422 | margin-top: calc(var(--cover-fontSize) / 3); 423 | margin-bottom: calc(var(--cover-fontSize) / 2); 424 | text-align: center; 425 | font-size: var(--cover-fontSize); 426 | } 427 | .cover-position { 428 | display: var(--show-position); 429 | font-weight: 300; 430 | margin-top: calc(var(--cover-fontsize) / 2); 431 | margin-bottom: var(--cover-fontsize); 432 | text-align: center; 433 | font-size: var(--cover-fontSize); 434 | } 435 | .cover-slider .cover-name, .cover-position { 436 | color: var(--primary-text-color); 437 | } 438 | 439 | h4 { 440 | color: var(--primary-text-color); 441 | display: block; 442 | font-weight: 300; 443 | margin-bottom: 20px; 444 | text-align: center; 445 | font-size:16px; 446 | margin-top:0; 447 | } 448 | .cover-position:after { 449 | content: "%"; 450 | padding-left: 1px; 451 | } 452 | 453 | .range-holder { 454 | height: var(--slider-height); 455 | position:relative; 456 | display: block; 457 | } 458 | .range-holder input[type="range"] { 459 | outline: 0; 460 | border: 0; 461 | border-radius: 4px; 462 | width: var(--slider-height); 463 | margin: 0; 464 | transition: box-shadow 0.2s ease-in-out; 465 | -webkit-transform:rotate(270deg); 466 | -moz-transform:rotate(270deg); 467 | -o-transform:rotate(270deg); 468 | -ms-transform:rotate(270deg); 469 | transform:rotate(270deg); 470 | overflow: hidden; 471 | height: var(--slider-width); 472 | -webkit-appearance: none; 473 | background-color: var(--closed-color); 474 | position: absolute; 475 | top: calc(50% - (var(--slider-width) / 2)); 476 | right: calc(50% - (var(--slider-height) / 2)); 477 | } 478 | .range-holder input[type="range"]::-webkit-slider-runnable-track { 479 | height: var(--slider-width); 480 | -webkit-appearance: none; 481 | color: var(--open-color); 482 | margin-top: 0px; 483 | transition: box-shadow 0.2s ease-in-out; 484 | } 485 | .range-holder input[type="range"]::-webkit-slider-thumb { 486 | width: calc((var(--slider-width) / 5) + 2px); 487 | border-right:8px solid var(--closed-color); 488 | border-left:8px solid var(--closed-color); 489 | border-top:20px solid var(--closed-color); 490 | border-bottom:20px solid var(--closed-color); 491 | -webkit-appearance: none; 492 | height: var(--slider-width); 493 | cursor: ew-resize; 494 | background: var(--closed-color); 495 | box-shadow: -350px 0 0 350px var(--open-color), inset 0 0 0 80px var(--open-color); 496 | border-radius: 0; 497 | transition: box-shadow 0.2s ease-in-out; 498 | position: relative; 499 | top: 0; 500 | } 501 | // .range-holder input[type="range"].on::-webkit-slider-thumb { 502 | // border-color: #1c7ae2; 503 | // box-shadow: -350px 0 0 350px #1c7ae2, inset 0 0 0 80px #FFF; 504 | // } 505 | 506 | .switch-holder { 507 | height: var(--switch-height); 508 | position:relative; 509 | display: block; 510 | } 511 | .switch-holder input[type="range"] { 512 | outline: 0; 513 | border: 0; 514 | border-radius: 4px; 515 | width: calc(var(--switch-height) - 20px); 516 | margin: 0; 517 | transition: box-shadow 0.2s ease-in-out; 518 | -webkit-transform: rotate(270deg); 519 | -moz-transform: rotate(270deg); 520 | -o-transform: rotate(270deg); 521 | -ms-transform: rotate(270deg); 522 | transform: rotate(270deg); 523 | overflow: hidden; 524 | height: calc(var(--switch-width) - 20px); 525 | -webkit-appearance: none; 526 | background-color: var(--switch-color); 527 | color: var(--switch-font-color); 528 | padding: 10px; 529 | position: absolute; 530 | top: calc(50% - (var(--switch-width) / 2)); 531 | right: calc(50% - (var(--switch-height) / 2)); 532 | } 533 | .switch-holder input[type="range"]::-webkit-slider-runnable-track { 534 | height: calc(var(--switch-width) - 20px); 535 | -webkit-appearance: none; 536 | color: var(--switch-color); 537 | margin-top: -1px; 538 | transition: box-shadow 0.2s ease-in-out; 539 | } 540 | .switch-holder input[type="range"]::-webkit-slider-thumb { 541 | width: calc(var(--switch-height) / 2); 542 | -webkit-appearance: none; 543 | height: calc(var(--switch-width) - 20px); 544 | cursor: ew-resize; 545 | background: var(--switch-color); 546 | color: var(--switch-font-color); 547 | transition: box-shadow 0.2s ease-in-out; 548 | box-shadow: -340px 0 0 350px #4d4d4d, inset 0 0 0 80px #969696; 549 | position: relative; 550 | top: 0; 551 | border-radius: 4px; 552 | } 553 | // .switch-holder input[type="range"].on::-webkit-slider-thumb { 554 | // box-shadow: -340px 0 0 350px #4d4d4d, inset 0 0 0 80px #1c7ae2; 555 | // } 556 | .toggle { 557 | margin-top: 20px; 558 | margin-bottom: 10px; 559 | display: var(--show-switch); 560 | align-items: center; 561 | justify-content: center; 562 | } 563 | .toggle > input.toggle-btn { 564 | display: none; 565 | } 566 | .toggle > input.toggle-btn + label { 567 | border: 1px solid #FFF; 568 | background: transparent; 569 | width: var(--switch-width); 570 | height: var(--switch-height); 571 | text-align:center; 572 | line-height: var(--switch-height); 573 | cursor: pointer; 574 | border-radius: 4px; 575 | color: var(--switch-font-color); 576 | display:block; 577 | font-size:var(--switch-labelSize); 578 | } 579 | .toggle > input.toggle-btn + label:active, 580 | .toggle > input.toggle-btn + label { 581 | background: var(--switch-color); 582 | border-color: var(--switch-color); 583 | } 584 | .toggle > input.toggle-btn + label> span:before { 585 | content: 'STOP'; 586 | } 587 | `; 588 | } 589 | 590 | } 591 | customElements.define('vertical-slider-cover-card', VerticalSliderCoverCard); 592 | --------------------------------------------------------------------------------