Ei kuvausta
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

surveillance-card.js 4.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. import {
  2. LitElement, html
  3. } from 'https://unpkg.com/@polymer/lit-element@^0.5.2/lit-element.js?module';
  4. import { repeat } from 'https://unpkg.com/lit-html@0.10.2/lib/repeat.js?module';
  5. class SurveillanceCard extends LitElement {
  6. /* eslint-disable indent,object-curly-newline */
  7. _render({ imageSources, currentCamera, lastMotion, updateInterval }) {
  8. const accessToken = currentCamera && this._hass.states[currentCamera].attributes.access_token;
  9. const template = html`
  10. <style>
  11. .container {
  12. height: 100%;
  13. width: 100%;
  14. display: flex;
  15. align-items: stretch;
  16. position: absolute;
  17. background: #000;
  18. }
  19. .thumbs {
  20. flex: 1;
  21. overflow-y: auto;
  22. position:relative;
  23. }
  24. .thumb > img {
  25. width: 100%;
  26. height: auto;
  27. border: 1px solid var(--primary-color);
  28. }
  29. .thumb {
  30. width: calc(100% - 9px);
  31. padding: 2px 4px;
  32. position: relative;
  33. }
  34. .thumb.motion > img {
  35. border-color: var(--accent-color);
  36. }
  37. img {
  38. display: block;
  39. }
  40. .mainImage {
  41. flex: 3;
  42. height: 100%;
  43. position: relative;
  44. display: flex;
  45. align-items: center;
  46. justify-content: center;
  47. overflow: hidden;
  48. }
  49. .mainImage > img {
  50. display: inline-block;
  51. max-width: 100%;
  52. max-height: 100%;
  53. }
  54. .loading {
  55. color: #FFF;
  56. text-align: center;
  57. font-size: 1.2rem;
  58. margin-top: 3rem;
  59. }
  60. </style>
  61. <div class="container">
  62. <div class="thumbs">
  63. ${imageSources ? repeat(this.cameras, (camera) => {
  64. const thumbClass = lastMotion && lastMotion === camera.motion ? 'thumb motion' : 'thumb';
  65. const source = this.imageSources[camera.entity];
  66. return html`
  67. <div class$="${thumbClass}" on-click="${() => { this.currentCamera = camera.entity; }}">
  68. <img src="${source || ''}" />
  69. </div>
  70. `;
  71. }) : html`<div class="loading">Loading Cameras...</div>`}
  72. </div>
  73. <div class="mainImage">
  74. <img src$="${currentCamera ? `/api/camera_proxy_stream/${currentCamera}?token=${accessToken}&interval=${updateInterval}` : ''}" />
  75. </div>
  76. </div>
  77. `;
  78. return template;
  79. }
  80. /* eslint-enable indent,object-curly-newline */
  81. static get properties() {
  82. return {
  83. _hass: Object,
  84. cameras: Array,
  85. currentCamera: String,
  86. imageSources: Object,
  87. lastMotion: String,
  88. thumbInterval: Number,
  89. updateInterval: Number
  90. };
  91. }
  92. setConfig(config) {
  93. this.cameras = config.cameras.map(c => ({
  94. entity: c.entity,
  95. motion: c.motion_entity
  96. }));
  97. this.currentCamera = this.cameras[0].entity;
  98. this.thumbInterval = (config.thumb_interval || 10) * 1000;
  99. this.updateInterval = config.update_interval || 1;
  100. this.focusMotion = config.focus_motion !== false;
  101. }
  102. set hass(hass) {
  103. this._hass = hass;
  104. for (const cam of this.cameras) {
  105. const { motion } = cam;
  106. if ((motion in hass.states) && hass.states[motion].state === 'on') {
  107. if (this.focusMotion && this.lastMotion !== motion) {
  108. this.currentCamera = cam.entity;
  109. }
  110. this.lastMotion = motion;
  111. return;
  112. }
  113. }
  114. this.lastMotion = null;
  115. }
  116. connectedCallback() {
  117. super.connectedCallback();
  118. this.thumbUpdater = setInterval(() => this._updateThumbs(), this.thumbInterval);
  119. }
  120. disconnectedCallback() {
  121. super.disconnectedCallback();
  122. clearInterval(this.thumbUpdater);
  123. this.imageSources = null;
  124. this.currentCamera = '';
  125. }
  126. _firstRendered() {
  127. this._updateThumbs();
  128. }
  129. async _updateCameraImageSrc(entity) {
  130. try {
  131. const { content_type: contentType, content } = await this._hass.callWS({
  132. type: 'camera_thumbnail',
  133. entity_id: entity,
  134. });
  135. return {
  136. entityId: entity,
  137. src: `data:${contentType};base64, ${content}`
  138. };
  139. } catch (err) {
  140. return {
  141. entityId: entity,
  142. src: null
  143. };
  144. }
  145. }
  146. _updateThumbs() {
  147. const sources = {};
  148. const promises = this.cameras.map(camera => this._updateCameraImageSrc(camera.entity));
  149. Promise.all(promises).then((vals) => {
  150. this.cameras.forEach((camera) => {
  151. const target = vals.find(val => val.entityId === camera.entity);
  152. sources[camera.entity] = target.src;
  153. });
  154. this.imageSources = sources;
  155. });
  156. }
  157. }
  158. customElements.define('surveillance-card', SurveillanceCard);