Files
hassos_config/www/community/lovelace-vertical-slider-cover-card/vertical-slider-cover-card.js
2023-02-26 11:43:30 +01:00

590 lines
22 KiB
JavaScript

/*
* Author : duytruong
* Github : https://github.com/konnectedvn
* Description :
* Date : 01 Feb 2021 08:44:30+07:00
* Based on : github.com/DBuit/hass-smart-home-panel-card (Thanks to DBuit!)
*/
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");
import {
LitElement,
html,
css
} from "https://unpkg.com/lit-element@2.0.1/lit-element.js?module";
class VerticalSliderCoverCard extends LitElement {
static get properties() {
return {
hass: {},
config: {},
active: {},
sliderVal: { type: Array }
};
}
constructor() {
super();
this.sliderVal = [];
}
render() {
var primaryTextColor = "var(--primary-text-color)";
var icon = this.config.icon ? this.config.icon : "mdi:blinds";
var iconSize = this.config.iconSize ? this.config.iconSize: "28px";
var iconColor = this.config.iconColor ? this.config.iconColor : primaryTextColor;
var positionWidth = this.config.positionWidth ? this.config.positionWidth : "100px";
var positionHeight = this.config.positionHeight ? this.config.positionHeight : "300px";
var showName = this.config.showName ? this.config.showName : true;
var showPosition = this.config.showPosition ? this.config.showPosition : true;
var switchWidth = this.config.switchWidth ? this.config.switchWidth : positionWidth;
var switchHeight = this.config.switchHeight ? this.config.switchHeight : switchWidth;
var showSwitch = this.config.showSwitch;
var switchFontColor = this.config.switchFontColor ? this.config.switchFontColor : primaryTextColor;
var gapWidth = this.config.gapWidth ? this.config.gapWidth : "50px";
var countText = this.config.countText ? this.config.countText : "covers open";
var countTextFontColor = this.config.countTextFontColor ? this.config.countTextFontColor : primaryTextColor;
var openBaseline = this.config.closedBaseline ? this.config.closedBaseline : 0;
var entityCounter = 0;
var showButton = this.config.showButton ? this.config.showButton : false;
var buttonText = this.config.buttonText ? this.config.buttonText : "Home";
var buttonPath = this.config.buttonPath ? this.config.buttonPath : "/lovelace/0";
var buttonService = this.config.buttonService ? this.config.buttonService: "";
var buttonData = this.config.buttonData ? this.config.buttonData : "";
var buttonFontColor = this.config.buttonFontColor ? this.config.buttonFontColor : primaryTextColor;
var background = this.config.background ? this.config.background : "transparent";
var sideColor1 = this.config.sideColor1 ? this.config.sideColor1 : "#ffcccc";
var sideColor2 = this.config.sideColor2 ? this.config.sideColor2 : '#b30000';
var switchColor = this.config.switchColor ? this.config.switchColor : sideColor2;
var closedColor = this.config.closedColor ? this.config.closedColor : "hsl(0, 0%, 20%)";
var openColor = this.config.openColor ? this.config.openColor : "hsl(0, 0%, 90%, 0.6)";
var panelType = this.config.panelType;
var showSidebar = this.config.showSidebar;
var titleSize = this.config.titleSize ? this.config.titleSize : "40px";
var titleFontColor = this.config.titleFontColor ? this.config.titleFontColor : primaryTextColor;
return html`
<ha-card>
<div class="page" style="background:${background};">
<div class="side" style="${this._panelSize(panelType)};--show-sidebar:${this._showFlex(showSidebar)};--sideColor-1:${sideColor1};--sideColor-2:${sideColor2};">
<div class="header">
</div>
<div class="center">
<div class="icon">
<ha-icon style="--mdc-icon-size:${iconSize};--icon-color:${iconColor};" icon="${icon}" />
</div>
<h1 style="--title-size:${titleSize};--title-font-color:${titleFontColor};">${this.config.title}</h1>
<h3 style="--count-font-color:${countTextFontColor};">${this._stateCount(openBaseline)} ${countText}</h3>
</div>
<div class="bottom">
${showButton ? html`<button class="back-btn" style="--button-size:${this._buttonFont(titleSize,buttonText)}px;--button-font-color:${buttonFontColor};" @click=${e => this._navigate(buttonPath,buttonService,buttonData)}>${buttonText}</button>` : html``}
</div>
</div>
<div class="main">
<div class="inner-main">
${this.config.entities.map(ent => {
entityCounter++;
var switchValue = 0;
const stateObj = this.hass.states[ent.entity];
switch(stateObj.state) {
case 'open':
switchValue = 100;
break;
case 'closed':
switchValue = 0;
break;
default:
switchValue = 0;
}
return stateObj ? html`
<div class="cover" style="--cover-width:${this._coverSize(positionWidth,gapWidth,panelType)};--center-slider:${this._centerSliders(panelType)};">
<div class="cover-slider">
<p class="cover-name" style="--show-name: ${this._showBlock(showName)};--cover-fontSize: ${this._coverNameFont(positionWidth,gapWidth)}px;">${ent.name || stateObj.attributes.friendly_name}</p>
${stateObj.attributes.supported_features > 6 ? html`
<p class="cover-position" style="--show-position: ${this._showBlock(showPosition)};--cover-fontSize: ${parseInt(positionWidth.replace(/px/,"")) / 4 - (parseInt(positionWidth.replace(/px/,"")) - 60) / 4}px;">${this._coverPosition(stateObj.state, stateObj.attributes.current_position, stateObj.entity_id)}</p>
<div class="range-holder" style="--slider-height: ${positionHeight};--closed-color: ${closedColor};">
<input type="range" class="${stateObj.state}" style="--slider-width: ${positionWidth};--slider-height: ${positionHeight};--closed-color: ${closedColor};--open-color: ${openColor};" .value="${stateObj.state === "closed" ? 0 : Math.round(stateObj.attributes.current_position)}" @input=${e => this._sliderChange(e.target.value, stateObj.entity_id)}} @change=${e => this._setPosition(stateObj.entity_id, e.target.value, ent.script)}>
</div>
` : html`
<h4>${stateObj.state}</h4>
<div class="switch-holder" style="--switch-height: ${switchHeight};">
<input type="range" class="${stateObj.state}" style="--switch-width: ${switchWidth};--switch-height: ${switchHeight};" value="0" min="0" max="1" .value="${switchValue}" @change=${e => this._switch(stateObj)}>
</div>
`}
<div class="toggle" style="--show-switch: ${this._showFlex(showSwitch)};">
<input ?checked=${stateObj.state == "open"} type="checkbox" id="toggle${entityCounter}" class="toggle-btn" @change=${e => this._switch(stateObj)} />
<label for="toggle${entityCounter}" style="--switch-width: ${switchWidth};--switch-height: ${switchHeight};--switch-font-color: ${switchFontColor};--switch-color: ${switchColor};--switch-labelSize: ${parseInt(switchWidth.replace(/px/,"")) / 5}px;"><span></span></label>
</div>
</div>
</div>
`: html``;
})}
</div>
</div>
</div>
</ha-card>
`;
}
updated() {}
_sliderChange(value, entity_id){
this.sliderVal[entity_id] = {val: value, active: true};
this.requestUpdate();
}
_coverPosition(coverState, coverPos, entity_id){
if (coverState === "closed") {
return '0';
} else if (typeof this.sliderVal[entity_id] === 'undefined' || !this.sliderVal[entity_id]['active']) {
return Math.round(coverPos);
} else {
return this.sliderVal[entity_id]['val'];
}
}
_setPosition(entity_id, value, script) {
if (this.hass.states[entity_id].attributes.current_position === value) {
return;
}
this.hass.callService("cover", "set_cover_position", {
entity_id: entity_id,
position: value
});
this.sliderVal[entity_id]['active'] = false;
if (script) {
this.hass.callService("script", "turn_on", {
entity_id: script
});
}
}
_stateCount(baseline) {
let count = 0;
this.config.entities.map(ent => {
const stateObj = this.hass.states[ent.entity];
if(stateObj.state === "open" && baseline === 0) {
count++;
} else if (stateObj.attributes.current_position >= baseline && baseline > 0) {
count++;
}
})
return count;
}
_panelSize(panelType) {
let sideWidth = 40;
if (panelType === true) {
sideWidth = 30;
}
return "--side-width:" + sideWidth + "px";
}
_showFlex(showSidebar) {
if (showSidebar === false) {
return "none";
}
return "flex";
}
_showBlock(confValue) {
if(confValue === false) {
return "none";
}
return "block";
}
_coverSize(positionWidth, gapWidth, panelType) {
if (panelType === false) {
return (parseInt(positionWidth.replace(/px/,"")) + parseInt(gapWidth.replace(/px/,""))) + "px";
} else {
return "50%";
}
}
_buttonFont(titleSize,buttonText) {
let fieldSize = parseInt(titleSize.replace(/px/,"")) * this.config.title.length;
let buttonSize = fieldSize / buttonText.length;
return buttonSize * 0.5;
}
_centerSliders(panelType) {
if (panelType === true) {
return "0 auto";
} else {
return "0";
}
}
_coverNameFont(positionWidth, gapWidth) {
let maxLength = 0;
this.config.entities.map(ent => {
const stateObj = this.hass.states[ent.entity];
let name = ent.name || stateObj.attributes.friendly_name;
if(name.length > maxLength) {
maxLength = name.length;
}
})
let fontsize = parseInt(positionWidth.replace(/px/,""));
if (parseInt(gapWidth.replace(/px/,"")) > 50) {
fontsize = fontsize + (parseInt(gapWidth.replace(/px/,"")) / 2);
} else {
fontsize = fontsize + parseInt(gapWidth.replace(/px/,""));
}
return ((( fontsize - 4 ) / maxLength) * 1.8) | 0;
}
_switch(state) {
this.hass.callService("cover", "stop_cover", {
entity_id: state.entity_id
});
}
_navigate(path,service,data) {
if (service.length === 0) {
window.location.href = path;
} else {
let domain = service.split(".",2)[0];
let ser = service.split(".",2)[1];
this.hass.callService(domain,ser, {
entity_id: data
});
}
}
setConfig(config) {
if (!config.entities) {
throw new Error("You need to define entities");
}
if (!config.title) {
throw new Error("You need to define a title");
}
for (var i = 0, len = config.entities.length; i < len; i++) {
if (config.entities[i].entity === undefined) {
throw new Error(config.entities[i] + " is INVALID! Should be object: - entity: " + config.entities[i] + ".");
}
}
this.config = config;
}
getCardSize() {
return this.config.entities.length + 1;
}
static get styles() {
return css`
:host([is-panel]) ha-card {
left: 50;
top: 0;
width: 100%;
height: 100%;
position: absolute;
}
ha-card {
overflow: hidden;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
}
.page {
width:100%;
height:100%;
display:flex;
flex-direction: row;
}
.page > .side {
padding: 10px;
width: var(--side-width)%;
display:var(--show-sidebar);
flex-direction:column;
background: rgb(28,122,226);
background: linear-gradient(145deg, var(--sideColor-1) 0%, var(--sideColor-2) 90%);
justify-content:space-between;
}
.side .header {
}
.side .center {
display:flex;
flex-direction:column;
}
.side .center .icon {
display:block;
overflow:hidden;
text-align:center;
}
.side .center .icon ha-icon {
color:var(--icon-color);
}
.side .center h1 {
color:var(--title-font-color);
margin:10px 0 0 5px;
font-weight:400;
font-size: var(--title-size);
line-height: var(--title-size);
text-align: center;
}
.side .center h3 {
color:var(--count-font-color);
margin:5px 0 5px 0;
font-size: 120%;
font-weight: 400;
line-height: 100%;
text-align: center;
}
.side .bottom {
}
.back-btn {
border:2px solid var(--button-font-color);
color:var(--button-font-color);
background:transparent;
font-size:var(--button-size);
border-radius: 4px;
width:100%;
display:block;
padding: 10px 0;
}
.page > .main {
width:100%;
overflow-x:scroll;
padding-bottom: 0px;
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
.page > .main::-webkit-scrollbar {
display: none;
}
.page > .main > .inner-main {
display:flex;
flex-direction:row;
align-items:center;
height:100%;
margin: auto;
padding-right: 0px;
}
.page > .main > .inner-main > .cover {
width: var(--cover-width);
display:inline-block;
margin: var(--center-slider);
padding-bottom: 0px;
}
.cover .icon {
margin: 0 auto;
text-align:center;
display:block;
height: 50px;
width: 50px;
color: rgba(255,255,255,0.3);
font-size: 30px;
padding-top:5px;
}
.cover .icon ha-icon {
width: 30px;
height: 30px;
text-align:center;
}
.cover .icon.on ha-icon {
fill: #f7d959;
}
h2 {
color: #FFF;
display: block;
font-weight: 300;
margin-bottom: 10px;
text-align: center;
font-size:20px;
margin-top:0;
}
h3 {
color: #FFF;
display: block;
font-weight: 300;
margin-top: 5px;
margin-bottom: 5px;
text-align: center;
font-size:18px;
}
.cover-name {
display: var(--show-name);
font-weight: 300;
margin-top: calc(var(--cover-fontSize) / 3);
margin-bottom: calc(var(--cover-fontSize) / 2);
text-align: center;
font-size: var(--cover-fontSize);
}
.cover-position {
display: var(--show-position);
font-weight: 300;
margin-top: calc(var(--cover-fontsize) / 2);
margin-bottom: var(--cover-fontsize);
text-align: center;
font-size: var(--cover-fontSize);
}
.cover-slider .cover-name, .cover-position {
color: var(--primary-text-color);
}
h4 {
color: var(--primary-text-color);
display: block;
font-weight: 300;
margin-bottom: 20px;
text-align: center;
font-size:16px;
margin-top:0;
}
.cover-position:after {
content: "%";
padding-left: 1px;
}
.range-holder {
height: var(--slider-height);
position:relative;
display: block;
}
.range-holder input[type="range"] {
outline: 0;
border: 0;
border-radius: 4px;
width: var(--slider-height);
margin: 0;
transition: box-shadow 0.2s ease-in-out;
-webkit-transform:rotate(270deg);
-moz-transform:rotate(270deg);
-o-transform:rotate(270deg);
-ms-transform:rotate(270deg);
transform:rotate(270deg);
overflow: hidden;
height: var(--slider-width);
-webkit-appearance: none;
background-color: var(--closed-color);
position: absolute;
top: calc(50% - (var(--slider-width) / 2));
right: calc(50% - (var(--slider-height) / 2));
}
.range-holder input[type="range"]::-webkit-slider-runnable-track {
height: var(--slider-width);
-webkit-appearance: none;
color: var(--open-color);
margin-top: 0px;
transition: box-shadow 0.2s ease-in-out;
}
.range-holder input[type="range"]::-webkit-slider-thumb {
width: calc((var(--slider-width) / 5) + 2px);
border-right:8px solid var(--closed-color);
border-left:8px solid var(--closed-color);
border-top:20px solid var(--closed-color);
border-bottom:20px solid var(--closed-color);
-webkit-appearance: none;
height: var(--slider-width);
cursor: ew-resize;
background: var(--closed-color);
box-shadow: -350px 0 0 350px var(--open-color), inset 0 0 0 80px var(--open-color);
border-radius: 0;
transition: box-shadow 0.2s ease-in-out;
position: relative;
top: 0;
}
// .range-holder input[type="range"].on::-webkit-slider-thumb {
// border-color: #1c7ae2;
// box-shadow: -350px 0 0 350px #1c7ae2, inset 0 0 0 80px #FFF;
// }
.switch-holder {
height: var(--switch-height);
position:relative;
display: block;
}
.switch-holder input[type="range"] {
outline: 0;
border: 0;
border-radius: 4px;
width: calc(var(--switch-height) - 20px);
margin: 0;
transition: box-shadow 0.2s ease-in-out;
-webkit-transform: rotate(270deg);
-moz-transform: rotate(270deg);
-o-transform: rotate(270deg);
-ms-transform: rotate(270deg);
transform: rotate(270deg);
overflow: hidden;
height: calc(var(--switch-width) - 20px);
-webkit-appearance: none;
background-color: var(--switch-color);
color: var(--switch-font-color);
padding: 10px;
position: absolute;
top: calc(50% - (var(--switch-width) / 2));
right: calc(50% - (var(--switch-height) / 2));
}
.switch-holder input[type="range"]::-webkit-slider-runnable-track {
height: calc(var(--switch-width) - 20px);
-webkit-appearance: none;
color: var(--switch-color);
margin-top: -1px;
transition: box-shadow 0.2s ease-in-out;
}
.switch-holder input[type="range"]::-webkit-slider-thumb {
width: calc(var(--switch-height) / 2);
-webkit-appearance: none;
height: calc(var(--switch-width) - 20px);
cursor: ew-resize;
background: var(--switch-color);
color: var(--switch-font-color);
transition: box-shadow 0.2s ease-in-out;
box-shadow: -340px 0 0 350px #4d4d4d, inset 0 0 0 80px #969696;
position: relative;
top: 0;
border-radius: 4px;
}
// .switch-holder input[type="range"].on::-webkit-slider-thumb {
// box-shadow: -340px 0 0 350px #4d4d4d, inset 0 0 0 80px #1c7ae2;
// }
.toggle {
margin-top: 20px;
margin-bottom: 10px;
display: var(--show-switch);
align-items: center;
justify-content: center;
}
.toggle > input.toggle-btn {
display: none;
}
.toggle > input.toggle-btn + label {
border: 1px solid #FFF;
background: transparent;
width: var(--switch-width);
height: var(--switch-height);
text-align:center;
line-height: var(--switch-height);
cursor: pointer;
border-radius: 4px;
color: var(--switch-font-color);
display:block;
font-size:var(--switch-labelSize);
}
.toggle > input.toggle-btn + label:active,
.toggle > input.toggle-btn + label {
background: var(--switch-color);
border-color: var(--switch-color);
}
.toggle > input.toggle-btn + label> span:before {
content: 'STOP';
}
`;
}
}
customElements.define('vertical-slider-cover-card', VerticalSliderCoverCard);