[3.0] Add inputs widgets (#2526)
* Add location widget * Fix translate, clear code * Fix translate * Add date input widgets * Add image input widgets * Init web camera input widget * Add functional web camera input widget * Add styles to webcamera iputs widget * Add link code
This commit is contained in:
		
							parent
							
								
									adc7194669
								
							
						
					
					
						commit
						1af55e8400
					
				
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -0,0 +1,74 @@
 | 
			
		||||
<!--
 | 
			
		||||
 | 
			
		||||
    Copyright © 2016-2020 The Thingsboard Authors
 | 
			
		||||
 | 
			
		||||
    Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
    you may not use this file except in compliance with the License.
 | 
			
		||||
    You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
        http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
    distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
    See the License for the specific language governing permissions and
 | 
			
		||||
    limitations under the License.
 | 
			
		||||
 | 
			
		||||
-->
 | 
			
		||||
<div fxLayout="column" fxLayoutAlign="center center" class="tb-web-camera" tb-fullscreen [fullscreen]="isShowCamera">
 | 
			
		||||
  <div [fxShow]="isEntityDetected && dataKeyDetected && isCameraSupport && isDeviceDetect" fxFlexFill>
 | 
			
		||||
    <div [fxShow]="!isShowCamera" fxLayout="column" fxLayoutAlign="space-between center" fxFlexFill>
 | 
			
		||||
      <div class="tb-web-camera__last-photo" fxFlex>
 | 
			
		||||
        <span [fxShow]="!lastPhoto" class="tb-web-camera__last-photo_text" translate>widgets.input-widgets.no-image</span>
 | 
			
		||||
        <img [fxShow]="lastPhoto" class="tb-web-camera__last-photo_img" [src]="lastPhoto" alt="last photo"/>
 | 
			
		||||
      </div>
 | 
			
		||||
      <button mat-raised-button color="primary" (click)="takePhoto()">
 | 
			
		||||
        {{ "widgets.input-widgets.take-photo" | translate }}
 | 
			
		||||
      </button>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div [fxShow]="isShowCamera" fxLayout="column" fxLayoutAlign="center center" class="camera-container">
 | 
			
		||||
      <div class="camera" [fxShow]="!isPreviewPhoto">
 | 
			
		||||
        <video autoplay muted playsinline class="camera-stream" #videoStream></video>
 | 
			
		||||
        <div class="camera-controls" fxLayout="row wrap" fxLayoutAlign="space-between end">
 | 
			
		||||
          <div fxFlex></div>
 | 
			
		||||
          <button mat-mini-fab color="primary" (click)="switchWebCamera()" [disabled]="singleDevice">
 | 
			
		||||
            <mat-icon>switch_camera</mat-icon>
 | 
			
		||||
          </button>
 | 
			
		||||
          <button mat-fab color="accent" (click)="createPhoto()">
 | 
			
		||||
            <mat-icon>photo_camera</mat-icon>
 | 
			
		||||
          </button>
 | 
			
		||||
          <button mat-mini-fab color="primary" (click)="closeCamera()">
 | 
			
		||||
            <mat-icon>close</mat-icon>
 | 
			
		||||
          </button>
 | 
			
		||||
          <div fxFlex></div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="camera" [fxShow]="isPreviewPhoto">
 | 
			
		||||
        <img alt="preview photo" class="camera-stream" [src]="previewPhoto">
 | 
			
		||||
        <canvas #canvas style="display:none;"></canvas>
 | 
			
		||||
        <div class="camera-controls" fxLayout="row" fxLayoutAlign="space-between end">
 | 
			
		||||
          <div fxFlex></div>
 | 
			
		||||
          <button mat-fab color="primary" [disabled]="updatePhoto" (click)="cancelPhoto()">
 | 
			
		||||
            <mat-icon>close</mat-icon>
 | 
			
		||||
          </button>
 | 
			
		||||
          <button mat-fab color="accent" [disabled]="updatePhoto" (click)="savePhoto()">
 | 
			
		||||
            <mat-icon>check</mat-icon>
 | 
			
		||||
          </button>
 | 
			
		||||
          <div fxFlex></div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="message-text" [fxHide]="isEntityDetected">
 | 
			
		||||
    {{ 'widgets.input-widgets.no-entity-selected' | translate }}
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="message-text" [fxShow]="isEntityDetected && !dataKeyDetected">
 | 
			
		||||
    {{ 'widgets.input-widgets.no-datakey-selected' | translate }}
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="message-text" [fxShow]="isEntityDetected && dataKeyDetected && !isCameraSupport">
 | 
			
		||||
    {{ 'widgets.input-widgets.no-support-web-camera' | translate }}
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="message-text" [fxShow]="isEntityDetected && dataKeyDetected && isCameraSupport && !isDeviceDetect">
 | 
			
		||||
    {{ 'widgets.input-widgets.no-support-web-camera' | translate }}
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
@ -0,0 +1,74 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2020 The Thingsboard Authors
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
.tb-web-camera {
 | 
			
		||||
  height: 100%;
 | 
			
		||||
 | 
			
		||||
  &__last-photo {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    margin: 5px 0;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    border: solid 1px;
 | 
			
		||||
 | 
			
		||||
    &_text {
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      top: 50%;
 | 
			
		||||
      left: 50%;
 | 
			
		||||
      margin-top: -.625em;
 | 
			
		||||
      transform: translate(-50%, -50%);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &_img {
 | 
			
		||||
      width: 100%;
 | 
			
		||||
      height: 100%;
 | 
			
		||||
      object-fit: contain;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .camera-container{
 | 
			
		||||
    height: 100%;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .camera {
 | 
			
		||||
    position: relative;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
 | 
			
		||||
    .camera-stream {
 | 
			
		||||
      display: block;
 | 
			
		||||
      width: 100%;
 | 
			
		||||
      height: 100%;
 | 
			
		||||
      object-fit: contain;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .camera-controls {
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      bottom: 0;
 | 
			
		||||
      width: 100%;
 | 
			
		||||
      padding: 0 5px 5px;
 | 
			
		||||
 | 
			
		||||
      .mat-button-base{
 | 
			
		||||
        margin: 6px 8px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .message-text {
 | 
			
		||||
    font-size: 18px;
 | 
			
		||||
    color: #a0a0a0;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,274 @@
 | 
			
		||||
///
 | 
			
		||||
/// Copyright © 2016-2020 The Thingsboard Authors
 | 
			
		||||
///
 | 
			
		||||
/// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
/// you may not use this file except in compliance with the License.
 | 
			
		||||
/// You may obtain a copy of the License at
 | 
			
		||||
///
 | 
			
		||||
///     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
///
 | 
			
		||||
/// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
/// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
/// See the License for the specific language governing permissions and
 | 
			
		||||
/// limitations under the License.
 | 
			
		||||
///
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  Component,
 | 
			
		||||
  ElementRef,
 | 
			
		||||
  Inject,
 | 
			
		||||
  Input,
 | 
			
		||||
  NgZone,
 | 
			
		||||
  OnDestroy,
 | 
			
		||||
  OnInit,
 | 
			
		||||
  ViewChild,
 | 
			
		||||
  ViewEncapsulation
 | 
			
		||||
} from '@angular/core';
 | 
			
		||||
import { PageComponent } from '@shared/components/page.component';
 | 
			
		||||
import { WidgetContext } from '@home/models/widget-component.models';
 | 
			
		||||
import { Store } from '@ngrx/store';
 | 
			
		||||
import { AppState } from '@core/core.state';
 | 
			
		||||
import { Overlay } from '@angular/cdk/overlay';
 | 
			
		||||
import { UtilsService } from '@core/services/utils.service';
 | 
			
		||||
import { Datasource, DatasourceData, DatasourceType } from '@shared/models/widget.models';
 | 
			
		||||
import { WINDOW } from '@core/services/window.service';
 | 
			
		||||
import { AttributeService } from '@core/http/attribute.service';
 | 
			
		||||
import { EntityId } from '@shared/models/id/entity-id';
 | 
			
		||||
import { AttributeScope, DataKeyType } from '@shared/models/telemetry/telemetry.models';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
 | 
			
		||||
interface WebCameraInputWidgetSettings {
 | 
			
		||||
  widgetTitle: string;
 | 
			
		||||
  imageQuality: number;
 | 
			
		||||
  imageFormat: string;
 | 
			
		||||
  maxWidth: number;
 | 
			
		||||
  maxHeight: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'tb-web-camera-widget',
 | 
			
		||||
  templateUrl: './web-camera-input.component.html',
 | 
			
		||||
  styleUrls: ['./web-camera-input.component.scss'],
 | 
			
		||||
  encapsulation: ViewEncapsulation.None
 | 
			
		||||
})
 | 
			
		||||
export class WebCameraInputWidgetComponent extends PageComponent implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
  constructor(@Inject(WINDOW) private window: Window,
 | 
			
		||||
              protected store: Store<AppState>,
 | 
			
		||||
              private elementRef: ElementRef,
 | 
			
		||||
              private ngZone: NgZone,
 | 
			
		||||
              private overlay: Overlay,
 | 
			
		||||
              private utils: UtilsService,
 | 
			
		||||
              private attributeService: AttributeService,
 | 
			
		||||
  ) {
 | 
			
		||||
    super(store);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public get videoElement() {
 | 
			
		||||
    return this.videoStreamRef.nativeElement;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public get canvasElement() {
 | 
			
		||||
    return this.canvasRef.nativeElement;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public get videoWidth() {
 | 
			
		||||
    const videoRatio = this.getVideoAspectRatio();
 | 
			
		||||
    return Math.min(this.width, this.height * videoRatio);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public get videoHeight() {
 | 
			
		||||
    const videoRatio = this.getVideoAspectRatio();
 | 
			
		||||
    return Math.min(this.height, this.width / videoRatio);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static DEFAULT_IMAGE_TYPE = 'image/jpeg';
 | 
			
		||||
  private static DEFAULT_IMAGE_QUALITY = 0.92;
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  ctx: WidgetContext;
 | 
			
		||||
 | 
			
		||||
  @ViewChild('videoStream', {static: true}) videoStreamRef: ElementRef<HTMLVideoElement>;
 | 
			
		||||
  @ViewChild('canvas', {static: true}) canvasRef: ElementRef<HTMLCanvasElement>;
 | 
			
		||||
 | 
			
		||||
  private videoInputsIndex = 0;
 | 
			
		||||
  private settings: WebCameraInputWidgetSettings;
 | 
			
		||||
  private datasource: Datasource;
 | 
			
		||||
  private width = 640;
 | 
			
		||||
  private height = 480;
 | 
			
		||||
  private availableVideoInputs: MediaDeviceInfo[];
 | 
			
		||||
  private mediaStream: MediaStream;
 | 
			
		||||
 | 
			
		||||
  isEntityDetected = false;
 | 
			
		||||
  dataKeyDetected = false;
 | 
			
		||||
  isCameraSupport = false;
 | 
			
		||||
  isDeviceDetect = false;
 | 
			
		||||
  isShowCamera = false;
 | 
			
		||||
  isPreviewPhoto = false;
 | 
			
		||||
  singleDevice = true;
 | 
			
		||||
  updatePhoto = false;
 | 
			
		||||
  previewPhoto: any;
 | 
			
		||||
  lastPhoto: any;
 | 
			
		||||
 | 
			
		||||
  private static hasGetUserMedia(): boolean {
 | 
			
		||||
    return !!(window.navigator.mediaDevices && window.navigator.mediaDevices.getUserMedia);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static getAvailableVideoInputs(): Promise<MediaDeviceInfo[]> {
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
      navigator.mediaDevices.enumerateDevices()
 | 
			
		||||
        .then((devices: MediaDeviceInfo[]) => {
 | 
			
		||||
          resolve(devices.filter((device: MediaDeviceInfo) => device.kind === 'videoinput'));
 | 
			
		||||
        })
 | 
			
		||||
        .catch(err => {
 | 
			
		||||
          reject(err.message || err);
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.ctx.$scope.webCameraInputWidget = this;
 | 
			
		||||
    this.settings = this.ctx.settings;
 | 
			
		||||
    this.datasource = this.ctx.datasources[0];
 | 
			
		||||
 | 
			
		||||
    if (this.settings.widgetTitle && this.settings.widgetTitle.length) {
 | 
			
		||||
      this.ctx.widgetTitle = this.utils.customTranslation(this.settings.widgetTitle, this.settings.widgetTitle);
 | 
			
		||||
    } else {
 | 
			
		||||
      this.ctx.widgetTitle = this.ctx.widgetConfig.title;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.width = this.settings.maxWidth ? this.settings.maxWidth : 640;
 | 
			
		||||
    this.height = this.settings.maxHeight ? this.settings.maxWidth : 480;
 | 
			
		||||
 | 
			
		||||
    if (this.datasource.type === DatasourceType.entity) {
 | 
			
		||||
      if (this.datasource.entityType && this.datasource.entityId) {
 | 
			
		||||
        this.isEntityDetected = true;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (this.datasource.dataKeys.length) {
 | 
			
		||||
      this.dataKeyDetected = true;
 | 
			
		||||
    }
 | 
			
		||||
    this.detectAvailableDevices();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnDestroy(): void {
 | 
			
		||||
    this.stopMediaTracks();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private updateWidgetData(data: Array<DatasourceData>) {
 | 
			
		||||
    const keyData = data[0].data;
 | 
			
		||||
    if (keyData && keyData.length) {
 | 
			
		||||
      this.lastPhoto = keyData[0][1];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public onDataUpdated() {
 | 
			
		||||
    this.ngZone.run(() => {
 | 
			
		||||
      this.updateWidgetData(this.ctx.defaultSubscription.data);
 | 
			
		||||
      this.ctx.detectChanges();
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  private detectAvailableDevices(): void {
 | 
			
		||||
    if (WebCameraInputWidgetComponent.hasGetUserMedia()) {
 | 
			
		||||
      this.isCameraSupport = true;
 | 
			
		||||
      WebCameraInputWidgetComponent.getAvailableVideoInputs().then((devices) => {
 | 
			
		||||
          this.isDeviceDetect = !!devices.length;
 | 
			
		||||
          this.singleDevice = devices.length < 2;
 | 
			
		||||
          this.availableVideoInputs = devices;
 | 
			
		||||
          this.ctx.detectChanges();
 | 
			
		||||
        }, () => {
 | 
			
		||||
          this.availableVideoInputs = [];
 | 
			
		||||
        }
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private getVideoAspectRatio(): number {
 | 
			
		||||
    if (this.videoElement.videoWidth && this.videoElement.videoWidth > 0 &&
 | 
			
		||||
      this.videoElement.videoHeight && this.videoElement.videoHeight > 0) {
 | 
			
		||||
      return this.videoElement.videoWidth / this.videoElement.videoHeight;
 | 
			
		||||
    }
 | 
			
		||||
    return this.width / this.height;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private stopMediaTracks() {
 | 
			
		||||
    if (this.mediaStream && this.mediaStream.getTracks) {
 | 
			
		||||
      this.mediaStream.getTracks()
 | 
			
		||||
        .forEach((track: MediaStreamTrack) => track.stop());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  takePhoto() {
 | 
			
		||||
    this.isShowCamera = true;
 | 
			
		||||
    this.initWebCamera(this.availableVideoInputs[this.videoInputsIndex].deviceId);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  closeCamera() {
 | 
			
		||||
    this.stopMediaTracks();
 | 
			
		||||
    this.videoElement.srcObject = null;
 | 
			
		||||
    this.isShowCamera = false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  cancelPhoto() {
 | 
			
		||||
    this.isPreviewPhoto = false;
 | 
			
		||||
    this.previewPhoto = '';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  savePhoto() {
 | 
			
		||||
    this.updatePhoto = true;
 | 
			
		||||
    let task: Observable<any>;
 | 
			
		||||
    const entityId: EntityId = {
 | 
			
		||||
      entityType: this.datasource.entityType,
 | 
			
		||||
      id: this.datasource.entityId
 | 
			
		||||
    };
 | 
			
		||||
    const saveData = [{
 | 
			
		||||
      key: this.datasource.dataKeys[0].name,
 | 
			
		||||
      value: this.previewPhoto
 | 
			
		||||
    }];
 | 
			
		||||
    if (this.datasource.dataKeys[0].type === DataKeyType.attribute) {
 | 
			
		||||
      task = this.attributeService.saveEntityAttributes(entityId, AttributeScope.SERVER_SCOPE, saveData);
 | 
			
		||||
    } else if (this.datasource.dataKeys[0].type === DataKeyType.timeseries) {
 | 
			
		||||
      task = this.attributeService.saveEntityTimeseries(entityId, 'scope', saveData);
 | 
			
		||||
    }
 | 
			
		||||
    task.subscribe(() => {
 | 
			
		||||
      this.isPreviewPhoto = false;
 | 
			
		||||
      this.updatePhoto = false;
 | 
			
		||||
      this.closeCamera();
 | 
			
		||||
    }, () => {
 | 
			
		||||
      this.updatePhoto = false;
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  switchWebCamera() {
 | 
			
		||||
    this.videoInputsIndex = (this.videoInputsIndex + 1) % this.availableVideoInputs.length;
 | 
			
		||||
    this.stopMediaTracks();
 | 
			
		||||
    this.initWebCamera(this.availableVideoInputs[this.videoInputsIndex].deviceId)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  createPhoto() {
 | 
			
		||||
    this.canvasElement.width = this.videoWidth;
 | 
			
		||||
    this.canvasElement.height = this.videoHeight;
 | 
			
		||||
    this.canvasElement.getContext('2d').drawImage(this.videoElement, 0, 0, this.videoWidth, this.videoHeight);
 | 
			
		||||
 | 
			
		||||
    const mimeType: string = this.settings.imageFormat ? this.settings.imageFormat : WebCameraInputWidgetComponent.DEFAULT_IMAGE_TYPE;
 | 
			
		||||
    const quality: number = this.settings.imageQuality ? this.settings.imageQuality : WebCameraInputWidgetComponent.DEFAULT_IMAGE_QUALITY;
 | 
			
		||||
    this.previewPhoto = this.canvasElement.toDataURL(mimeType, quality);
 | 
			
		||||
    this.isPreviewPhoto = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private initWebCamera(deviceId?: string) {
 | 
			
		||||
    if (window.navigator.mediaDevices && window.navigator.mediaDevices.getUserMedia) {
 | 
			
		||||
      const videoTrackConstraints = {
 | 
			
		||||
        video: {deviceId: deviceId !== '' ? {exact: deviceId} : undefined}
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      window.navigator.mediaDevices.getUserMedia(videoTrackConstraints).then((stream: MediaStream) => {
 | 
			
		||||
        this.mediaStream = stream;
 | 
			
		||||
        this.videoElement.srcObject = stream;
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -31,6 +31,7 @@ import {
 | 
			
		||||
  DateRangeNavigatorWidgetComponent
 | 
			
		||||
} from '@home/components/widget/lib/date-range-navigator/date-range-navigator.component';
 | 
			
		||||
import { MultipleInputWidgetComponent } from './lib/multiple-input-widget.component';
 | 
			
		||||
import { WebCameraInputWidgetComponent } from './lib/web-camera-input.component';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
  declarations:
 | 
			
		||||
@ -43,7 +44,8 @@ import { MultipleInputWidgetComponent } from './lib/multiple-input-widget.compon
 | 
			
		||||
      EntitiesHierarchyWidgetComponent,
 | 
			
		||||
      DateRangeNavigatorWidgetComponent,
 | 
			
		||||
      DateRangeNavigatorPanelComponent,
 | 
			
		||||
      MultipleInputWidgetComponent
 | 
			
		||||
      MultipleInputWidgetComponent,
 | 
			
		||||
      WebCameraInputWidgetComponent
 | 
			
		||||
    ],
 | 
			
		||||
  imports: [
 | 
			
		||||
    CommonModule,
 | 
			
		||||
@ -58,7 +60,8 @@ import { MultipleInputWidgetComponent } from './lib/multiple-input-widget.compon
 | 
			
		||||
    EntitiesHierarchyWidgetComponent,
 | 
			
		||||
    RpcWidgetsModule,
 | 
			
		||||
    DateRangeNavigatorWidgetComponent,
 | 
			
		||||
    MultipleInputWidgetComponent
 | 
			
		||||
    MultipleInputWidgetComponent,
 | 
			
		||||
    WebCameraInputWidgetComponent
 | 
			
		||||
  ],
 | 
			
		||||
  providers: [
 | 
			
		||||
    CustomDialogService
 | 
			
		||||
 | 
			
		||||
@ -16,15 +16,15 @@
 | 
			
		||||
 | 
			
		||||
-->
 | 
			
		||||
<div class="tb-container">
 | 
			
		||||
  <label class="tb-title">{{label}}</label>
 | 
			
		||||
  <label class="tb-title" *ngIf="label">{{label}}</label>
 | 
			
		||||
  <ng-container #flow="flow"
 | 
			
		||||
                [flowConfig]="{singleFile: true, allowDuplicateUploads: true}">
 | 
			
		||||
    <div class="tb-image-select-container">
 | 
			
		||||
      <div class="tb-image-preview-container">
 | 
			
		||||
        <div *ngIf="!safeImageUrl" translate>dashboard.no-image</div>
 | 
			
		||||
        <img *ngIf="safeImageUrl" class="tb-image-preview" [src]="safeImageUrl" />
 | 
			
		||||
      <div *ngIf="showPreview" class="tb-image-preview-container">
 | 
			
		||||
        <div *ngIf="!safeImageUrl;else elseBlock" translate>dashboard.no-image</div>
 | 
			
		||||
        <ng-template #elseBlock><img class="tb-image-preview" [src]="safeImageUrl" /></ng-template>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="tb-image-clear-container">
 | 
			
		||||
      <div *ngIf="showClearButton" class="tb-image-clear-container">
 | 
			
		||||
        <button mat-button mat-icon-button color="primary"
 | 
			
		||||
                type="button"
 | 
			
		||||
                (click)="clearImage()"
 | 
			
		||||
@ -37,8 +37,8 @@
 | 
			
		||||
      <div class="drop-area tb-flow-drop"
 | 
			
		||||
           flowDrop
 | 
			
		||||
           [flow]="flow.flowJs">
 | 
			
		||||
        <label for="select" translate>dashboard.drop-image</label>
 | 
			
		||||
        <input class="file-input" flowButton type="file" [flow]="flow.flowJs" [flowAttributes]="{accept: 'image/*'}" id="select">
 | 
			
		||||
        <label for="{{inputId}}" translate>dashboard.drop-image</label>
 | 
			
		||||
        <input class="file-input" flowButton type="file" [flow]="flow.flowJs" [flowAttributes]="{accept: 'image/*'}" id="{{inputId}}">
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </ng-container>
 | 
			
		||||
 | 
			
		||||
@ -35,9 +35,9 @@ $previewSize: 100px !default;
 | 
			
		||||
 | 
			
		||||
  .tb-image-preview {
 | 
			
		||||
    width: auto;
 | 
			
		||||
    max-width: $previewSize;
 | 
			
		||||
    max-width: $previewSize - 2;
 | 
			
		||||
    height: auto;
 | 
			
		||||
    max-height: $previewSize;
 | 
			
		||||
    max-height: $previewSize - 2;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .tb-image-preview-container {
 | 
			
		||||
 | 
			
		||||
@ -14,34 +14,16 @@
 | 
			
		||||
/// limitations under the License.
 | 
			
		||||
///
 | 
			
		||||
 | 
			
		||||
import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild, AfterViewInit, OnDestroy } from '@angular/core';
 | 
			
		||||
import { AfterViewInit, Component, forwardRef, Input, OnDestroy, ViewChild } from '@angular/core';
 | 
			
		||||
import { PageComponent } from '@shared/components/page.component';
 | 
			
		||||
import { Store } from '@ngrx/store';
 | 
			
		||||
import { AppState } from '@core/core.state';
 | 
			
		||||
import { DataKey, DatasourceType } from '@shared/models/widget.models';
 | 
			
		||||
import {
 | 
			
		||||
  ControlValueAccessor,
 | 
			
		||||
  FormBuilder,
 | 
			
		||||
  FormControl,
 | 
			
		||||
  FormGroup,
 | 
			
		||||
  NG_VALIDATORS,
 | 
			
		||||
  NG_VALUE_ACCESSOR,
 | 
			
		||||
  Validator,
 | 
			
		||||
  Validators
 | 
			
		||||
} from '@angular/forms';
 | 
			
		||||
import { UtilsService } from '@core/services/utils.service';
 | 
			
		||||
import { TranslateService } from '@ngx-translate/core';
 | 
			
		||||
import { MatDialog } from '@angular/material/dialog';
 | 
			
		||||
import { EntityService } from '@core/http/entity.service';
 | 
			
		||||
import { DataKeysCallbacks } from '@home/components/widget/data-keys.component.models';
 | 
			
		||||
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
 | 
			
		||||
import { Observable, of, Subscription } from 'rxjs';
 | 
			
		||||
import { map, mergeMap, tap } from 'rxjs/operators';
 | 
			
		||||
import { alarmFields } from '@shared/models/alarm.models';
 | 
			
		||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR, } from '@angular/forms';
 | 
			
		||||
import { Subscription } from 'rxjs';
 | 
			
		||||
import { coerceBooleanProperty } from '@angular/cdk/coercion';
 | 
			
		||||
import { DialogService } from '@core/services/dialog.service';
 | 
			
		||||
import { FlowDirective } from '@flowjs/ngx-flow';
 | 
			
		||||
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
 | 
			
		||||
import { UtilsService } from '@core/services/utils.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'tb-image-input',
 | 
			
		||||
@ -61,9 +43,11 @@ export class ImageInputComponent extends PageComponent implements AfterViewInit,
 | 
			
		||||
  label: string;
 | 
			
		||||
 | 
			
		||||
  private requiredValue: boolean;
 | 
			
		||||
 | 
			
		||||
  get required(): boolean {
 | 
			
		||||
    return this.requiredValue;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  set required(value: boolean) {
 | 
			
		||||
    const newVal = coerceBooleanProperty(value);
 | 
			
		||||
@ -75,6 +59,15 @@ export class ImageInputComponent extends PageComponent implements AfterViewInit,
 | 
			
		||||
  @Input()
 | 
			
		||||
  disabled: boolean;
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  showClearButton = true;
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  showPreview = true;
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  inputId = this.utils.guid();
 | 
			
		||||
 | 
			
		||||
  imageUrl: string;
 | 
			
		||||
  safeImageUrl: SafeUrl;
 | 
			
		||||
 | 
			
		||||
@ -86,6 +79,7 @@ export class ImageInputComponent extends PageComponent implements AfterViewInit,
 | 
			
		||||
  private propagateChange = null;
 | 
			
		||||
 | 
			
		||||
  constructor(protected store: Store<AppState>,
 | 
			
		||||
              private utils: UtilsService,
 | 
			
		||||
              private sanitizer: DomSanitizer) {
 | 
			
		||||
    super(store);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -75,6 +75,7 @@
 | 
			
		||||
import './zone-flags';
 | 
			
		||||
import 'zone.js/dist/zone';  // Included with Angular CLI.
 | 
			
		||||
import 'core-js/es/array';
 | 
			
		||||
import moment from 'moment';
 | 
			
		||||
 | 
			
		||||
/***************************************************************************************************
 | 
			
		||||
 * APPLICATION IMPORTS
 | 
			
		||||
@ -99,6 +100,7 @@ const tinycolor = tinycolor_;
 | 
			
		||||
 | 
			
		||||
(window as any).tinycolor = tinycolor;
 | 
			
		||||
(window as any).cssjs = cssjs;
 | 
			
		||||
(window as any).moment = moment;
 | 
			
		||||
(window as any).TbFlot = TbFlot;
 | 
			
		||||
(window as any).TbAnalogueCompass = TbAnalogueCompass;
 | 
			
		||||
(window as any).TbAnalogueRadialGauge = TbAnalogueRadialGauge;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user