@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.
- package/dist-client/pages/building-inspection/building-inspection-detail-camera.d.ts +33 -4
- package/dist-client/pages/building-inspection/building-inspection-detail-camera.js +456 -37
- package/dist-client/pages/building-inspection/building-inspection-detail-camera.js.map +1 -1
- package/dist-client/pages/checklist-template/checklist-template-item.js +14 -3
- package/dist-client/pages/checklist-template/checklist-template-item.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/service/checklist-template-item/checklist-template-item-mutation.js +4 -1
- package/dist-server/service/checklist-template-item/checklist-template-item-mutation.js.map +1 -1
- package/dist-server/service/checklist-template-item/checklist-template-item-type.d.ts +2 -0
- package/dist-server/service/checklist-template-item/checklist-template-item-type.js +5 -0
- package/dist-server/service/checklist-template-item/checklist-template-item-type.js.map +1 -1
- package/dist-server/service/checklist-template-item/checklist-template-item.d.ts +6 -0
- package/dist-server/service/checklist-template-item/checklist-template-item.js +16 -1
- package/dist-server/service/checklist-template-item/checklist-template-item.js.map +1 -1
- package/dist-server/service/checklist-template-item/index.d.ts +1 -1
- package/dist-server/service/checklist-template-item/spec-matching-service.d.ts +2 -0
- package/dist-server/service/checklist-template-item/spec-matching-service.js +50 -0
- package/dist-server/service/checklist-template-item/spec-matching-service.js.map +1 -0
- package/dist-server/service/index.d.ts +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- 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
|
-
|
|
14
|
-
|
|
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
|
|
24
|
-
private
|
|
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.
|
|
19
|
-
this.
|
|
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
|
-
<!--
|
|
101
|
+
<!-- Preview selected or recorded video -->
|
|
39
102
|
<div preview>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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="
|
|
136
|
+
<input id="cameraInput" type="file" accept="video/*" capture @change="${this._onVideoSelected}" />
|
|
56
137
|
</div>
|
|
57
138
|
|
|
58
139
|
<!-- Action buttons -->
|
|
59
|
-
<div
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
137
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
262
|
-
background-color: #
|
|
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, "
|
|
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, "
|
|
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);
|