Commit b6a3e7121568a3d31b112fdc0a3a81edf2e95ff4
Committed by
Igor Kulikov
1 parent
75dec357
Feature/input widget webcamera (#1990)
* create widget and work to video * style widget stream photo and create preview photo * Add translate, save and style from widget
Showing
6 changed files
with
360 additions
and
1 deletions
@@ -276,6 +276,22 @@ | @@ -276,6 +276,22 @@ | ||
276 | "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"readOnly\": {\n \"title\": \"Value is read only\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"inputTypeNumber\": {\n \"title\": \"Datakey is a number\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"step\": {\n \"title\": \"Step interval between valid values (only for numbers)\",\n \"type\": \"number\",\n \"default\": \"1\"\n },\n \"icon\": {\n \"title\": \"Icon to show before input cell\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"readOnly\",\n \"inputTypeNumber\",\n \"step\",\n\t\t{\n \t\t\"key\": \"icon\",\n\t\t\t\"type\": \"icon\"\n\t\t},\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n }\n ]\n}\n", | 276 | "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"readOnly\": {\n \"title\": \"Value is read only\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"inputTypeNumber\": {\n \"title\": \"Datakey is a number\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"step\": {\n \"title\": \"Step interval between valid values (only for numbers)\",\n \"type\": \"number\",\n \"default\": \"1\"\n },\n \"icon\": {\n \"title\": \"Icon to show before input cell\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"readOnly\",\n \"inputTypeNumber\",\n \"step\",\n\t\t{\n \t\t\"key\": \"icon\",\n\t\t\t\"type\": \"icon\"\n\t\t},\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n }\n ]\n}\n", |
277 | "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.23592248334107624,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update Multiple Attributes\",\"dropShadow\":true,\"enableFullscreen\":false,\"enableDataExport\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" | 277 | "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.23592248334107624,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update Multiple Attributes\",\"dropShadow\":true,\"enableFullscreen\":false,\"enableDataExport\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" |
278 | } | 278 | } |
279 | + }, | ||
280 | + { | ||
281 | + "alias": "web_camera_input", | ||
282 | + "name": "Web Camera Input", | ||
283 | + "descriptor": { | ||
284 | + "type": "latest", | ||
285 | + "sizeX": 9.5, | ||
286 | + "sizeY": 6.5, | ||
287 | + "resources": [], | ||
288 | + "templateHtml": "<tb-web-camera-widget ctx=\"ctx\">\n</tb-web-camera-widget>", | ||
289 | + "templateCss": "#container {\n overflow: auto;\n}\n\n.tbDatasource-container {\n margin: 5px;\n padding: 8px;\n}\n\n.tbDatasource-title {\n font-size: 1.200rem;\n font-weight: 500;\n padding-bottom: 10px;\n}\n\n.tbDatasource-table {\n width: 100%;\n box-shadow: 0 0 10px #ccc;\n border-collapse: collapse;\n white-space: nowrap;\n font-size: 1.000rem;\n color: #757575;\n}\n\n.tbDatasource-table td {\n position: relative;\n border-top: 1px solid rgba(0, 0, 0, 0.12);\n border-bottom: 1px solid rgba(0, 0, 0, 0.12);\n padding: 0px 18px;\n box-sizing: border-box;\n}", | ||
290 | + "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}\n\nself.onDataUpdated = function() {\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {\n}\n", | ||
291 | + "settingsSchema": "{}", | ||
292 | + "dataKeySettingsSchema": "{}\n", | ||
293 | + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Web Camera Input\",\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"displayTimewindow\":true,\"showLegend\":false,\"actions\":{}}" | ||
294 | + } | ||
279 | } | 295 | } |
280 | ] | 296 | ] |
281 | } | 297 | } |
@@ -25,6 +25,7 @@ import thingsboardEntitiesHierarchyWidget from '../widget/lib/entities-hierarchy | @@ -25,6 +25,7 @@ import thingsboardEntitiesHierarchyWidget from '../widget/lib/entities-hierarchy | ||
25 | import thingsboardExtensionsTableWidget from '../widget/lib/extensions-table-widget'; | 25 | import thingsboardExtensionsTableWidget from '../widget/lib/extensions-table-widget'; |
26 | import thingsboardDateRangeNavigatorWidget from '../widget/lib/date-range-navigator/date-range-navigator'; | 26 | import thingsboardDateRangeNavigatorWidget from '../widget/lib/date-range-navigator/date-range-navigator'; |
27 | import thingsboardMultipleInputWidget from '../widget/lib/multiple-input-widget'; | 27 | import thingsboardMultipleInputWidget from '../widget/lib/multiple-input-widget'; |
28 | +import thingsboardWebCameraInputWidget from '../widget/lib/web-camera-input-widget'; | ||
28 | 29 | ||
29 | import thingsboardRpcWidgets from '../widget/lib/rpc'; | 30 | import thingsboardRpcWidgets from '../widget/lib/rpc'; |
30 | 31 | ||
@@ -50,7 +51,8 @@ import thingsboardUtils from '../common/utils.service'; | @@ -50,7 +51,8 @@ import thingsboardUtils from '../common/utils.service'; | ||
50 | export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsboardLedLight, | 51 | export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsboardLedLight, |
51 | thingsboardTimeseriesTableWidget, thingsboardAlarmsTableWidget, thingsboardEntitiesTableWidget, | 52 | thingsboardTimeseriesTableWidget, thingsboardAlarmsTableWidget, thingsboardEntitiesTableWidget, |
52 | thingsboardEntitiesHierarchyWidget, thingsboardExtensionsTableWidget, thingsboardDateRangeNavigatorWidget, | 53 | thingsboardEntitiesHierarchyWidget, thingsboardExtensionsTableWidget, thingsboardDateRangeNavigatorWidget, |
53 | - thingsboardMultipleInputWidget, thingsboardRpcWidgets, thingsboardTypes, thingsboardUtils, TripAnimationWidget]) | 54 | + thingsboardMultipleInputWidget, thingsboardWebCameraInputWidget, thingsboardRpcWidgets, thingsboardTypes, |
55 | + thingsboardUtils, TripAnimationWidget]) | ||
54 | .factory('widgetService', WidgetService) | 56 | .factory('widgetService', WidgetService) |
55 | .name; | 57 | .name; |
56 | 58 |
@@ -1693,10 +1693,15 @@ | @@ -1693,10 +1693,15 @@ | ||
1693 | "entity-timeseries-required": "Entity timeseries is required", | 1693 | "entity-timeseries-required": "Entity timeseries is required", |
1694 | "not-allowed-entity": "Selected entity cannot have shared attributes", | 1694 | "not-allowed-entity": "Selected entity cannot have shared attributes", |
1695 | "no-attribute-selected": "No attribute is selected", | 1695 | "no-attribute-selected": "No attribute is selected", |
1696 | + "no-datakey-selected": "No datakey is selected", | ||
1696 | "no-entity-selected": "No entity selected", | 1697 | "no-entity-selected": "No entity selected", |
1698 | + "no-image": "No image", | ||
1699 | + "no-support-web-camera": "No supported web camera", | ||
1697 | "no-timeseries-selected": "No timeseries selected", | 1700 | "no-timeseries-selected": "No timeseries selected", |
1698 | "switch-attribute-value": "Switch entity attribute value", | 1701 | "switch-attribute-value": "Switch entity attribute value", |
1702 | + "switch-camera": "Switch camera", | ||
1699 | "switch-timeseries-value": "Switch entity timeseries value", | 1703 | "switch-timeseries-value": "Switch entity timeseries value", |
1704 | + "take-photo": "Take photo", | ||
1700 | "time": "Time", | 1705 | "time": "Time", |
1701 | "timeseries-not-allowed": "Timeseries parameter cannot be used in this widget", | 1706 | "timeseries-not-allowed": "Timeseries parameter cannot be used in this widget", |
1702 | "update-failed": "Update failed", | 1707 | "update-failed": "Update failed", |
1 | +/* | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +import './web-camera-input-widget.scss'; | ||
17 | + | ||
18 | +/* eslint-disable import/no-unresolved, import/default */ | ||
19 | +import webCameraWidgetTemplate from './web-camera-input-widget.tpl.html'; | ||
20 | +/* eslint-enable import/no-unresolved, import/default */ | ||
21 | + | ||
22 | +export default angular.module('thingsboard.widgets.webCameraWidget', []) | ||
23 | + .directive('tbWebCameraWidget', webCameraWidget) | ||
24 | + .name; | ||
25 | + | ||
26 | +/*@ngInject*/ | ||
27 | +function webCameraWidget() { | ||
28 | + return { | ||
29 | + restrict: "E", | ||
30 | + scope: true, | ||
31 | + bindToController: { | ||
32 | + ctx: '=' | ||
33 | + }, | ||
34 | + controller: WebCameraWidgetController, | ||
35 | + controllerAs: 'vm', | ||
36 | + templateUrl: webCameraWidgetTemplate | ||
37 | + }; | ||
38 | +} | ||
39 | + | ||
40 | +function WebCameraWidgetController($element, $scope, $window, types, utils, attributeService, Fullscreen) { | ||
41 | + let vm = this; | ||
42 | + | ||
43 | + vm.videoInput = []; | ||
44 | + vm.videoDevice = ""; | ||
45 | + vm.previewPhoto = ""; | ||
46 | + vm.isShowCamera = false; | ||
47 | + vm.isPreviewPhoto = false; | ||
48 | + | ||
49 | + let streamDevice = null; | ||
50 | + let indexWebCamera = 0; | ||
51 | + let videoElement = null; | ||
52 | + let canvas = null; | ||
53 | + let photoCamera = null; | ||
54 | + let dataKeyType = ""; | ||
55 | + | ||
56 | + vm.getStream = getStream; | ||
57 | + vm.createPhoto = createPhoto; | ||
58 | + vm.takePhoto = takePhoto; | ||
59 | + vm.switchWebCamera = switchWebCamera; | ||
60 | + vm.cancelPhoto = cancelPhoto; | ||
61 | + vm.closeCamera = closeCamera; | ||
62 | + vm.savePhoto = savePhoto; | ||
63 | + | ||
64 | + vm.isEntityDetected = false; | ||
65 | + vm.dataKeyDetected = false; | ||
66 | + vm.isCameraSupport = false; | ||
67 | + vm.isDeviceDetect = false; | ||
68 | + | ||
69 | + $scope.$watch('vm.ctx', function () { | ||
70 | + if (vm.ctx && vm.ctx.datasources && vm.ctx.datasources.length) { | ||
71 | + let datasource = vm.ctx.datasources[0]; | ||
72 | + if (datasource.type === types.datasourceType.entity) { | ||
73 | + if (datasource.entityType && datasource.entityId) { | ||
74 | + if (vm.ctx.settings.widgetTitle && vm.ctx.settings.widgetTitle.length) { | ||
75 | + $scope.titleTemplate = utils.customTranslation(vm.ctx.settings.widgetTitle, vm.ctx.settings.widgetTitle); | ||
76 | + } else { | ||
77 | + $scope.titleTemplate = vm.ctx.widgetConfig.title; | ||
78 | + } | ||
79 | + vm.isEntityDetected = true; | ||
80 | + } | ||
81 | + } | ||
82 | + if (datasource.dataKeys.length) { | ||
83 | + $scope.currentKey = datasource.dataKeys[0].name; | ||
84 | + dataKeyType = datasource.dataKeys[0].type; | ||
85 | + vm.dataKeyDetected = true; | ||
86 | + } | ||
87 | + if (hasGetUserMedia()) { | ||
88 | + vm.isCameraSupport = true; | ||
89 | + getDevices().then(gotDevices).then(() => { | ||
90 | + vm.isDeviceDetect = !!vm.videoInput.length; | ||
91 | + }); | ||
92 | + } | ||
93 | + } | ||
94 | + }); | ||
95 | + | ||
96 | + function hasGetUserMedia() { | ||
97 | + return !!($window.navigator.mediaDevices && $window.navigator.mediaDevices.getUserMedia); | ||
98 | + } | ||
99 | + | ||
100 | + function takePhoto(){ | ||
101 | + vm.isShowCamera = true; | ||
102 | + videoElement = $element[0].querySelector('#videoStream'); | ||
103 | + photoCamera = $element[0].querySelector('#photoCamera'); | ||
104 | + canvas = $element[0].querySelector('canvas'); | ||
105 | + Fullscreen.enable(photoCamera); | ||
106 | + getStream(); | ||
107 | + } | ||
108 | + | ||
109 | + function cancelPhoto() { | ||
110 | + vm.isPreviewPhoto = false; | ||
111 | + vm.previewPhoto = ""; | ||
112 | + } | ||
113 | + | ||
114 | + function switchWebCamera() { | ||
115 | + indexWebCamera = (indexWebCamera+1)%vm.videoInput.length; | ||
116 | + vm.videoDevice = vm.videoInput[indexWebCamera].deviceId; | ||
117 | + getStream(); | ||
118 | + } | ||
119 | + | ||
120 | + function getDevices() { | ||
121 | + return $window.navigator.mediaDevices.enumerateDevices(); | ||
122 | + } | ||
123 | + | ||
124 | + function gotDevices(deviceInfos) { | ||
125 | + for (const deviceInfo of deviceInfos) { | ||
126 | + let device = { | ||
127 | + deviceId: deviceInfo.deviceId, | ||
128 | + label: "" | ||
129 | + }; | ||
130 | + if (deviceInfo.kind === 'videoinput') { | ||
131 | + device.label = deviceInfo.label || `Camera ${vm.videoInput.length + 1}`; | ||
132 | + vm.videoInput.push(device); | ||
133 | + } | ||
134 | + } | ||
135 | + } | ||
136 | + | ||
137 | + function getStream() { | ||
138 | + if (streamDevice !== null) { | ||
139 | + streamDevice.getTracks().forEach(track => { | ||
140 | + track.stop(); | ||
141 | + }); | ||
142 | + } | ||
143 | + const constraints = { | ||
144 | + video: {deviceId: vm.videoDevice !== "" ? {exact: vm.videoDevice} : undefined} | ||
145 | + }; | ||
146 | + return $window.navigator.mediaDevices.getUserMedia(constraints).then(gotStream); | ||
147 | + } | ||
148 | + | ||
149 | + function gotStream(stream) { | ||
150 | + streamDevice = stream; | ||
151 | + if(vm.videoDevice === ""){ | ||
152 | + indexWebCamera = vm.videoInput.findIndex(option => option.label === stream.getVideoTracks()[0].label); | ||
153 | + indexWebCamera = indexWebCamera === -1 ? 0 : indexWebCamera; | ||
154 | + vm.videoDevice = vm.videoInput[indexWebCamera].deviceId; | ||
155 | + } | ||
156 | + videoElement.srcObject = stream; | ||
157 | + } | ||
158 | + | ||
159 | + function createPhoto() { | ||
160 | + canvas.width = videoElement.videoWidth; | ||
161 | + canvas.height = videoElement.videoHeight; | ||
162 | + canvas.getContext('2d').drawImage(videoElement, 0, 0); | ||
163 | + vm.previewPhoto = canvas.toDataURL('image/png'); | ||
164 | + vm.isPreviewPhoto = true; | ||
165 | + } | ||
166 | + | ||
167 | + function closeCamera(){ | ||
168 | + Fullscreen.cancel(photoCamera); | ||
169 | + vm.isShowCamera = false; | ||
170 | + if (streamDevice !== null) { | ||
171 | + streamDevice.getTracks().forEach(track => { | ||
172 | + track.stop(); | ||
173 | + }); | ||
174 | + } | ||
175 | + streamDevice = null; | ||
176 | + videoElement.srcObject = null; | ||
177 | + } | ||
178 | + | ||
179 | + function savePhoto(){ | ||
180 | + let promiseData = null; | ||
181 | + let datasource = vm.ctx.datasources[0]; | ||
182 | + let saveData = [{ | ||
183 | + key: datasource.dataKeys[0].name, | ||
184 | + value: vm.previewPhoto | ||
185 | + }]; | ||
186 | + if(dataKeyType === types.dataKeyType.attribute){ | ||
187 | + promiseData = attributeService.saveEntityAttributes(datasource.entityType, datasource.entityId, types.attributesScope.server.value, saveData); | ||
188 | + } else if(dataKeyType === types.dataKeyType.timeseries){ | ||
189 | + promiseData = attributeService.saveEntityTimeseries(datasource.entityType, datasource.entityId, "scope", saveData); | ||
190 | + } | ||
191 | + promiseData.then(()=>{ | ||
192 | + vm.isPreviewPhoto = false; | ||
193 | + closeCamera(); | ||
194 | + }) | ||
195 | + } | ||
196 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +.tb-web-camera { | ||
18 | + height: 100%; | ||
19 | + | ||
20 | + &__last-photo{ | ||
21 | + width: 100%; | ||
22 | + margin: 5px 0; | ||
23 | + text-align: center; | ||
24 | + border: solid 1px; | ||
25 | + | ||
26 | + &_text{ | ||
27 | + position: absolute; | ||
28 | + top: 50%; | ||
29 | + left: 50%; | ||
30 | + margin-top: -.625em; | ||
31 | + transform: translate(-50%, -50%); | ||
32 | + } | ||
33 | + | ||
34 | + &_img{ | ||
35 | + width: 100%; | ||
36 | + height: 100%; | ||
37 | + object-fit: contain; | ||
38 | + } | ||
39 | + } | ||
40 | + | ||
41 | + .camera { | ||
42 | + position: relative; | ||
43 | + width: 100%; | ||
44 | + height: 100%; | ||
45 | + overflow: hidden; | ||
46 | + | ||
47 | + .camera-stream{ | ||
48 | + display: block; | ||
49 | + width: 100%; | ||
50 | + height: 100%; | ||
51 | + object-fit: contain; | ||
52 | + } | ||
53 | + | ||
54 | + .camera-controls { | ||
55 | + position: absolute; | ||
56 | + bottom: 0; | ||
57 | + width: 100%; | ||
58 | + padding: 0 5px 5px; | ||
59 | + } | ||
60 | + } | ||
61 | + | ||
62 | + .message-text{ | ||
63 | + font-size: 18px; | ||
64 | + color: #a0a0a0; | ||
65 | + text-align: center; | ||
66 | + } | ||
67 | +} |
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2019 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<div layout="column" layout-align="center center" class="tb-web-camera"> | ||
19 | + <div ng-if="vm.isEntityDetected && vm.dataKeyDetected && vm.isCameraSupport && vm.isDeviceDetect" layout-fill> | ||
20 | + <div ng-show="!vm.isShowCamera" layout="column" layout-align="space-between center" layout-fill> | ||
21 | + <div class="tb-web-camera__last-photo" flex> | ||
22 | + <span ng-show="!vm.ctx.data[0].data[0][1]" class="tb-web-camera__last-photo_text" translate>widgets.input-widgets.no-image</span> | ||
23 | + <img ng-show="vm.ctx.data[0].data[0][1]" class="tb-web-camera__last-photo_img" ng-src="{{vm.ctx.data[0].data[0][1]}}" /> | ||
24 | + </div> | ||
25 | + <md-button class="md-raised md-primary" ng-click="vm.takePhoto()"">{{ "widgets.input-widgets.take-photo" | translate }}</md-button> | ||
26 | + </div> | ||
27 | + <div ng-show="vm.isShowCamera" layout="column" layout-align="center center" id="photoCamera"> | ||
28 | + <div class="camera" ng-show="!vm.isPreviewPhoto"> | ||
29 | + <video autoplay muted playsinline id="videoStream" class="camera-stream"></video> | ||
30 | + <div class="camera-controls" layout="row" layout-wrap="" layout-align="space-between end"> | ||
31 | + <div flex></div> | ||
32 | + <md-button class="md-fab md-primary md-mini" aria-label="{{ 'widgets.input-widgets.switch-camera' | translate }}" | ||
33 | + ng-click="vm.switchWebCamera()" ng-disabled="{{vm.videoInput.length < 2}}"> | ||
34 | + <md-icon md-font-icon="switch_camera" aaria-label="{{ 'widgets.input-widgets.switch-camera' | translate }}">switch_camera</md-icon> | ||
35 | + </md-button> | ||
36 | + <md-button class="md-fab md-hue-2" aria-label="{{ 'widgets.input-widgets.take-photo' | translate }}" ng-click="vm.createPhoto()"> | ||
37 | + <md-icon md-font-icon="photo_camera" aria-label="Take Photo">photo_camera</md-icon> | ||
38 | + </md-button> | ||
39 | + <md-button class="md-fab md-primary md-mini" aria-label="{{ 'action.cancel' | translate }}" ng-click="vm.closeCamera()"> | ||
40 | + <md-icon md-font-icon="close" aria-label="{{ 'action.cancel' | translate }}">close</md-icon> | ||
41 | + </md-button> | ||
42 | + <div flex></div> | ||
43 | + </div> | ||
44 | + </div> | ||
45 | + <div class="camera" ng-show="vm.isPreviewPhoto"> | ||
46 | + <img class="camera-stream" ng-src="{{vm.previewPhoto}}"> | ||
47 | + <canvas style="display:none;"></canvas> | ||
48 | + <div class="camera-controls" layout="row" layout-wrap="" layout-align="space-between end"> | ||
49 | + <div flex></div> | ||
50 | + <md-button class="md-fab md-primary" aria-label="{{ 'action.cancel' | translate }}" ng-click="vm.cancelPhoto()"> | ||
51 | + <md-icon md-font-icon="close" aria-label="{{ 'action.cancel' | translate }}">close</md-icon> | ||
52 | + </md-button> | ||
53 | + <md-button class="md-fab md-hue-2" aria-label="{{ 'action.save' | translate }}" ng-click="vm.savePhoto()"> | ||
54 | + <md-icon md-font-icon="check" aria-label="{{ 'action.save' | translate }}">check</md-icon> | ||
55 | + </md-button> | ||
56 | + <div flex></div> | ||
57 | + </div> | ||
58 | + </div> | ||
59 | + </div> | ||
60 | + </div> | ||
61 | + <div class="message-text" ng-hide="vm.isEntityDetected"> | ||
62 | + {{ 'widgets.input-widgets.no-entity-selected' | translate }} | ||
63 | + </div> | ||
64 | + <div class="message-text" ng-if="vm.isEntityDetected && !vm.dataKeyDetected"> | ||
65 | + {{ 'widgets.input-widgets.no-datakey-selected' | translate }} | ||
66 | + </div> | ||
67 | + <div class="message-text" ng-if="vm.isEntityDetected && vm.dataKeyDetected && !vm.isCameraSupport"> | ||
68 | + {{ 'widgets.input-widgets.no-support-web-camera' | translate }} | ||
69 | + </div> | ||
70 | + <div class="message-text" ng-if="vm.isEntityDetected && vm.dataKeyDetected && vm.isCameraSupport && !vm.isDeviceDetect"> | ||
71 | + {{ 'widgets.input-widgets.no-support-web-camera' | translate }} | ||
72 | + </div> | ||
73 | +</div> |