Advertisement
314ma

custom weather card

Feb 16th, 2021
1,314
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // modify lines: 219, 230, 242
  2. const LitElement = Object.getPrototypeOf(
  3.   customElements.get("ha-panel-lovelace")
  4. );
  5. const html = LitElement.prototype.html;
  6.  
  7. const weatherIconsDay = {
  8.   clear: "day",
  9.   "clear-night": "night",
  10.   cloudy: "cloudy",
  11.   fog: "cloudy",
  12.   hail: "rainy-7",
  13.   lightning: "thunder",
  14.   "lightning-rainy": "thunder",
  15.   partlycloudy: "cloudy-day-3",
  16.   pouring: "rainy-6",
  17.   rainy: "rainy-5",
  18.   snowy: "snowy-6",
  19.   "snowy-rainy": "rainy-7",
  20.   sunny: "day",
  21.   windy: "cloudy",
  22.   "windy-variant": "cloudy-day-3",
  23.   exceptional: "!!"
  24. };
  25.  
  26. const weatherIconsNight = {
  27.   ...weatherIconsDay,
  28.   clear: "night",
  29.   sunny: "night",
  30.   partlycloudy: "cloudy-night-3",
  31.   "windy-variant": "cloudy-night-3"
  32. };
  33.  
  34. const weatherNames = {
  35.   cloudy: "Pochmurno",
  36.   fog: "Mgła",
  37.   "clear-night": "Pogodnie",
  38.   hail: "Grad",
  39.   lightning: "Błyskawice",
  40.   "lightning-rainy": "Burza",
  41.   partlycloudy: "Częściowe zachmurzenie",
  42.   pouring: "Ulewa",
  43.   rainy: "Deszczowo",
  44.   snowy: "Śnieżnie",
  45.   "snowy-rainy": "Śnieżnie, deszczowo",
  46.   sunny: "Pogodnie",
  47.   windy: "Wietrznie",
  48.   "windy-variant": "Wietrznie"
  49. };
  50.  
  51. const windDirections = [
  52.   "N",
  53.   "NNE",
  54.   "NE",
  55.   "ENE",
  56.   "E",
  57.   "ESE",
  58.   "SE",
  59.   "SSE",
  60.   "S",
  61.   "SSW",
  62.   "SW",
  63.   "WSW",
  64.   "W",
  65.   "WNW",
  66.   "NW",
  67.   "NNW",
  68.   "N"
  69. ];
  70.  
  71. const fireEvent = (node, type, detail, options) => {
  72.   options = options || {};
  73.   detail = detail === null || detail === undefined ? {} : detail;
  74.   const event = new Event(type, {
  75.     bubbles: options.bubbles === undefined ? true : options.bubbles,
  76.     cancelable: Boolean(options.cancelable),
  77.     composed: options.composed === undefined ? true : options.composed
  78.   });
  79.   event.detail = detail;
  80.   node.dispatchEvent(event);
  81.   return event;
  82. };
  83.  
  84. function hasConfigOrEntityChanged(element, changedProps) {
  85.   if (changedProps.has("_config")) {
  86.     return true;
  87.   }
  88.  
  89.   const oldHass = changedProps.get("hass");
  90.   if (oldHass) {
  91.     return (
  92.       oldHass.states[element._config.entity] !==
  93.         element.hass.states[element._config.entity] ||
  94.       oldHass.states["sun.sun"] !== element.hass.states["sun.sun"]
  95.     );
  96.   }
  97.  
  98.   return true;
  99. }
  100.  
  101. class WeatherCard extends LitElement {
  102.   static get properties() {
  103.     return {
  104.       _config: {},
  105.       hass: {}
  106.     };
  107.   }
  108.  
  109.   static async getConfigElement() {
  110.     await import("./weather-card-editor.js");
  111.     return document.createElement("weather-card-editor");
  112.   }
  113.  
  114.   static getStubConfig() {
  115.     return {};
  116.   }
  117.  
  118.   setConfig(config) {
  119.     if (!config.entity) {
  120.       throw new Error("Please define a weather entity");
  121.     }
  122.     this._config = config;
  123.   }
  124.  
  125.   shouldUpdate(changedProps) {
  126.     return hasConfigOrEntityChanged(this, changedProps);
  127.   }
  128.  
  129.   render() {
  130.     if (!this._config || !this.hass) {
  131.       return html``;
  132.     }
  133.  
  134.     const stateObj = this.hass.states[this._config.entity];
  135.  
  136.     if (!stateObj) {
  137.       return html`
  138.         <style>
  139.           .not-found {
  140.             flex: 1;
  141.             background-color: yellow;
  142.             padding: 8px;
  143.           }
  144.         </style>
  145.         <ha-card>
  146.           <div class="not-found">
  147.             Entity not available: ${this._config.entity}
  148.           </div>
  149.         </ha-card>
  150.       `;
  151.     }
  152.  
  153.     const lang = this.hass.selectedLanguage || this.hass.language;
  154.  
  155.     const next_rising = new Date(
  156.       this.hass.states["sun.sun"].attributes.next_rising
  157.     );
  158.     const next_setting = new Date(
  159.       this.hass.states["sun.sun"].attributes.next_setting
  160.     );
  161.  
  162.     return html`
  163.       ${this.renderStyle()}
  164.       <ha-card @click="${this._handleClick}">
  165.         <span
  166.           class="icon bigger"
  167.           style="background: none, url(${
  168.            this.getWeatherIcon(
  169.              stateObj.state.toLowerCase(),
  170.              this.hass.states["sun.sun"].state
  171.            )
  172.          }) no-repeat; background-size: contain;"
  173.           >
  174.         </span>
  175.         <span class="title">${weatherNames[stateObj.state]}</span>
  176.         ${
  177.           this._config.name
  178.             ? html`
  179.                 <span class="title"> ${this._config.name} </span>
  180.               `
  181.             : ""
  182.         }
  183.         <span class="temp"
  184.           >${
  185.             this.getUnit("temperature") == "°F"
  186.               ? Math.round(stateObj.attributes.temperature)
  187.               : stateObj.attributes.temperature
  188.           }</span
  189.         >
  190.         <span class="tempc"> ${this.getUnit("temperature")}</span>
  191.         <span>
  192.           <ul class="variations">
  193.             <li>
  194.               <span class="ha-icon"
  195.                 ><ha-icon icon="mdi:water-percent"></ha-icon
  196.               ></span>
  197.               ${stateObj.attributes.humidity}<span class="unit"> % </span>
  198.               <br />
  199.               <span class="ha-icon"
  200.                 ><ha-icon icon="mdi:weather-windy"></ha-icon
  201.               ></span>
  202.               ${
  203.                 windDirections[
  204.                   parseInt((stateObj.attributes.wind_bearing + 11.25) / 22.5)
  205.                 ]
  206.               }
  207.               ${stateObj.attributes.wind_speed}<span class="unit">
  208.                 ${this.getUnit("length")}/h
  209.               </span>
  210.               <br />
  211.               <span class="ha-icon"
  212.                 ><ha-icon icon="mdi:weather-sunset-up"></ha-icon
  213.               ></span>
  214.               ${next_rising.toLocaleTimeString()}
  215.               <br />
  216.               <span class="ha-icon"
  217.                 ><ha-icon icon="mdi:smog"></ha-icon
  218.               ></span>
  219.               ${this.hass.states["sensor.looko2_ijpstring"].state}
  220.             </li>
  221.             <li>
  222.               <span class="ha-icon"><ha-icon icon="mdi:gauge"></ha-icon></span
  223.               >${stateObj.attributes.pressure}<span class="unit">
  224.                 ${this.getUnit("air_pressure")}
  225.               </span>
  226.               <br />
  227.               <span class="ha-icon"
  228.                 ><ha-icon icon="mdi:thermometer"></ha-icon
  229.               ></span>
  230.               ${this.hass.states["sensor.fibaro_door_sensor_1_temperature"].state}<span class="unit">
  231.                 ${this.getUnit("temperature")}
  232.               </span>
  233.               <br />
  234.               <span class="ha-icon"
  235.                 ><ha-icon icon="mdi:weather-sunset-down"></ha-icon
  236.               ></span>
  237.               ${next_setting.toLocaleTimeString()}
  238.               <br />
  239.               <span class="ha-icon"
  240.                 ><ha-icon icon="mdi:smog"></ha-icon
  241.               ></span>
  242.               ${this.hass.states["sensor.looko2_ijp"].state}
  243.             </li>
  244.           </ul>
  245.         </span>
  246.         ${
  247.           stateObj.attributes.forecast &&
  248.           stateObj.attributes.forecast.length > 0
  249.             ? html`
  250.                 <div class="forecast clear">
  251.                   ${
  252.                     stateObj.attributes.forecast.slice(0, 5).map(
  253.                       daily => html`
  254.                         <div class="day">
  255.                           <span class="dayname"
  256.                             >${
  257.                               new Date(daily.datetime).toLocaleDateString(
  258.                                 lang,
  259.                                 {
  260.                                   weekday: "short",
  261.                                   hour: "2-digit"
  262.                                 }
  263.                               )
  264.                             }</span
  265.                           >
  266.                           <br /><i
  267.                             class="icon"
  268.                             style="background: none, url(${
  269.                              this.getWeatherIcon(daily.condition.toLowerCase())
  270.                            }) no-repeat; background-size: contain;"
  271.                           ></i>
  272.                           <br /><span class="highTemp"
  273.                             >${daily.temperature}${
  274.                               this.getUnit("temperature")
  275.                             }</span
  276.                           >
  277.                           ${
  278.                             typeof daily.templow !== 'undefined'
  279.                               ? html`
  280.                                   <br /><span class="lowTemp"
  281.                                     >${daily.templow}${
  282.                                       this.getUnit("temperature")
  283.                                     }</span
  284.                                   >
  285.                                 `
  286.                               : ""
  287.                           }
  288.                         </div>
  289.                       `
  290.                     )
  291.                   }
  292.                 </div>
  293.               `
  294.             : ""
  295.         }
  296.       </ha-card>
  297.     `;
  298.   }
  299.  
  300.   getWeatherIcon(condition, sun) {
  301.     return `${
  302.       this._config.icons
  303.         ? this._config.icons
  304.         : "https://cdn.jsdelivr.net/gh/bramkragten/custom-ui@master/weather-card/icons/animated/"
  305.     }${
  306.       sun && sun == "below_horizon"
  307.         ? weatherIconsNight[condition]
  308.         : weatherIconsDay[condition]
  309.     }.svg`;
  310.   }
  311.  
  312.   getUnit(measure) {
  313.     const lengthUnit = this.hass.config.unit_system.length;
  314.     switch (measure) {
  315.       case "air_pressure":
  316.         return lengthUnit === "km" ? "hPa" : "inHg";
  317.       case "length":
  318.         return lengthUnit;
  319.       case "precipitation":
  320.         return lengthUnit === "km" ? "mm" : "in";
  321.       default:
  322.         return this.hass.config.unit_system[measure] || "";
  323.     }
  324.   }
  325.  
  326.   _handleClick() {
  327.     fireEvent(this, "hass-more-info", { entityId: this._config.entity });
  328.   }
  329.  
  330.   getCardSize() {
  331.     return 3;
  332.   }
  333.  
  334.   renderStyle() {
  335.     return html`
  336.       <style>
  337.         ha-card {
  338.           cursor: pointer;
  339.           margin: auto;
  340.           padding-top: 2.5em;
  341.           padding-bottom: 1.3em;
  342.           padding-left: 1em;
  343.           padding-right: 1em;
  344.           position: relative;
  345.         }
  346.  
  347.         .clear {
  348.           clear: both;
  349.         }
  350.  
  351.         .ha-icon {
  352.           height: 18px;
  353.           margin-right: 5px;
  354.           color: var(--paper-item-icon-color);
  355.         }
  356.  
  357.         .title {
  358.           position: absolute;
  359.           left: 4.5em;
  360.           top: 1.2em;
  361.           right: 4em;
  362.           font-weight: 300;
  363.           font-size: 2em;
  364.           color: var(--primary-text-color);
  365.         }
  366.         .temp {
  367.           font-weight: 300;
  368.           font-size: 4em;
  369.           color: var(--primary-text-color);
  370.           position: absolute;
  371.           right: 1em;
  372.           top: 0.3em;
  373.         }
  374.  
  375.         .tempc {
  376.           font-weight: 300;
  377.           font-size: 1.5em;
  378.           vertical-align: super;
  379.           color: var(--primary-text-color);
  380.           position: absolute;
  381.           right: 1em;
  382.           margin-top: -14px;
  383.           margin-right: 7px;
  384.         }
  385.  
  386.         .variations {
  387.           display: flex;
  388.           flex-flow: row wrap;
  389.           justify-content: space-between;
  390.           font-weight: 300;
  391.           color: var(--primary-text-color);
  392.           list-style: none;
  393.           margin-top: 4.5em;
  394.           padding: 0;
  395.         }
  396.  
  397.         .variations li {
  398.           flex-basis: auto;
  399.         }
  400.  
  401.         .variations li:first-child {
  402.           padding-left: 1em;
  403.         }
  404.  
  405.         .variations li:last-child {
  406.           padding-right: 1em;
  407.         }
  408.  
  409.         .unit {
  410.           font-size: 0.8em;
  411.         }
  412.  
  413.         .forecast {
  414.           width: 100%;
  415.           margin: 0 auto;
  416.           height: 9em;
  417.         }
  418.  
  419.         .day {
  420.           display: block;
  421.           width: 20%;
  422.           float: left;
  423.           text-align: center;
  424.           color: var(--primary-text-color);
  425.           border-right: 0.1em solid #d9d9d9;
  426.           line-height: 2;
  427.           box-sizing: border-box;
  428.         }
  429.  
  430.         .dayname {
  431.           text-transform: uppercase;
  432.         }
  433.  
  434.         .forecast .day:first-child {
  435.           margin-left: 0;
  436.         }
  437.  
  438.         .forecast .day:nth-last-child(1) {
  439.           border-right: none;
  440.           margin-right: 0;
  441.         }
  442.  
  443.         .highTemp {
  444.           font-weight: bold;
  445.         }
  446.  
  447.         .lowTemp {
  448.           color: var(--secondary-text-color);
  449.         }
  450.  
  451.         .icon.bigger {
  452.           width: 10em;
  453.           height: 10em;
  454.           margin-top: -4em;
  455.           position: absolute;
  456.           left: 0em;
  457.         }
  458.  
  459.         .icon {
  460.           width: 50px;
  461.           height: 50px;
  462.           margin-right: 5px;
  463.           display: inline-block;
  464.           vertical-align: middle;
  465.           background-size: contain;
  466.           background-position: center center;
  467.           background-repeat: no-repeat;
  468.           text-indent: -9999px;
  469.         }
  470.  
  471.         .weather {
  472.           font-weight: 300;
  473.           font-size: 1.5em;
  474.           color: var(--primary-text-color);
  475.           text-align: left;
  476.           position: absolute;
  477.           top: -0.5em;
  478.           left: 6em;
  479.           word-wrap: break-word;
  480.           width: 30%;
  481.         }
  482.       </style>
  483.     `;
  484.   }
  485. }
  486. customElements.define("weather-card", WeatherCard);
  487.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement