@dssp/supervision 1.0.0-alpha.41 → 1.0.0-alpha.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (22) hide show
  1. package/dist-client/pages/building-inspection/building-inspection-detail-camera.d.ts +33 -4
  2. package/dist-client/pages/building-inspection/building-inspection-detail-camera.js +456 -37
  3. package/dist-client/pages/building-inspection/building-inspection-detail-camera.js.map +1 -1
  4. package/dist-client/pages/checklist-template/checklist-template-item.js +14 -3
  5. package/dist-client/pages/checklist-template/checklist-template-item.js.map +1 -1
  6. package/dist-client/route.d.ts +1 -1
  7. package/dist-client/tsconfig.tsbuildinfo +1 -1
  8. package/dist-server/service/checklist-template-item/checklist-template-item-mutation.js +4 -1
  9. package/dist-server/service/checklist-template-item/checklist-template-item-mutation.js.map +1 -1
  10. package/dist-server/service/checklist-template-item/checklist-template-item-type.d.ts +2 -0
  11. package/dist-server/service/checklist-template-item/checklist-template-item-type.js +5 -0
  12. package/dist-server/service/checklist-template-item/checklist-template-item-type.js.map +1 -1
  13. package/dist-server/service/checklist-template-item/checklist-template-item.d.ts +6 -0
  14. package/dist-server/service/checklist-template-item/checklist-template-item.js +16 -1
  15. package/dist-server/service/checklist-template-item/checklist-template-item.js.map +1 -1
  16. package/dist-server/service/checklist-template-item/index.d.ts +1 -1
  17. package/dist-server/service/checklist-template-item/spec-matching-service.d.ts +2 -0
  18. package/dist-server/service/checklist-template-item/spec-matching-service.js +50 -0
  19. package/dist-server/service/checklist-template-item/spec-matching-service.js.map +1 -0
  20. package/dist-server/service/index.d.ts +1 -1
  21. package/dist-server/tsconfig.tsbuildinfo +1 -1
  22. package/package.json +4 -4
@@ -1,17 +1,40 @@
1
1
  import '@material/web/icon/icon.js';
2
2
  import '@operato/input/ox-input-switch.js';
3
3
  import '@operato/mini-map/ox-zoomable-image.js';
4
+ import '@material/web/progress/circular-progress.js';
4
5
  import { PropertyValues } from 'lit';
5
6
  import { PageView } from '@operato/shell';
6
7
  import { PageLifecycle } from '@operato/shell/dist/src/app/pages/page-view';
7
8
  import './component/building-inspection-detail-header';
8
9
  export declare class BuildingInspectionCamera extends PageView {
9
10
  static styles: import("lit").CSSResult[];
11
+ private KEYPOINT_RULER_API_BASE_URL;
10
12
  project: any;
11
13
  buildingInspection: any;
12
14
  buildingInspectionId: string;
13
- capturedImage: string | null;
14
- originImage: string | null;
15
+ capturedVideoUrl: string | null;
16
+ videoFile: File | null;
17
+ frame: any | null;
18
+ analyzing: boolean;
19
+ selectedPoints: {
20
+ x1: number | null;
21
+ y1: number | null;
22
+ x2: number | null;
23
+ y2: number | null;
24
+ };
25
+ frameNaturalWidth: number;
26
+ frameNaturalHeight: number;
27
+ displayedImageRect: {
28
+ width: number;
29
+ height: number;
30
+ left: number;
31
+ top: number;
32
+ } | null;
33
+ distance: number | null;
34
+ unit: string | null;
35
+ private frameImageEl;
36
+ private imageWrapperEl;
37
+ private cameraInputEl;
15
38
  get context(): {
16
39
  title: string;
17
40
  };
@@ -20,6 +43,12 @@ export declare class BuildingInspectionCamera extends PageView {
20
43
  pageUpdated(changes: any, lifecycle: PageLifecycle): Promise<void>;
21
44
  initBuildingInspection(buildingInspectionId?: string): Promise<void>;
22
45
  private _getProjectByBuildingComplexId;
23
- private toggleAI;
24
- private handleImageCapture;
46
+ private _onVideoSelected;
47
+ private _onAnalyzeVideo;
48
+ private _onRetry;
49
+ private _onImageLoad;
50
+ private _onImageClick;
51
+ private _hasTwoPoints;
52
+ private _renderMarkers;
53
+ private _onAnalyzePhoto;
25
54
  }
@@ -2,9 +2,10 @@ import { __decorate, __metadata } from "tslib";
2
2
  import '@material/web/icon/icon.js';
3
3
  import '@operato/input/ox-input-switch.js';
4
4
  import '@operato/mini-map/ox-zoomable-image.js';
5
+ import '@material/web/progress/circular-progress.js';
5
6
  import gql from 'graphql-tag';
6
7
  import { css, html } from 'lit';
7
- import { customElement, state } from 'lit/decorators.js';
8
+ import { customElement, state, query } from 'lit/decorators.js';
8
9
  import { PageView } from '@operato/shell';
9
10
  import { CommonGristStyles, ScrollbarStyles } from '@operato/styles';
10
11
  import { client } from '@operato/graphql';
@@ -12,11 +13,73 @@ import './component/building-inspection-detail-header';
12
13
  let BuildingInspectionCamera = class BuildingInspectionCamera extends PageView {
13
14
  constructor() {
14
15
  super(...arguments);
16
+ this.KEYPOINT_RULER_API_BASE_URL = 'https://hatiolab-korea-uni.ettisoft.com';
15
17
  this.project = {};
16
18
  this.buildingInspection = {};
17
19
  this.buildingInspectionId = '';
18
- this.capturedImage = null; // For storing the captured image
19
- this.originImage = null;
20
+ this.capturedVideoUrl = null;
21
+ this.videoFile = null;
22
+ this.frame = null;
23
+ this.analyzing = false;
24
+ this.selectedPoints = {
25
+ x1: null,
26
+ y1: null,
27
+ x2: null,
28
+ y2: null
29
+ };
30
+ this.frameNaturalWidth = 0;
31
+ this.frameNaturalHeight = 0;
32
+ this.displayedImageRect = null;
33
+ this.distance = null;
34
+ this.unit = null;
35
+ this._onImageLoad = () => {
36
+ if (!this.frameImageEl || !this.imageWrapperEl)
37
+ return;
38
+ const img = this.frameImageEl;
39
+ const wrapperRect = this.imageWrapperEl.getBoundingClientRect();
40
+ const imgRect = img.getBoundingClientRect();
41
+ this.frameNaturalWidth = img.naturalWidth;
42
+ this.frameNaturalHeight = img.naturalHeight;
43
+ this.displayedImageRect = {
44
+ width: imgRect.width,
45
+ height: imgRect.height,
46
+ left: imgRect.left - wrapperRect.left,
47
+ top: imgRect.top - wrapperRect.top
48
+ };
49
+ };
50
+ this._onImageClick = (event) => {
51
+ if (!this.frameImageEl || !this.imageWrapperEl)
52
+ return;
53
+ const imgRect = this.frameImageEl.getBoundingClientRect();
54
+ const wrapperRect = this.imageWrapperEl.getBoundingClientRect();
55
+ const clientX = event.clientX;
56
+ const clientY = event.clientY;
57
+ // Ignore clicks outside the displayed image area
58
+ if (clientX < imgRect.left || clientX > imgRect.right || clientY < imgRect.top || clientY > imgRect.bottom) {
59
+ return;
60
+ }
61
+ const relativeX = clientX - imgRect.left;
62
+ const relativeY = clientY - imgRect.top;
63
+ const xOnNatural = Math.round((relativeX * this.frameNaturalWidth) / imgRect.width);
64
+ const yOnNatural = Math.round((relativeY * this.frameNaturalHeight) / imgRect.height);
65
+ if (this.selectedPoints.x1 === null || this.selectedPoints.y1 === null) {
66
+ this.selectedPoints = Object.assign(Object.assign({}, this.selectedPoints), { x1: xOnNatural, y1: yOnNatural });
67
+ }
68
+ else if (this.selectedPoints.x2 === null || this.selectedPoints.y2 === null) {
69
+ this.selectedPoints = Object.assign(Object.assign({}, this.selectedPoints), { x2: xOnNatural, y2: yOnNatural });
70
+ }
71
+ else {
72
+ // 세 번째 클릭부터는 초기화 후 첫 점으로 설정
73
+ this.selectedPoints = { x1: xOnNatural, y1: yOnNatural, x2: null, y2: null };
74
+ }
75
+ // Update displayed rect cache (in case layout changed)
76
+ this.displayedImageRect = {
77
+ width: imgRect.width,
78
+ height: imgRect.height,
79
+ left: imgRect.left - wrapperRect.left,
80
+ top: imgRect.top - wrapperRect.top
81
+ };
82
+ };
20
83
  }
21
84
  get context() {
22
85
  return {
@@ -24,7 +87,7 @@ let BuildingInspectionCamera = class BuildingInspectionCamera extends PageView {
24
87
  };
25
88
  }
26
89
  render() {
27
- var _a, _b, _c, _d, _e, _f, _g, _h;
90
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
28
91
  return html `
29
92
  <building-inspection-detail-header
30
93
  .buildingInspectionId=${(_a = this.buildingInspection) === null || _a === void 0 ? void 0 : _a.id}
@@ -35,31 +98,58 @@ let BuildingInspectionCamera = class BuildingInspectionCamera extends PageView {
35
98
  ></building-inspection-detail-header>
36
99
 
37
100
  <div body>
38
- <!-- Display the captured image if available -->
101
+ <!-- Preview selected or recorded video -->
39
102
  <div preview>
40
- ${this.capturedImage
41
- ? html `<ox-zoomable-image src=${this.capturedImage} restrict-boundary></ox-zoomable-image>`
42
- : html `<md-icon>photo_camera</md-icon>`}
103
+ <!-- analyzing 로딩 중일 때 -->
104
+ ${this.analyzing
105
+ ? html `<div class="loading-container">
106
+ <md-circular-progress indeterminate></md-circular-progress>
107
+ <div class="loading-text">분석 중...</div>
108
+ </div>`
109
+ : // 동영상 분석 완료 단일 프레임 표시
110
+ this.frame
111
+ ? html `
112
+ <div class="image-wrapper" @click=${this._onImageClick}>
113
+ <img
114
+ id="frameImage"
115
+ src="${this.KEYPOINT_RULER_API_BASE_URL + ((_j = this.frame) === null || _j === void 0 ? void 0 : _j.download_url)}"
116
+ alt="frame"
117
+ loading="eager"
118
+ @load=${this._onImageLoad}
119
+ />
120
+ ${this._renderMarkers()}
121
+ </div>
122
+ `
123
+ : // 동영상 선택 후 동영상 프리뷰
124
+ this.capturedVideoUrl
125
+ ? html `<video src=${this.capturedVideoUrl} controls playsinline></video>`
126
+ : // 초기 상태, 동영상 선택 전
127
+ html `<md-icon>videocam</md-icon>`}
43
128
  </div>
44
129
 
45
130
  <div controls>
46
- <!-- AI toggle switch -->
47
- <div class="switch-container">
48
- <label>AI 기능</label>
49
- <ox-input-switch type="checkbox" @change=${this.toggleAI} round></ox-input-switch>
50
- </div>
131
+ <span class="controls-title">동영상 촬영</span>
51
132
 
52
- <!-- Camera shutter button -->
133
+ <!-- Camera shutter button (record video) -->
53
134
  <div class="camera-shutter">
54
135
  <md-icon>camera</md-icon>
55
- <input type="file" accept="image/*" capture @change="${this.handleImageCapture}" />
136
+ <input id="cameraInput" type="file" accept="video/*" capture @change="${this._onVideoSelected}" />
56
137
  </div>
57
138
 
58
139
  <!-- Action buttons -->
59
- <div class="action-buttons">
60
- <button class="save">저장</button>
61
- <button class="retry">재촬영</button>
62
- <button class="cancel" @click=${() => (this.capturedImage = this.originImage)}>취소</button>
140
+ <div action-buttons>
141
+ ${this.frame
142
+ ? html `
143
+ <button class="photo" ?disabled=${this.analyzing || !this._hasTwoPoints()} @click=${this._onAnalyzePhoto}>
144
+ 사진 분석
145
+ </button>
146
+ `
147
+ : html `
148
+ <button class="save" ?disabled=${this.analyzing || !this.capturedVideoUrl} @click=${this._onAnalyzeVideo}>
149
+ 동영상 분석
150
+ </button>
151
+ `}
152
+ <button class="retry" @click=${this._onRetry}>초기화</button>
63
153
  </div>
64
154
  </div>
65
155
  </div>
@@ -133,20 +223,162 @@ let BuildingInspectionCamera = class BuildingInspectionCamera extends PageView {
133
223
  return;
134
224
  this.project = response.data.project;
135
225
  }
136
- toggleAI(e) {
137
- const isChecked = e.target.checked;
138
- console.log(`AI 기능: ${isChecked ? 'ON' : 'OFF'}`);
139
- // Implement additional AI functionality toggling if needed
140
- }
141
- handleImageCapture(event) {
226
+ _onVideoSelected(event) {
227
+ var _a;
142
228
  const input = event.target;
143
- if (input.files && input.files[0]) {
144
- const reader = new FileReader();
145
- reader.onload = e => {
146
- var _a;
147
- this.capturedImage = (_a = e.target) === null || _a === void 0 ? void 0 : _a.result;
229
+ const file = (_a = input === null || input === void 0 ? void 0 : input.files) === null || _a === void 0 ? void 0 : _a[0];
230
+ if (!file)
231
+ return;
232
+ // Only allow video files
233
+ if (!file.type.startsWith('video/')) {
234
+ console.warn('선택된 파일이 동영상이 아닙니다.');
235
+ return;
236
+ }
237
+ // Revoke old object URL if exists
238
+ if (this.capturedVideoUrl) {
239
+ URL.revokeObjectURL(this.capturedVideoUrl);
240
+ }
241
+ this.videoFile = file;
242
+ this.capturedVideoUrl = URL.createObjectURL(file);
243
+ }
244
+ async _onAnalyzeVideo() {
245
+ var _a, _b, _c, _d, _e, _f, _g;
246
+ if (!this.videoFile) {
247
+ console.warn('선택된 동영상이 없습니다.');
248
+ return;
249
+ }
250
+ try {
251
+ this.analyzing = true;
252
+ const form = new FormData();
253
+ form.append('files', this.videoFile, this.videoFile.name);
254
+ const response = await fetch(`${this.KEYPOINT_RULER_API_BASE_URL}/api/run/keypoint-ruler`, {
255
+ method: 'POST',
256
+ headers: { Authorization: `Basic ${btoa('admin:admin1234')}` },
257
+ body: form
258
+ });
259
+ const data = await response.json();
260
+ this.frame = {
261
+ file_id: (_b = (_a = data === null || data === void 0 ? void 0 : data.result) === null || _a === void 0 ? void 0 : _a.output_files[0]) === null || _b === void 0 ? void 0 : _b.file_id,
262
+ download_url: (_d = (_c = data === null || data === void 0 ? void 0 : data.result) === null || _c === void 0 ? void 0 : _c.output_files[0]) === null || _d === void 0 ? void 0 : _d.download_url,
263
+ file_path: (_f = (_e = data === null || data === void 0 ? void 0 : data.result) === null || _e === void 0 ? void 0 : _e.frames[0]) === null || _f === void 0 ? void 0 : _f.file_path,
264
+ calibration: (_g = data === null || data === void 0 ? void 0 : data.result) === null || _g === void 0 ? void 0 : _g.calibration
148
265
  };
149
- reader.readAsDataURL(input.files[0]);
266
+ }
267
+ catch (error) {
268
+ console.error('동영상 업로드 실패:', error);
269
+ }
270
+ finally {
271
+ this.analyzing = false;
272
+ }
273
+ }
274
+ _onRetry() {
275
+ // Clear current selection and reopen camera
276
+ if (this.capturedVideoUrl) {
277
+ URL.revokeObjectURL(this.capturedVideoUrl);
278
+ }
279
+ this.capturedVideoUrl = null;
280
+ this.videoFile = null;
281
+ this.frame = null;
282
+ this.selectedPoints = { x1: null, y1: null, x2: null, y2: null };
283
+ this.displayedImageRect = null;
284
+ this.frameNaturalWidth = 0;
285
+ this.frameNaturalHeight = 0;
286
+ this.cameraInputEl.value = '';
287
+ this.distance = null;
288
+ this.unit = null;
289
+ }
290
+ _hasTwoPoints() {
291
+ const { x1, y1, x2, y2 } = this.selectedPoints;
292
+ return x1 !== null && y1 !== null && x2 !== null && y2 !== null;
293
+ }
294
+ _renderMarkers() {
295
+ if (!this.displayedImageRect)
296
+ return html ``;
297
+ const { left, top, width, height } = this.displayedImageRect;
298
+ const { x1, y1, x2, y2 } = this.selectedPoints;
299
+ const toDisplay = (x, y) => {
300
+ const dx = left + (x * width) / (this.frameNaturalWidth || 1);
301
+ const dy = top + (y * height) / (this.frameNaturalHeight || 1);
302
+ return { dx, dy };
303
+ };
304
+ return html `
305
+ ${x1 !== null && y1 !== null
306
+ ? (() => {
307
+ const p = toDisplay(x1, y1);
308
+ return html `<div class="point-marker point-1" style="left:${p.dx}px; top:${p.dy}px;"></div>`;
309
+ })()
310
+ : ''}
311
+ ${x2 !== null && y2 !== null
312
+ ? (() => {
313
+ const p = toDisplay(x2, y2);
314
+ return html `<div class="point-marker point-2" style="left:${p.dx}px; top:${p.dy}px;"></div>`;
315
+ })()
316
+ : ''}
317
+ ${this._hasTwoPoints()
318
+ ? (() => {
319
+ const p1 = toDisplay(x1, y1);
320
+ const p2 = toDisplay(x2, y2);
321
+ const x = Math.min(p1.dx, p2.dx);
322
+ const y = Math.min(p1.dy, p2.dy);
323
+ const w = Math.abs(p1.dx - p2.dx);
324
+ const h = Math.abs(p1.dy - p2.dy);
325
+ const cx = (p1.dx + p2.dx) / 2;
326
+ const cy = (p1.dy + p2.dy) / 2;
327
+ return html `
328
+ <svg class="line-overlay" style="left:${x}px; top:${y}px; width:${w}px; height:${h}px;">
329
+ <line
330
+ x1=${p1.dx < p2.dx ? 0 : w}
331
+ y1=${p1.dy < p2.dy ? 0 : h}
332
+ x2=${p1.dx < p2.dx ? w : 0}
333
+ y2=${p1.dy < p2.dy ? h : 0}
334
+ stroke="#ff9800"
335
+ stroke-width="3"
336
+ />
337
+ </svg>
338
+ ${this.distance !== null && this.unit
339
+ ? html `<div class="distance-label" style="left:${cx}px; top:${cy}px;">${this.distance} ${this.unit}</div>`
340
+ : ''}
341
+ `;
342
+ })()
343
+ : ''}
344
+ `;
345
+ }
346
+ async _onAnalyzePhoto() {
347
+ if (!this.frame) {
348
+ console.warn('분석할 사진이 없습니다.');
349
+ return;
350
+ }
351
+ if (!this._hasTwoPoints()) {
352
+ console.warn('두 점을 먼저 선택해 주세요.');
353
+ return;
354
+ }
355
+ try {
356
+ this.analyzing = true;
357
+ const payload = {
358
+ x1: this.selectedPoints.x1,
359
+ y1: this.selectedPoints.y1,
360
+ x2: this.selectedPoints.x2,
361
+ y2: this.selectedPoints.y2,
362
+ file_path: this.frame.file_path,
363
+ calibration: this.frame.calibration
364
+ };
365
+ const form = new FormData();
366
+ form.append('input', JSON.stringify(payload));
367
+ const response = await fetch(`${this.KEYPOINT_RULER_API_BASE_URL}/api/run/keypoint-ruler`, {
368
+ method: 'POST',
369
+ headers: { Authorization: `Basic ${btoa('admin:admin1234')}` },
370
+ body: form
371
+ });
372
+ const result = await response.json();
373
+ const { distance, unit } = result.result || {};
374
+ this.distance = distance;
375
+ this.unit = unit;
376
+ }
377
+ catch (error) {
378
+ console.error('사진 분석 실패:', error);
379
+ }
380
+ finally {
381
+ this.analyzing = false;
150
382
  }
151
383
  }
152
384
  };
@@ -156,11 +388,11 @@ BuildingInspectionCamera.styles = [
156
388
  css `
157
389
  :host {
158
390
  display: grid;
159
- grid-template-rows: 75px auto;
391
+ grid-template-rows: 75px 1fr;
160
392
  color: #4e5055;
161
393
  width: 100%;
394
+ height: 100%;
162
395
  background-color: #f7f7f7;
163
- overflow-y: auto;
164
396
  }
165
397
 
166
398
  div[body] {
@@ -168,6 +400,7 @@ BuildingInspectionCamera.styles = [
168
400
  justify-items: center;
169
401
  gap: var(--spacing-medium);
170
402
  margin: var(--spacing-medium);
403
+ min-height: 0;
171
404
  }
172
405
 
173
406
  div[preview] {
@@ -178,6 +411,8 @@ BuildingInspectionCamera.styles = [
178
411
  display: flex;
179
412
  justify-content: center;
180
413
  align-items: center;
414
+ min-height: 0;
415
+ overflow: hidden;
181
416
  }
182
417
 
183
418
  div[preview] img {
@@ -186,10 +421,131 @@ BuildingInspectionCamera.styles = [
186
421
  object-fit: contain;
187
422
  }
188
423
 
424
+ div[preview] video {
425
+ max-width: 100%;
426
+ max-height: 100%;
427
+ object-fit: contain;
428
+ }
429
+
189
430
  div[preview] md-icon {
190
431
  --md-icon-size: 160px;
191
432
  }
192
433
 
434
+ .loading-container {
435
+ display: flex;
436
+ flex-direction: column;
437
+ align-items: center;
438
+ justify-content: center;
439
+ gap: 12px;
440
+ }
441
+
442
+ .loading-text {
443
+ font-size: 14px;
444
+ color: #666;
445
+ }
446
+
447
+ .frame-container {
448
+ width: 100%;
449
+ height: 100%;
450
+ max-height: 100%;
451
+ min-height: 0;
452
+ overflow-y: auto;
453
+ display: flex;
454
+ flex-direction: column;
455
+ gap: 12px;
456
+ padding: 12px;
457
+ }
458
+
459
+ .frames-header {
460
+ display: flex;
461
+ align-items: center;
462
+ justify-content: center;
463
+ gap: 12px;
464
+ font-size: 20px;
465
+ font-weight: 700;
466
+ color: #333;
467
+ text-align: center;
468
+ padding-block: 8px;
469
+ }
470
+
471
+ .frames-count {
472
+ font-size: 16px;
473
+ color: #777;
474
+ font-weight: 600;
475
+ }
476
+
477
+ .frames-grid {
478
+ display: grid;
479
+ grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
480
+ gap: 12px;
481
+ }
482
+
483
+ .frame-item {
484
+ border: 1px solid #ddd;
485
+ border-radius: 8px;
486
+ overflow: hidden;
487
+ background: #fff;
488
+ cursor: pointer;
489
+ transition:
490
+ box-shadow 0.2s,
491
+ transform 0.1s;
492
+ }
493
+
494
+ .frame-item:hover {
495
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
496
+ transform: translateY(-1px);
497
+ }
498
+
499
+ .frame-item img {
500
+ display: block;
501
+ width: 100%;
502
+ height: 120px;
503
+ object-fit: cover;
504
+ }
505
+
506
+ .image-wrapper {
507
+ position: relative;
508
+ width: 100%;
509
+ height: 100%;
510
+ align-content: center;
511
+ }
512
+
513
+ .point-marker {
514
+ position: absolute;
515
+ width: 14px;
516
+ height: 14px;
517
+ border-radius: 50%;
518
+ border: 2px solid #fff;
519
+ transform: translate(-50%, -50%);
520
+ pointer-events: none;
521
+ box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.25);
522
+ }
523
+
524
+ .point-1 {
525
+ background-color: #e53935;
526
+ }
527
+
528
+ .point-2 {
529
+ background-color: #1e88e5;
530
+ }
531
+
532
+ .line-overlay {
533
+ position: absolute;
534
+ pointer-events: none;
535
+ }
536
+
537
+ .distance-label {
538
+ position: absolute;
539
+ background: rgba(0, 0, 0, 0.7);
540
+ color: #fff;
541
+ font-size: 14px;
542
+ padding: 2px 6px;
543
+ border-radius: 4px;
544
+ transform: translate(-50%, -120%);
545
+ pointer-events: none;
546
+ white-space: nowrap;
547
+ }
548
+
193
549
  div[controls] {
194
550
  width: 240px;
195
551
  display: flex;
@@ -202,6 +558,7 @@ BuildingInspectionCamera.styles = [
202
558
  display: flex;
203
559
  flex-direction: row;
204
560
  justify-content: space-between;
561
+ gap: 15px;
205
562
  }
206
563
 
207
564
  .switch-container {
@@ -226,6 +583,14 @@ BuildingInspectionCamera.styles = [
226
583
  position: relative;
227
584
  }
228
585
 
586
+ .controls-title {
587
+ font-size: 18px;
588
+ font-weight: 700;
589
+ color: #333;
590
+ margin-bottom: 8px;
591
+ text-align: center;
592
+ }
593
+
229
594
  .camera-shutter md-icon {
230
595
  --md-icon-size: 100px;
231
596
  }
@@ -251,6 +616,11 @@ BuildingInspectionCamera.styles = [
251
616
  button.save {
252
617
  background-color: #4caf50;
253
618
  color: white;
619
+
620
+ &:disabled {
621
+ background-color: #ccc;
622
+ cursor: default;
623
+ }
254
624
  }
255
625
 
256
626
  button.retry {
@@ -258,9 +628,14 @@ BuildingInspectionCamera.styles = [
258
628
  color: white;
259
629
  }
260
630
 
261
- button.cancel {
262
- background-color: #d9534f;
631
+ button.photo {
632
+ background-color: #1e88e5;
263
633
  color: white;
634
+
635
+ &:disabled {
636
+ background-color: #ccc;
637
+ cursor: default;
638
+ }
264
639
  }
265
640
  `
266
641
  ];
@@ -279,11 +654,55 @@ __decorate([
279
654
  __decorate([
280
655
  state(),
281
656
  __metadata("design:type", Object)
282
- ], BuildingInspectionCamera.prototype, "capturedImage", void 0);
657
+ ], BuildingInspectionCamera.prototype, "capturedVideoUrl", void 0);
658
+ __decorate([
659
+ state(),
660
+ __metadata("design:type", Object)
661
+ ], BuildingInspectionCamera.prototype, "videoFile", void 0);
662
+ __decorate([
663
+ state(),
664
+ __metadata("design:type", Object)
665
+ ], BuildingInspectionCamera.prototype, "frame", void 0);
666
+ __decorate([
667
+ state(),
668
+ __metadata("design:type", Boolean)
669
+ ], BuildingInspectionCamera.prototype, "analyzing", void 0);
283
670
  __decorate([
284
671
  state(),
285
672
  __metadata("design:type", Object)
286
- ], BuildingInspectionCamera.prototype, "originImage", void 0);
673
+ ], BuildingInspectionCamera.prototype, "selectedPoints", void 0);
674
+ __decorate([
675
+ state(),
676
+ __metadata("design:type", Number)
677
+ ], BuildingInspectionCamera.prototype, "frameNaturalWidth", void 0);
678
+ __decorate([
679
+ state(),
680
+ __metadata("design:type", Number)
681
+ ], BuildingInspectionCamera.prototype, "frameNaturalHeight", void 0);
682
+ __decorate([
683
+ state(),
684
+ __metadata("design:type", Object)
685
+ ], BuildingInspectionCamera.prototype, "displayedImageRect", void 0);
686
+ __decorate([
687
+ state(),
688
+ __metadata("design:type", Object)
689
+ ], BuildingInspectionCamera.prototype, "distance", void 0);
690
+ __decorate([
691
+ state(),
692
+ __metadata("design:type", Object)
693
+ ], BuildingInspectionCamera.prototype, "unit", void 0);
694
+ __decorate([
695
+ query('img#frameImage'),
696
+ __metadata("design:type", HTMLImageElement)
697
+ ], BuildingInspectionCamera.prototype, "frameImageEl", void 0);
698
+ __decorate([
699
+ query('div.image-wrapper'),
700
+ __metadata("design:type", HTMLDivElement)
701
+ ], BuildingInspectionCamera.prototype, "imageWrapperEl", void 0);
702
+ __decorate([
703
+ query('#cameraInput'),
704
+ __metadata("design:type", HTMLInputElement)
705
+ ], BuildingInspectionCamera.prototype, "cameraInputEl", void 0);
287
706
  BuildingInspectionCamera = __decorate([
288
707
  customElement('building-inspection-detail-camera')
289
708
  ], BuildingInspectionCamera);