@dssp/supervision 1.0.0-alpha.49 → 1.0.0-alpha.50

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.
@@ -0,0 +1,22 @@
1
+ import '@material/web/icon/icon.js';
2
+ import '@operato/input/ox-input-switch.js';
3
+ import '@operato/mini-map/ox-zoomable-image.js';
4
+ import '@material/web/progress/circular-progress.js';
5
+ import { PropertyValues } from 'lit';
6
+ import { PageView } from '@operato/shell';
7
+ import { PageLifecycle } from '@operato/shell/dist/src/app/pages/page-view';
8
+ import './component/building-inspection-detail-header';
9
+ export declare class BuildingInspectionAiDefect extends PageView {
10
+ static styles: import("lit").CSSResult[];
11
+ project: any;
12
+ buildingInspection: any;
13
+ buildingInspectionId: string;
14
+ get context(): {
15
+ title: string;
16
+ };
17
+ render(): import("lit-html").TemplateResult<1>;
18
+ protected updated(changes: PropertyValues): Promise<void>;
19
+ pageUpdated(changes: any, lifecycle: PageLifecycle): Promise<void>;
20
+ initBuildingInspection(buildingInspectionId?: string): Promise<void>;
21
+ private _getProjectByBuildingComplexId;
22
+ }
@@ -0,0 +1,169 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ import '@material/web/icon/icon.js';
3
+ import '@operato/input/ox-input-switch.js';
4
+ import '@operato/mini-map/ox-zoomable-image.js';
5
+ import '@material/web/progress/circular-progress.js';
6
+ import gql from 'graphql-tag';
7
+ import { css, html } from 'lit';
8
+ import { customElement, state } from 'lit/decorators.js';
9
+ import { PageView } from '@operato/shell';
10
+ import { CommonGristStyles, ScrollbarStyles } from '@operato/styles';
11
+ import { client } from '@operato/graphql';
12
+ import './component/building-inspection-detail-header';
13
+ let BuildingInspectionAiDefect = class BuildingInspectionAiDefect extends PageView {
14
+ constructor() {
15
+ super(...arguments);
16
+ this.project = {};
17
+ this.buildingInspection = {};
18
+ this.buildingInspectionId = '';
19
+ }
20
+ get context() {
21
+ return {
22
+ title: '검측 관리 상세 - AI 하자 분석'
23
+ };
24
+ }
25
+ render() {
26
+ var _a, _b, _c, _d, _e, _f, _g, _h;
27
+ return html `
28
+ <building-inspection-detail-header
29
+ .buildingInspectionId=${(_a = this.buildingInspection) === null || _a === void 0 ? void 0 : _a.id}
30
+ .buildingLevelId=${(_c = (_b = this.buildingInspection) === null || _b === void 0 ? void 0 : _b.buildingLevel) === null || _c === void 0 ? void 0 : _c.id}
31
+ .projectName=${this.project.name}
32
+ .buildingName=${(_f = (_e = (_d = this.buildingInspection) === null || _d === void 0 ? void 0 : _d.buildingLevel) === null || _e === void 0 ? void 0 : _e.building) === null || _f === void 0 ? void 0 : _f.name}
33
+ .buildingLevelFloor=${(_h = (_g = this.buildingInspection) === null || _g === void 0 ? void 0 : _g.buildingLevel) === null || _h === void 0 ? void 0 : _h.floor}
34
+ ></building-inspection-detail-header>
35
+
36
+ <div body>
37
+ <div preview>
38
+ <video src="/attachment/21e53688-5262-4fae-961a-591db7114b75.mp4" controls playsinline></video>
39
+ </div>
40
+ </div>
41
+ `;
42
+ }
43
+ async updated(changes) { }
44
+ async pageUpdated(changes, lifecycle) {
45
+ if (this.active) {
46
+ this.buildingInspectionId = lifecycle.resourceId || '';
47
+ await this.initBuildingInspection(this.buildingInspectionId);
48
+ }
49
+ }
50
+ async initBuildingInspection(buildingInspectionId = '') {
51
+ var _a, _b, _c, _d;
52
+ const response = await client.query({
53
+ query: gql `
54
+ query BuildingInspection($buildingInspectionId: String!) {
55
+ buildingInspection(id: $buildingInspectionId) {
56
+ id
57
+ status
58
+ requestDate
59
+ drawingMarker
60
+ checklist {
61
+ id
62
+ location
63
+ inspectionDrawingType
64
+ }
65
+ buildingLevel {
66
+ id
67
+ floor
68
+ mainDrawing {
69
+ id
70
+ name
71
+ fullpath
72
+ }
73
+ mainDrawingImage
74
+ building {
75
+ id
76
+ name
77
+ buildingComplex {
78
+ id
79
+ }
80
+ }
81
+ }
82
+ }
83
+ }
84
+ `,
85
+ variables: {
86
+ buildingInspectionId
87
+ }
88
+ });
89
+ if (response.errors)
90
+ return;
91
+ this.buildingInspection = response.data.buildingInspection;
92
+ await this._getProjectByBuildingComplexId((_d = (_c = (_b = (_a = this.buildingInspection) === null || _a === void 0 ? void 0 : _a.buildingLevel) === null || _b === void 0 ? void 0 : _b.building) === null || _c === void 0 ? void 0 : _c.buildingComplex) === null || _d === void 0 ? void 0 : _d.id);
93
+ }
94
+ async _getProjectByBuildingComplexId(buildingComplexId) {
95
+ const response = await client.query({
96
+ query: gql `
97
+ query ProjectByBuildingComplexId($buildingComplexId: String!) {
98
+ project: projectByBuildingComplexId(buildingComplexId: $buildingComplexId) {
99
+ id
100
+ name
101
+ }
102
+ }
103
+ `,
104
+ variables: {
105
+ buildingComplexId
106
+ }
107
+ });
108
+ if (response.errors)
109
+ return;
110
+ this.project = response.data.project;
111
+ }
112
+ };
113
+ BuildingInspectionAiDefect.styles = [
114
+ ScrollbarStyles,
115
+ CommonGristStyles,
116
+ css `
117
+ :host {
118
+ display: grid;
119
+ grid-template-rows: 75px 1fr;
120
+ color: #4e5055;
121
+ width: 100%;
122
+ height: 100%;
123
+ background-color: #f7f7f7;
124
+ }
125
+
126
+ div[body] {
127
+ display: flex;
128
+ justify-items: center;
129
+ gap: var(--spacing-medium);
130
+ margin: var(--spacing-medium);
131
+ min-height: 0;
132
+ }
133
+
134
+ div[preview] {
135
+ flex: 1;
136
+ border: 2px solid #ddd;
137
+ border-radius: 10px;
138
+
139
+ display: flex;
140
+ justify-content: center;
141
+ align-items: center;
142
+ min-height: 0;
143
+ overflow: hidden;
144
+ }
145
+
146
+ div[preview] video {
147
+ max-width: 100%;
148
+ max-height: 100%;
149
+ object-fit: contain;
150
+ }
151
+ `
152
+ ];
153
+ __decorate([
154
+ state(),
155
+ __metadata("design:type", Object)
156
+ ], BuildingInspectionAiDefect.prototype, "project", void 0);
157
+ __decorate([
158
+ state(),
159
+ __metadata("design:type", Object)
160
+ ], BuildingInspectionAiDefect.prototype, "buildingInspection", void 0);
161
+ __decorate([
162
+ state(),
163
+ __metadata("design:type", String)
164
+ ], BuildingInspectionAiDefect.prototype, "buildingInspectionId", void 0);
165
+ BuildingInspectionAiDefect = __decorate([
166
+ customElement('building-inspection-detail-ai-defect')
167
+ ], BuildingInspectionAiDefect);
168
+ export { BuildingInspectionAiDefect };
169
+ //# sourceMappingURL=building-inspection-detail-ai-defect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"building-inspection-detail-ai-defect.js","sourceRoot":"","sources":["../../../client/pages/building-inspection/building-inspection-detail-ai-defect.ts"],"names":[],"mappings":";AAAA,OAAO,4BAA4B,CAAA;AACnC,OAAO,mCAAmC,CAAA;AAC1C,OAAO,wCAAwC,CAAA;AAC/C,OAAO,6CAA6C,CAAA;AAEpD,OAAO,GAAG,MAAM,aAAa,CAAA;AAC7B,OAAO,EAAE,GAAG,EAAE,IAAI,EAAkB,MAAM,KAAK,CAAA;AAC/C,OAAO,EAAE,aAAa,EAAE,KAAK,EAAS,MAAM,mBAAmB,CAAA;AAE/D,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEpE,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAEzC,OAAO,+CAA+C,CAAA;AAG/C,IAAM,0BAA0B,GAAhC,MAAM,0BAA2B,SAAQ,QAAQ;IAAjD;;QA0CI,YAAO,GAAQ,EAAE,CAAA;QACjB,uBAAkB,GAAQ,EAAE,CAAA;QAC5B,yBAAoB,GAAW,EAAE,CAAA;IAoG5C,CAAC;IAlGC,IAAI,OAAO;QACT,OAAO;YACL,KAAK,EAAE,qBAAqB;SAC7B,CAAA;IACH,CAAC;IAED,MAAM;;QACJ,OAAO,IAAI,CAAA;;gCAEiB,MAAA,IAAI,CAAC,kBAAkB,0CAAE,EAAE;2BAChC,MAAA,MAAA,IAAI,CAAC,kBAAkB,0CAAE,aAAa,0CAAE,EAAE;uBAC9C,IAAI,CAAC,OAAO,CAAC,IAAI;wBAChB,MAAA,MAAA,MAAA,IAAI,CAAC,kBAAkB,0CAAE,aAAa,0CAAE,QAAQ,0CAAE,IAAI;8BAChD,MAAA,MAAA,IAAI,CAAC,kBAAkB,0CAAE,aAAa,0CAAE,KAAK;;;;;;;;KAQtE,CAAA;IACH,CAAC;IAES,KAAK,CAAC,OAAO,CAAC,OAAuB,IAAkB,CAAC;IAElE,KAAK,CAAC,WAAW,CAAC,OAAY,EAAE,SAAwB;QACtD,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,IAAI,CAAC,oBAAoB,GAAG,SAAS,CAAC,UAAU,IAAI,EAAE,CAAA;YACtD,MAAM,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAA;QAC9D,CAAC;IACH,CAAC;IAED,KAAK,CAAC,sBAAsB,CAAC,uBAA+B,EAAE;;QAC5D,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC;YAClC,KAAK,EAAE,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BT;YACD,SAAS,EAAE;gBACT,oBAAoB;aACrB;SACF,CAAC,CAAA;QAEF,IAAI,QAAQ,CAAC,MAAM;YAAE,OAAM;QAE3B,IAAI,CAAC,kBAAkB,GAAG,QAAQ,CAAC,IAAI,CAAC,kBAAkB,CAAA;QAE1D,MAAM,IAAI,CAAC,8BAA8B,CAAC,MAAA,MAAA,MAAA,MAAA,IAAI,CAAC,kBAAkB,0CAAE,aAAa,0CAAE,QAAQ,0CAAE,eAAe,0CAAE,EAAE,CAAC,CAAA;IAClH,CAAC;IAEO,KAAK,CAAC,8BAA8B,CAAC,iBAAiB;QAC5D,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC;YAClC,KAAK,EAAE,GAAG,CAAA;;;;;;;OAOT;YACD,SAAS,EAAE;gBACT,iBAAiB;aAClB;SACF,CAAC,CAAA;QAEF,IAAI,QAAQ,CAAC,MAAM;YAAE,OAAM;QAE3B,IAAI,CAAC,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAA;IACtC,CAAC;;AA9IM,iCAAM,GAAG;IACd,eAAe;IACf,iBAAiB;IACjB,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAmCF;CACF,AAvCY,CAuCZ;AAEQ;IAAR,KAAK,EAAE;;2DAAkB;AACjB;IAAR,KAAK,EAAE;;sEAA6B;AAC5B;IAAR,KAAK,EAAE;;wEAAkC;AA5C/B,0BAA0B;IADtC,aAAa,CAAC,sCAAsC,CAAC;GACzC,0BAA0B,CAgJtC","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@operato/input/ox-input-switch.js'\nimport '@operato/mini-map/ox-zoomable-image.js'\nimport '@material/web/progress/circular-progress.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, PropertyValues } from 'lit'\nimport { customElement, state, query } from 'lit/decorators.js'\n\nimport { PageView } from '@operato/shell'\nimport { CommonGristStyles, ScrollbarStyles } from '@operato/styles'\nimport { PageLifecycle } from '@operato/shell/dist/src/app/pages/page-view'\nimport { client } from '@operato/graphql'\n\nimport './component/building-inspection-detail-header'\n\n@customElement('building-inspection-detail-ai-defect')\nexport class BuildingInspectionAiDefect extends PageView {\n static styles = [\n ScrollbarStyles,\n CommonGristStyles,\n css`\n :host {\n display: grid;\n grid-template-rows: 75px 1fr;\n color: #4e5055;\n width: 100%;\n height: 100%;\n background-color: #f7f7f7;\n }\n\n div[body] {\n display: flex;\n justify-items: center;\n gap: var(--spacing-medium);\n margin: var(--spacing-medium);\n min-height: 0;\n }\n\n div[preview] {\n flex: 1;\n border: 2px solid #ddd;\n border-radius: 10px;\n\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 0;\n overflow: hidden;\n }\n\n div[preview] video {\n max-width: 100%;\n max-height: 100%;\n object-fit: contain;\n }\n `\n ]\n\n @state() project: any = {}\n @state() buildingInspection: any = {}\n @state() buildingInspectionId: string = ''\n\n get context() {\n return {\n title: '검측 관리 상세 - AI 하자 분석'\n }\n }\n\n render() {\n return html`\n <building-inspection-detail-header\n .buildingInspectionId=${this.buildingInspection?.id}\n .buildingLevelId=${this.buildingInspection?.buildingLevel?.id}\n .projectName=${this.project.name}\n .buildingName=${this.buildingInspection?.buildingLevel?.building?.name}\n .buildingLevelFloor=${this.buildingInspection?.buildingLevel?.floor}\n ></building-inspection-detail-header>\n\n <div body>\n <div preview>\n <video src=\"/attachment/21e53688-5262-4fae-961a-591db7114b75.mp4\" controls playsinline></video>\n </div>\n </div>\n `\n }\n\n protected async updated(changes: PropertyValues): Promise<void> {}\n\n async pageUpdated(changes: any, lifecycle: PageLifecycle) {\n if (this.active) {\n this.buildingInspectionId = lifecycle.resourceId || ''\n await this.initBuildingInspection(this.buildingInspectionId)\n }\n }\n\n async initBuildingInspection(buildingInspectionId: string = '') {\n const response = await client.query({\n query: gql`\n query BuildingInspection($buildingInspectionId: String!) {\n buildingInspection(id: $buildingInspectionId) {\n id\n status\n requestDate\n drawingMarker\n checklist {\n id\n location\n inspectionDrawingType\n }\n buildingLevel {\n id\n floor\n mainDrawing {\n id\n name\n fullpath\n }\n mainDrawingImage\n building {\n id\n name\n buildingComplex {\n id\n }\n }\n }\n }\n }\n `,\n variables: {\n buildingInspectionId\n }\n })\n\n if (response.errors) return\n\n this.buildingInspection = response.data.buildingInspection\n\n await this._getProjectByBuildingComplexId(this.buildingInspection?.buildingLevel?.building?.buildingComplex?.id)\n }\n\n private async _getProjectByBuildingComplexId(buildingComplexId) {\n const response = await client.query({\n query: gql`\n query ProjectByBuildingComplexId($buildingComplexId: String!) {\n project: projectByBuildingComplexId(buildingComplexId: $buildingComplexId) {\n id\n name\n }\n }\n `,\n variables: {\n buildingComplexId\n }\n })\n\n if (response.errors) return\n\n this.project = response.data.project\n }\n}\n"]}
@@ -107,32 +107,25 @@ let BuildingInspectionAiMeasurement = class BuildingInspectionAiMeasurement exte
107
107
  <div body>
108
108
  <!-- Preview selected or recorded video -->
109
109
  <div preview>
110
- <!-- analyzing 로딩 중일 -->
111
- ${this.analyzing
112
- ? html `<div class="loading-container">
113
- <md-circular-progress indeterminate></md-circular-progress>
114
- <div class="loading-text">분석 중...</div>
115
- </div>`
116
- : // 동영상 분석 완료 후, 단일 프레임 표시
117
- ((_j = this.frame) === null || _j === void 0 ? void 0 : _j.download_url)
118
- ? html `
119
- <div class="image-wrapper" @click=${this._onImageClick}>
120
- <img
121
- id="frameImage"
122
- src="${this.KEYPOINT_RULER_API_BASE_URL + ((_k = this.frame) === null || _k === void 0 ? void 0 : _k.download_url)}"
123
- crossorigin="anonymous"
124
- alt="frame"
125
- loading="eager"
126
- @load=${this._onImageLoad}
127
- />
128
- ${this._renderMarkers()}
129
- </div>
130
- `
131
- : // 동영상 선택 후 동영상 프리뷰
132
- this.capturedVideoUrl
133
- ? html `<video src=${this.capturedVideoUrl} controls playsinline></video>`
134
- : // 초기 상태, 동영상 선택 전
135
- html `<md-icon>videocam</md-icon>`}
110
+ ${((_j = this.frame) === null || _j === void 0 ? void 0 : _j.download_url)
111
+ ? html `
112
+ <div class="image-wrapper" @click=${this._onImageClick}>
113
+ <img
114
+ id="frameImage"
115
+ src="${this.KEYPOINT_RULER_API_BASE_URL + ((_k = this.frame) === null || _k === void 0 ? void 0 : _k.download_url)}"
116
+ crossorigin="anonymous"
117
+ alt="frame"
118
+ loading="eager"
119
+ @load=${this._onImageLoad}
120
+ />
121
+ ${this._renderMarkers()}
122
+ </div>
123
+ `
124
+ : // 동영상 선택 후 동영상 프리뷰
125
+ this.capturedVideoUrl
126
+ ? html `<video src=${this.capturedVideoUrl} controls playsinline></video>`
127
+ : // 초기 상태, 동영상 선택 전
128
+ html `<md-icon>videocam</md-icon>`}
136
129
  </div>
137
130
 
138
131
  <div controls>
@@ -165,6 +158,16 @@ let BuildingInspectionAiMeasurement = class BuildingInspectionAiMeasurement exte
165
158
  </div>
166
159
  </div>
167
160
  </div>
161
+
162
+ <!-- 전체 화면을 덮는 로딩 오버레이 -->
163
+ ${this.analyzing
164
+ ? html `<div class="loading-overlay">
165
+ <div class="loading-container">
166
+ <md-circular-progress indeterminate></md-circular-progress>
167
+ <div class="loading-text">분석 중...</div>
168
+ </div>
169
+ </div>`
170
+ : ''}
168
171
  `;
169
172
  }
170
173
  async updated(changes) { }
@@ -723,6 +726,28 @@ BuildingInspectionAiMeasurement.styles = [
723
726
  cursor: default;
724
727
  }
725
728
  }
729
+
730
+ .loading-overlay {
731
+ position: fixed;
732
+ top: 0;
733
+ left: 0;
734
+ right: 0;
735
+ bottom: 0;
736
+ background: rgba(0, 0, 0, 0.5);
737
+ display: flex;
738
+ flex-direction: column;
739
+ align-items: center;
740
+ justify-content: center;
741
+ gap: 12px;
742
+ z-index: 2000;
743
+ }
744
+
745
+ .loading-overlay .loading-container {
746
+ background: white;
747
+ padding: 32px;
748
+ border-radius: 12px;
749
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
750
+ }
726
751
  `
727
752
  ];
728
753
  __decorate([
@@ -1 +1 @@
1
- {"version":3,"file":"building-inspection-detail-ai-measurement.js","sourceRoot":"","sources":["../../../client/pages/building-inspection/building-inspection-detail-ai-measurement.ts"],"names":[],"mappings":";AAAA,OAAO,4BAA4B,CAAA;AACnC,OAAO,mCAAmC,CAAA;AAC1C,OAAO,wCAAwC,CAAA;AAC/C,OAAO,6CAA6C,CAAA;AAEpD,OAAO,GAAG,MAAM,aAAa,CAAA;AAC7B,OAAO,EAAE,GAAG,EAAE,IAAI,EAAkB,MAAM,KAAK,CAAA;AAC/C,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAC/D,OAAO,WAAW,MAAM,aAAa,CAAA;AAErC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEpE,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAExC,OAAO,+CAA+C,CAAA;AAG/C,IAAM,+BAA+B,GAArC,MAAM,+BAAgC,SAAQ,QAAQ;IAAtD;;QAsQG,gCAA2B,GAAW,yCAAyC,CAAA;QAE9E,YAAO,GAAQ,EAAE,CAAA;QACjB,uBAAkB,GAAQ,EAAE,CAAA;QAC5B,yBAAoB,GAAW,EAAE,CAAA;QACjC,qBAAgB,GAAkB,IAAI,CAAA;QACtC,cAAS,GAAgB,IAAI,CAAA;QAC7B,UAAK,GAAe,IAAI,CAAA;QACxB,cAAS,GAAY,KAAK,CAAA;QAC1B,mBAAc,GAAmF;YACxG,EAAE,EAAE,IAAI;YACR,EAAE,EAAE,IAAI;YACR,EAAE,EAAE,IAAI;YACR,EAAE,EAAE,IAAI;SACT,CAAA;QACQ,sBAAiB,GAAW,CAAC,CAAA;QAC7B,uBAAkB,GAAW,CAAC,CAAA;QAC9B,uBAAkB,GAAwE,IAAI,CAAA;QAC9F,aAAQ,GAAkB,IAAI,CAAA;QAC9B,SAAI,GAAkB,IAAI,CAAA;QAC1B,mBAAc,GAAkB,IAAI,CAAA;QAuOrC,iBAAY,GAAG,GAAG,EAAE;YAC1B,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,CAAC,IAAI,CAAC,cAAc;gBAAE,OAAM;YACtD,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,CAAA;YAC7B,MAAM,WAAW,GAAG,IAAI,CAAC,cAAc,CAAC,qBAAqB,EAAE,CAAA;YAC/D,MAAM,OAAO,GAAG,GAAG,CAAC,qBAAqB,EAAE,CAAA;YAC3C,IAAI,CAAC,iBAAiB,GAAG,GAAG,CAAC,YAAY,CAAA;YACzC,IAAI,CAAC,kBAAkB,GAAG,GAAG,CAAC,aAAa,CAAA;YAC3C,IAAI,CAAC,kBAAkB,GAAG;gBACxB,KAAK,EAAE,OAAO,CAAC,KAAK;gBACpB,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,IAAI,EAAE,OAAO,CAAC,IAAI,GAAG,WAAW,CAAC,IAAI;gBACrC,GAAG,EAAE,OAAO,CAAC,GAAG,GAAG,WAAW,CAAC,GAAG;aACnC,CAAA;QACH,CAAC,CAAA;QAEO,kBAAa,GAAG,CAAC,KAAiB,EAAE,EAAE;YAC5C,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,CAAC,IAAI,CAAC,cAAc;gBAAE,OAAM;YACtD,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,qBAAqB,EAAE,CAAA;YACzD,MAAM,WAAW,GAAG,IAAI,CAAC,cAAc,CAAC,qBAAqB,EAAE,CAAA;YAC/D,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAA;YAC7B,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAA;YAE7B,iDAAiD;YACjD,IAAI,OAAO,GAAG,OAAO,CAAC,IAAI,IAAI,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,OAAO,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;gBAC3G,OAAM;YACR,CAAC;YAED,8BAA8B;YAC9B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;YACpB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;YAChB,IAAI,CAAC,cAAc,GAAG,IAAI,CAAA;YAE1B,MAAM,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC,IAAI,CAAA;YACxC,MAAM,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC,GAAG,CAAA;YAEvC,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,SAAS,GAAG,IAAI,CAAC,iBAAiB,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAA;YACnF,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,SAAS,GAAG,IAAI,CAAC,kBAAkB,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;YAErF,IAAI,IAAI,CAAC,cAAc,CAAC,EAAE,KAAK,IAAI,IAAI,IAAI,CAAC,cAAc,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC;gBACvE,IAAI,CAAC,cAAc,mCAAQ,IAAI,CAAC,cAAc,KAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,UAAU,GAAE,CAAA;YAClF,CAAC;iBAAM,IAAI,IAAI,CAAC,cAAc,CAAC,EAAE,KAAK,IAAI,IAAI,IAAI,CAAC,cAAc,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC;gBAC9E,IAAI,CAAC,cAAc,mCAAQ,IAAI,CAAC,cAAc,KAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,UAAU,GAAE,CAAA;YAClF,CAAC;iBAAM,CAAC;gBACN,4BAA4B;gBAC5B,IAAI,CAAC,cAAc,GAAG,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,CAAA;YAC9E,CAAC;YAED,uDAAuD;YACvD,IAAI,CAAC,kBAAkB,GAAG;gBACxB,KAAK,EAAE,OAAO,CAAC,KAAK;gBACpB,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,IAAI,EAAE,OAAO,CAAC,IAAI,GAAG,WAAW,CAAC,IAAI;gBACrC,GAAG,EAAE,OAAO,CAAC,GAAG,GAAG,WAAW,CAAC,GAAG;aACnC,CAAA;QACH,CAAC,CAAA;IA+KH,CAAC;IAtcC,IAAI,OAAO;QACT,OAAO;YACL,KAAK,EAAE,qBAAqB;SAC7B,CAAA;IACH,CAAC;IAED,MAAM;;QACJ,OAAO,IAAI,CAAA;;gCAEiB,MAAA,IAAI,CAAC,kBAAkB,0CAAE,EAAE;2BAChC,MAAA,MAAA,IAAI,CAAC,kBAAkB,0CAAE,aAAa,0CAAE,EAAE;uBAC9C,IAAI,CAAC,OAAO,CAAC,IAAI;wBAChB,MAAA,MAAA,MAAA,IAAI,CAAC,kBAAkB,0CAAE,aAAa,0CAAE,QAAQ,0CAAE,IAAI;8BAChD,MAAA,MAAA,IAAI,CAAC,kBAAkB,0CAAE,aAAa,0CAAE,KAAK;;;;;;;YAO/D,IAAI,CAAC,SAAS;YACd,CAAC,CAAC,IAAI,CAAA;;;qBAGG;YACT,CAAC,CAAC,yBAAyB;gBACzB,CAAA,MAAA,IAAI,CAAC,KAAK,0CAAE,YAAY;oBACxB,CAAC,CAAC,IAAI,CAAA;sDACkC,IAAI,CAAC,aAAa;;;6BAG3C,IAAI,CAAC,2BAA2B,IAAG,MAAA,IAAI,CAAC,KAAK,0CAAE,YAAY,CAAA;;;;8BAI1D,IAAI,CAAC,YAAY;;sBAEzB,IAAI,CAAC,cAAc,EAAE;;iBAE1B;oBACH,CAAC,CAAC,mBAAmB;wBACnB,IAAI,CAAC,gBAAgB;4BACrB,CAAC,CAAC,IAAI,CAAA,cAAc,IAAI,CAAC,gBAAgB,gCAAgC;4BACzE,CAAC,CAAC,kBAAkB;gCAClB,IAAI,CAAA,6BAA6B;;;;;;;YAOvC,CAAA,MAAA,IAAI,CAAC,KAAK,0CAAE,YAAY,KAAI,IAAI,CAAC,aAAa,EAAE,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,IAAI;YAC9E,CAAC,CAAC,0BAA0B;gBAC1B,IAAI,CAAA,kCAAkC,IAAI,CAAC,SAAS,WAAW,IAAI,CAAC,UAAU,iBAAiB;YACjG,CAAC,CAAC,2BAA2B;gBAC3B,IAAI,CAAA;;gFAE8D,IAAI,CAAC,gBAAgB;qBAChF;;;;cAIP,IAAI,CAAC,KAAK;YACV,CAAC,CAAC,IAAI,CAAA;oDACgC,IAAI,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,WAAW,IAAI,CAAC,eAAe;;;iBAGzG;YACH,CAAC,CAAC,IAAI,CAAA;mDAC+B,IAAI,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,gBAAgB,WAAW,IAAI,CAAC,eAAe;;;iBAGzG;2CAC0B,IAAI,CAAC,QAAQ;;;;KAInD,CAAA;IACH,CAAC;IAES,KAAK,CAAC,OAAO,CAAC,OAAuB,IAAkB,CAAC;IAElE,KAAK,CAAC,WAAW,CAAC,OAAY,EAAE,SAAwB;QACtD,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,IAAI,CAAC,QAAQ,EAAE,CAAA;YACf,IAAI,CAAC,oBAAoB,GAAG,SAAS,CAAC,UAAU,IAAI,EAAE,CAAA;YACtD,MAAM,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAA;QAC9D,CAAC;IACH,CAAC;IAED,KAAK,CAAC,sBAAsB,CAAC,uBAA+B,EAAE;;QAC5D,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC;YAClC,KAAK,EAAE,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BT;YACD,SAAS,EAAE;gBACT,oBAAoB;aACrB;SACF,CAAC,CAAA;QAEF,IAAI,QAAQ,CAAC,MAAM;YAAE,OAAM;QAE3B,IAAI,CAAC,kBAAkB,GAAG,QAAQ,CAAC,IAAI,CAAC,kBAAkB,CAAA;QAE1D,MAAM,IAAI,CAAC,8BAA8B,CAAC,MAAA,MAAA,MAAA,MAAA,IAAI,CAAC,kBAAkB,0CAAE,aAAa,0CAAE,QAAQ,0CAAE,eAAe,0CAAE,EAAE,CAAC,CAAA;IAClH,CAAC;IAEO,KAAK,CAAC,8BAA8B,CAAC,iBAAiB;QAC5D,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC;YAClC,KAAK,EAAE,GAAG,CAAA;;;;;;;OAOT;YACD,SAAS,EAAE;gBACT,iBAAiB;aAClB;SACF,CAAC,CAAA;QAEF,IAAI,QAAQ,CAAC,MAAM;YAAE,OAAM;QAE3B,IAAI,CAAC,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAA;IACtC,CAAC;IAEO,gBAAgB,CAAC,KAAY;;QACnC,MAAM,KAAK,GAAG,KAAK,CAAC,MAA0B,CAAA;QAC9C,MAAM,IAAI,GAAG,MAAA,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,KAAK,0CAAG,CAAC,CAAC,CAAA;QAC9B,IAAI,CAAC,IAAI;YAAE,OAAM;QAEjB,yBAAyB;QACzB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YACpC,OAAO,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAA;YAClC,OAAM;QACR,CAAC;QAED,kCAAkC;QAClC,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC1B,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAA;QAC5C,CAAC;QAED,IAAI,CAAC,SAAS,GAAG,IAAI,CAAA;QACrB,IAAI,CAAC,gBAAgB,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAA;IACnD,CAAC;IAEO,KAAK,CAAC,eAAe;;QAC3B,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,OAAO,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAA;YAC9B,OAAM;QACR,CAAC;QAED,IAAI,CAAC;YACH,IAAI,CAAC,SAAS,GAAG,IAAI,CAAA;YACrB,MAAM,IAAI,GAAG,IAAI,QAAQ,EAAE,CAAA;YAC3B,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;YACzD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC,2BAA2B,yBAAyB,EAAE;gBACzF,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,aAAa,EAAE,SAAS,IAAI,CAAC,iBAAiB,CAAC,EAAE,EAAE;gBAC9D,IAAI,EAAE,IAAI;aACX,CAAC,CAAA;YAEF,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;YAElC,IAAI,CAAC,KAAK,GAAG;gBACX,OAAO,EAAE,MAAA,MAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,MAAM,0CAAE,YAAY,CAAC,CAAC,CAAC,0CAAE,OAAO;gBAC/C,YAAY,EAAE,MAAA,MAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,MAAM,0CAAE,YAAY,CAAC,CAAC,CAAC,0CAAE,YAAY;gBACzD,SAAS,EAAE,MAAA,MAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,MAAM,0CAAE,MAAM,CAAC,CAAC,CAAC,0CAAE,SAAS;gBAC7C,WAAW,EAAE,MAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,MAAM,0CAAE,WAAW;aACvC,CAAA;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,aAAa,EAAE,KAAK,CAAC,CAAA;QACrC,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,SAAS,GAAG,KAAK,CAAA;QACxB,CAAC;IACH,CAAC;IAEO,QAAQ;QACd,4CAA4C;QAC5C,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC1B,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAA;QAC5C,CAAC;QACD,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAA;QAC5B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAA;QACrB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;QACjB,IAAI,CAAC,cAAc,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,CAAA;QAChE,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAA;QAC9B,IAAI,CAAC,iBAAiB,GAAG,CAAC,CAAA;QAC1B,IAAI,CAAC,kBAAkB,GAAG,CAAC,CAAA;QAC3B,IAAI,CAAC,aAAa,CAAC,KAAK,GAAG,EAAE,CAAA;QAC7B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;QACpB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,CAAC,cAAc,GAAG,IAAI,CAAA;IAC5B,CAAC;IA0DO,aAAa;QACnB,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,IAAI,CAAC,cAAc,CAAA;QAC9C,OAAO,EAAE,KAAK,IAAI,IAAI,EAAE,KAAK,IAAI,IAAI,EAAE,KAAK,IAAI,IAAI,EAAE,KAAK,IAAI,CAAA;IACjE,CAAC;IAEO,cAAc;QACpB,IAAI,CAAC,IAAI,CAAC,kBAAkB;YAAE,OAAO,IAAI,CAAA,EAAE,CAAA;QAC3C,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,kBAAkB,CAAA;QAC5D,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,IAAI,CAAC,cAAc,CAAA;QAC9C,MAAM,SAAS,GAAG,CAAC,CAAS,EAAE,CAAS,EAAE,EAAE;YACzC,MAAM,EAAE,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,iBAAiB,IAAI,CAAC,CAAC,CAAA;YAC7D,MAAM,EAAE,GAAG,GAAG,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,kBAAkB,IAAI,CAAC,CAAC,CAAA;YAC9D,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE,CAAA;QACnB,CAAC,CAAA;QAED,OAAO,IAAI,CAAA;;QAEP,EAAE,KAAK,IAAI,IAAI,EAAE,KAAK,IAAI;YAC1B,CAAC,CAAC,CAAC,GAAG,EAAE;gBACJ,MAAM,CAAC,GAAG,SAAS,CAAC,EAAE,EAAE,EAAE,CAAC,CAAA;gBAC3B,OAAO,IAAI,CAAA,iDAAiD,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,EAAE,aAAa,CAAA;YAC9F,CAAC,CAAC,EAAE;YACN,CAAC,CAAC,EAAE;;QAEJ,EAAE,KAAK,IAAI,IAAI,EAAE,KAAK,IAAI;YAC1B,CAAC,CAAC,CAAC,GAAG,EAAE;gBACJ,MAAM,CAAC,GAAG,SAAS,CAAC,EAAE,EAAE,EAAE,CAAC,CAAA;gBAC3B,OAAO,IAAI,CAAA,iDAAiD,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,EAAE,aAAa,CAAA;YAC9F,CAAC,CAAC,EAAE;YACN,CAAC,CAAC,EAAE;;QAEJ,IAAI,CAAC,aAAa,EAAE;YACpB,CAAC,CAAC,CAAC,GAAG,EAAE;gBACJ,MAAM,EAAE,GAAG,SAAS,CAAC,EAAY,EAAE,EAAY,CAAC,CAAA;gBAChD,MAAM,EAAE,GAAG,SAAS,CAAC,EAAY,EAAE,EAAY,CAAC,CAAA;gBAChD,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;gBAC9B,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;gBAE9B,eAAe;gBACf,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;gBACjF,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,GAAG,IAAI,CAAC,EAAE,CAAC,CAAA;gBAExE,OAAO,IAAI,CAAA;;;;;0BAKG,EAAE,CAAC,EAAE;yBACN,EAAE,CAAC,EAAE;2BACH,MAAM;;sCAEK,KAAK;;;;gBAI3B,IAAI,CAAC,cAAc;oBACnB,CAAC,CAAC,IAAI,CAAA,2CAA2C,EAAE,WAAW,EAAE,QAAQ,IAAI,CAAC,cAAc,QAAQ;oBACnG,CAAC,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,IAAI;wBAC1B,CAAC,CAAC,IAAI,CAAA,2CAA2C,EAAE,WAAW,EAAE,QAAQ,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,IAAI,QAAQ;wBAC1G,CAAC,CAAC,EAAE;aACT,CAAA;YACH,CAAC,CAAC,EAAE;YACN,CAAC,CAAC,EAAE;KACP,CAAA;IACH,CAAC;IAEO,KAAK,CAAC,eAAe;QAC3B,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YAChB,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,CAAA;YAC7B,OAAM;QACR,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC;YAC1B,OAAO,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAA;YAChC,OAAM;QACR,CAAC;QAED,IAAI,CAAC;YACH,IAAI,CAAC,SAAS,GAAG,IAAI,CAAA;YACrB,MAAM,OAAO,GAAG;gBACd,EAAE,EAAE,IAAI,CAAC,cAAc,CAAC,EAAE;gBAC1B,EAAE,EAAE,IAAI,CAAC,cAAc,CAAC,EAAE;gBAC1B,EAAE,EAAE,IAAI,CAAC,cAAc,CAAC,EAAE;gBAC1B,EAAE,EAAE,IAAI,CAAC,cAAc,CAAC,EAAE;gBAC1B,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS;gBAC/B,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW;aACpC,CAAA;YAED,MAAM,IAAI,GAAG,IAAI,QAAQ,EAAE,CAAA;YAC3B,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;YAC7C,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC,2BAA2B,yBAAyB,EAAE;gBACzF,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,aAAa,EAAE,SAAS,IAAI,CAAC,iBAAiB,CAAC,EAAE,EAAE;gBAC9D,IAAI,EAAE,IAAI;aACX,CAAC,CAAA;YAEF,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;YACpC,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC,MAAM,IAAI,EAAE,CAAA;YACrD,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAA;YACxB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;YAChB,IAAI,CAAC,cAAc,GAAG,KAAK,CAAA;QAC7B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,CAAA;QACnC,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,SAAS,GAAG,KAAK,CAAA;QACxB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,UAAU;;QACd,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YACjF,MAAM,CAAC,EAAE,OAAO,EAAE,qBAAqB,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;YACzD,OAAM;QACR,CAAC;QAED,MAAM,WAAW,GAAG,MAAA,MAAA,IAAI,CAAC,kBAAkB,0CAAE,SAAS,0CAAE,EAAE,CAAA;QAC1D,IAAI,IAAU,CAAA;QACd,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,IAAI,CAAC,eAAe,EAAE,CAAA;QACrC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,CAAC,EAAE,OAAO,EAAE,iBAAiB,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAA;YACtD,OAAM;QACR,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC;YACnC,QAAQ,EAAE,GAAG,CAAA;;;;;;OAMZ;YACD,SAAS,EAAE;gBACT,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAA,IAAI,CAAC,kBAAkB,0CAAE,MAAM,EAAE,CAAC;aAChH;YACD,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE;SAC7B,CAAC,CAAA;QAEF,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;YACrB,MAAM,CAAC,EAAE,OAAO,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;YACrD,OAAM;QACR,CAAC;QAED,OAAO,QAAQ,CAAC,IAAI,CAAC,iBAAiB,CAAA;IACxC,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAA;QACvC,CAAC;QAED,IAAI,CAAC;YACH,oCAAoC;YACpC,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,cAAc,EAAE;gBACpD,eAAe,EAAE,IAAI,EAAE,QAAQ;gBAC/B,KAAK,EAAE,CAAC,EAAE,UAAU;gBACpB,OAAO,EAAE,IAAI,EAAE,cAAc;gBAC7B,UAAU,EAAE,KAAK,EAAE,YAAY;gBAC/B,OAAO,EAAE,KAAK,CAAC,UAAU;aAC1B,CAAC,CAAA;YAEF,iBAAiB;YACjB,MAAM,IAAI,GAAS,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;gBAC7C,MAAM,CAAC,MAAM,CAAC,OAAc,EAAE,WAAW,EAAE,GAAG,CAAC,CAAA;YACjD,CAAC,CAAC,CAAA;YAEF,mBAAmB;YACnB,OAAO,IAAI,IAAI,CAAC,CAAC,IAAK,CAAC,EAAE,cAAc,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE;gBACvD,IAAI,EAAE,WAAW;aAClB,CAAC,CAAA;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,oBAAoB,EAAE,KAAK,CAAC,CAAA;YAC1C,MAAM,IAAI,KAAK,CAAC,gBAAgB,CAAC,CAAA;QACnC,CAAC;IACH,CAAC;;AApuBM,sCAAM,GAAG;IACd,eAAe;IACf,iBAAiB;IACjB,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA+PF;CACF,AAnQY,CAmQZ;AAIQ;IAAR,KAAK,EAAE;;gEAAkB;AACjB;IAAR,KAAK,EAAE;;2EAA6B;AAC5B;IAAR,KAAK,EAAE;;6EAAkC;AACjC;IAAR,KAAK,EAAE;;yEAAuC;AACtC;IAAR,KAAK,EAAE;;kEAA8B;AAC7B;IAAR,KAAK,EAAE;;8DAAyB;AACxB;IAAR,KAAK,EAAE;;kEAA2B;AAC1B;IAAR,KAAK,EAAE;;uEAKP;AACQ;IAAR,KAAK,EAAE;;0EAA8B;AAC7B;IAAR,KAAK,EAAE;;2EAA+B;AAC9B;IAAR,KAAK,EAAE;;2EAA+F;AAC9F;IAAR,KAAK,EAAE;;iEAA+B;AAC9B;IAAR,KAAK,EAAE;;6DAA2B;AAC1B;IAAR,KAAK,EAAE;;uEAAqC;AAEZ;IAAhC,KAAK,CAAC,gBAAgB,CAAC;8BAAwB,gBAAgB;qEAAA;AAC5B;IAAnC,KAAK,CAAC,mBAAmB,CAAC;8BAA0B,cAAc;uEAAA;AACpC;IAA9B,KAAK,CAAC,cAAc,CAAC;8BAAyB,gBAAgB;sEAAA;AA9RpD,+BAA+B;IAD3C,aAAa,CAAC,2CAA2C,CAAC;GAC9C,+BAA+B,CAsuB3C","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@operato/input/ox-input-switch.js'\nimport '@operato/mini-map/ox-zoomable-image.js'\nimport '@material/web/progress/circular-progress.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, PropertyValues } from 'lit'\nimport { customElement, state, query } from 'lit/decorators.js'\nimport html2canvas from 'html2canvas'\n\nimport { PageView } from '@operato/shell'\nimport { CommonGristStyles, ScrollbarStyles } from '@operato/styles'\nimport { PageLifecycle } from '@operato/shell/dist/src/app/pages/page-view'\nimport { client } from '@operato/graphql'\nimport { notify } from '@operato/layout'\n\nimport './component/building-inspection-detail-header'\n\n@customElement('building-inspection-detail-ai-measurement')\nexport class BuildingInspectionAiMeasurement extends PageView {\n static styles = [\n ScrollbarStyles,\n CommonGristStyles,\n css`\n :host {\n display: grid;\n grid-template-rows: 75px 1fr;\n color: #4e5055;\n width: 100%;\n height: 100%;\n background-color: #f7f7f7;\n }\n\n div[body] {\n display: flex;\n justify-items: center;\n gap: var(--spacing-medium);\n margin: var(--spacing-medium);\n min-height: 0;\n }\n\n div[preview] {\n flex: 1;\n border: 2px solid #ddd;\n border-radius: 10px;\n\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 0;\n overflow: hidden;\n }\n\n div[preview] img {\n max-width: 100%;\n max-height: 100%;\n object-fit: contain;\n }\n\n div[preview] video {\n max-width: 100%;\n max-height: 100%;\n object-fit: contain;\n }\n\n div[preview] md-icon {\n --md-icon-size: 160px;\n }\n\n .loading-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n }\n\n .loading-text {\n font-size: 14px;\n color: #666;\n }\n\n .frame-container {\n width: 100%;\n height: 100%;\n max-height: 100%;\n min-height: 0;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 12px;\n }\n\n .frames-header {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 12px;\n font-size: 20px;\n font-weight: 700;\n color: #333;\n text-align: center;\n padding-block: 8px;\n }\n\n .frames-count {\n font-size: 16px;\n color: #777;\n font-weight: 600;\n }\n\n .frames-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));\n gap: 12px;\n }\n\n .frame-item {\n border: 1px solid #ddd;\n border-radius: 8px;\n overflow: hidden;\n background: #fff;\n cursor: pointer;\n transition:\n box-shadow 0.2s,\n transform 0.1s;\n }\n\n .frame-item:hover {\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n transform: translateY(-1px);\n }\n\n .frame-item img {\n display: block;\n width: 100%;\n height: 120px;\n object-fit: cover;\n }\n\n .image-wrapper {\n position: relative;\n width: 100%;\n height: 100%;\n align-content: center;\n text-align: center;\n }\n\n .point-marker {\n position: absolute;\n width: 14px;\n height: 14px;\n border-radius: 50%;\n border: 2px solid #fff;\n transform: translate(-50%, -50%);\n pointer-events: none;\n box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.25);\n }\n\n .point-1 {\n background-color: #e53935;\n }\n\n .point-2 {\n background-color: #1e88e5;\n }\n\n .line-overlay {\n position: absolute;\n pointer-events: none;\n background-color: #ff9800;\n transform-origin: left center;\n }\n\n .distance-label {\n position: absolute;\n background: rgba(0, 0, 0, 0.7);\n color: #fff;\n font-size: 14px;\n padding: 2px 6px;\n border-radius: 4px;\n transform: translate(-50%, -120%);\n pointer-events: none;\n white-space: nowrap;\n }\n\n div[controls] {\n width: 240px;\n display: flex;\n flex-direction: column;\n justify-content: space-between;\n align-items: center;\n }\n\n div[action-buttons] {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n gap: 15px;\n }\n\n .switch-container {\n display: flex;\n align-items: center;\n gap: 10px;\n font-size: 24px;\n font-weight: bold;\n }\n\n ox-input-switch {\n --ox-simple-switch-fullwidth: 68px;\n --ox-simple-switch-fullheight: 34px;\n --ox-simple-switch-thumbnail-size: 34px;\n }\n\n .camera-shutter {\n display: flex;\n justify-content: center;\n align-items: center;\n cursor: pointer;\n position: relative;\n }\n\n .controls-title {\n font-size: 18px;\n font-weight: 700;\n color: #333;\n margin-bottom: 8px;\n text-align: center;\n }\n\n .camera-shutter md-icon {\n --md-icon-size: 100px;\n }\n\n .camera-shutter input {\n opacity: 0;\n width: 100%;\n height: 100%;\n position: absolute;\n left: 0;\n top: 0;\n cursor: pointer;\n }\n\n button {\n padding: 10px 20px;\n font-size: 16px;\n border-radius: 5px;\n border: none;\n cursor: pointer;\n }\n\n button.save {\n background-color: #4caf50;\n color: white;\n\n &:disabled {\n background-color: #ccc;\n cursor: default;\n }\n }\n\n button.retry {\n background-color: #f0ad4e;\n color: white;\n }\n\n button.photo {\n background-color: #1e88e5;\n color: white;\n\n &:disabled {\n background-color: #ccc;\n cursor: default;\n }\n }\n `\n ]\n\n private KEYPOINT_RULER_API_BASE_URL: string = 'https://hatiolab-korea-uni.ettisoft.com'\n\n @state() project: any = {}\n @state() buildingInspection: any = {}\n @state() buildingInspectionId: string = ''\n @state() capturedVideoUrl: string | null = null\n @state() videoFile: File | null = null\n @state() frame: any | null = null\n @state() analyzing: boolean = false\n @state() selectedPoints: { x1: number | null; y1: number | null; x2: number | null; y2: number | null } = {\n x1: null,\n y1: null,\n x2: null,\n y2: null\n }\n @state() frameNaturalWidth: number = 0\n @state() frameNaturalHeight: number = 0\n @state() displayedImageRect: { width: number; height: number; left: number; top: number } | null = null\n @state() distance: number | null = null\n @state() unit: string | null = null\n @state() analyzingError: string | null = null\n\n @query('img#frameImage') private frameImageEl!: HTMLImageElement\n @query('div.image-wrapper') private imageWrapperEl!: HTMLDivElement\n @query('#cameraInput') private cameraInputEl!: HTMLInputElement\n\n get context() {\n return {\n title: '검측 관리 상세 - AI 거리 측정'\n }\n }\n\n render() {\n return html`\n <building-inspection-detail-header\n .buildingInspectionId=${this.buildingInspection?.id}\n .buildingLevelId=${this.buildingInspection?.buildingLevel?.id}\n .projectName=${this.project.name}\n .buildingName=${this.buildingInspection?.buildingLevel?.building?.name}\n .buildingLevelFloor=${this.buildingInspection?.buildingLevel?.floor}\n ></building-inspection-detail-header>\n\n <div body>\n <!-- Preview selected or recorded video -->\n <div preview>\n <!-- analyzing 로딩 중일 때 -->\n ${this.analyzing\n ? html`<div class=\"loading-container\">\n <md-circular-progress indeterminate></md-circular-progress>\n <div class=\"loading-text\">분석 중...</div>\n </div>`\n : // 동영상 분석 완료 후, 단일 프레임 표시\n this.frame?.download_url\n ? html`\n <div class=\"image-wrapper\" @click=${this._onImageClick}>\n <img\n id=\"frameImage\"\n src=\"${this.KEYPOINT_RULER_API_BASE_URL + this.frame?.download_url}\"\n crossorigin=\"anonymous\"\n alt=\"frame\"\n loading=\"eager\"\n @load=${this._onImageLoad}\n />\n ${this._renderMarkers()}\n </div>\n `\n : // 동영상 선택 후 동영상 프리뷰\n this.capturedVideoUrl\n ? html`<video src=${this.capturedVideoUrl} controls playsinline></video>`\n : // 초기 상태, 동영상 선택 전\n html`<md-icon>videocam</md-icon>`}\n </div>\n\n <div controls>\n <span class=\"controls-title\">동영상 촬영</span>\n\n <!-- 가운데 버튼 -->\n ${this.frame?.download_url && this._hasTwoPoints() && this.distance && this.unit\n ? // 거리 측정 완료 시, 사진 저장 버튼 표시\n html`<button class=\"save\" ?disabled=${this.analyzing} @click=${this._savePhoto}>사진 저장</button>`\n : // 거리 측정 완료 전, 동영상 촬영 버튼 표시\n html`<div class=\"camera-shutter\">\n <md-icon>camera</md-icon>\n <input id=\"cameraInput\" type=\"file\" accept=\"video/*\" @change=\"${this._onVideoSelected}\" />\n </div>`}\n\n <!-- Action buttons -->\n <div action-buttons>\n ${this.frame\n ? html`\n <button class=\"photo\" ?disabled=${this.analyzing || !this._hasTwoPoints()} @click=${this._onAnalyzePhoto}>\n 사진 분석\n </button>\n `\n : html`\n <button class=\"save\" ?disabled=${this.analyzing || !this.capturedVideoUrl} @click=${this._onAnalyzeVideo}>\n 동영상 분석\n </button>\n `}\n <button class=\"retry\" @click=${this._onRetry}>초기화</button>\n </div>\n </div>\n </div>\n `\n }\n\n protected async updated(changes: PropertyValues): Promise<void> {}\n\n async pageUpdated(changes: any, lifecycle: PageLifecycle) {\n if (this.active) {\n this._onRetry()\n this.buildingInspectionId = lifecycle.resourceId || ''\n await this.initBuildingInspection(this.buildingInspectionId)\n }\n }\n\n async initBuildingInspection(buildingInspectionId: string = '') {\n const response = await client.query({\n query: gql`\n query BuildingInspection($buildingInspectionId: String!) {\n buildingInspection(id: $buildingInspectionId) {\n id\n status\n requestDate\n drawingMarker\n checklist {\n id\n location\n inspectionDrawingType\n }\n buildingLevel {\n id\n floor\n mainDrawing {\n id\n name\n fullpath\n }\n mainDrawingImage\n building {\n id\n name\n buildingComplex {\n id\n }\n }\n }\n }\n }\n `,\n variables: {\n buildingInspectionId\n }\n })\n\n if (response.errors) return\n\n this.buildingInspection = response.data.buildingInspection\n\n await this._getProjectByBuildingComplexId(this.buildingInspection?.buildingLevel?.building?.buildingComplex?.id)\n }\n\n private async _getProjectByBuildingComplexId(buildingComplexId) {\n const response = await client.query({\n query: gql`\n query ProjectByBuildingComplexId($buildingComplexId: String!) {\n project: projectByBuildingComplexId(buildingComplexId: $buildingComplexId) {\n id\n name\n }\n }\n `,\n variables: {\n buildingComplexId\n }\n })\n\n if (response.errors) return\n\n this.project = response.data.project\n }\n\n private _onVideoSelected(event: Event) {\n const input = event.target as HTMLInputElement\n const file = input?.files?.[0]\n if (!file) return\n\n // Only allow video files\n if (!file.type.startsWith('video/')) {\n console.warn('선택된 파일이 동영상이 아닙니다.')\n return\n }\n\n // Revoke old object URL if exists\n if (this.capturedVideoUrl) {\n URL.revokeObjectURL(this.capturedVideoUrl)\n }\n\n this.videoFile = file\n this.capturedVideoUrl = URL.createObjectURL(file)\n }\n\n private async _onAnalyzeVideo() {\n if (!this.videoFile) {\n console.warn('선택된 동영상이 없습니다.')\n return\n }\n\n try {\n this.analyzing = true\n const form = new FormData()\n form.append('files', this.videoFile, this.videoFile.name)\n const response = await fetch(`${this.KEYPOINT_RULER_API_BASE_URL}/api/run/keypoint-ruler`, {\n method: 'POST',\n headers: { Authorization: `Basic ${btoa('admin:admin1234')}` },\n body: form\n })\n\n const data = await response.json()\n\n this.frame = {\n file_id: data?.result?.output_files[0]?.file_id,\n download_url: data?.result?.output_files[0]?.download_url,\n file_path: data?.result?.frames[0]?.file_path,\n calibration: data?.result?.calibration\n }\n } catch (error) {\n console.error('동영상 업로드 실패:', error)\n } finally {\n this.analyzing = false\n }\n }\n\n private _onRetry() {\n // Clear current selection and reopen camera\n if (this.capturedVideoUrl) {\n URL.revokeObjectURL(this.capturedVideoUrl)\n }\n this.capturedVideoUrl = null\n this.videoFile = null\n this.frame = null\n this.selectedPoints = { x1: null, y1: null, x2: null, y2: null }\n this.displayedImageRect = null\n this.frameNaturalWidth = 0\n this.frameNaturalHeight = 0\n this.cameraInputEl.value = ''\n this.distance = null\n this.unit = null\n this.analyzingError = null\n }\n\n private _onImageLoad = () => {\n if (!this.frameImageEl || !this.imageWrapperEl) return\n const img = this.frameImageEl\n const wrapperRect = this.imageWrapperEl.getBoundingClientRect()\n const imgRect = img.getBoundingClientRect()\n this.frameNaturalWidth = img.naturalWidth\n this.frameNaturalHeight = img.naturalHeight\n this.displayedImageRect = {\n width: imgRect.width,\n height: imgRect.height,\n left: imgRect.left - wrapperRect.left,\n top: imgRect.top - wrapperRect.top\n }\n }\n\n private _onImageClick = (event: MouseEvent) => {\n if (!this.frameImageEl || !this.imageWrapperEl) return\n const imgRect = this.frameImageEl.getBoundingClientRect()\n const wrapperRect = this.imageWrapperEl.getBoundingClientRect()\n const clientX = event.clientX\n const clientY = event.clientY\n\n // Ignore clicks outside the displayed image area\n if (clientX < imgRect.left || clientX > imgRect.right || clientY < imgRect.top || clientY > imgRect.bottom) {\n return\n }\n\n // 새로운 마크 입력 시 이전 거리/단위 결과 초기화\n this.distance = null\n this.unit = null\n this.analyzingError = null\n\n const relativeX = clientX - imgRect.left\n const relativeY = clientY - imgRect.top\n\n const xOnNatural = Math.round((relativeX * this.frameNaturalWidth) / imgRect.width)\n const yOnNatural = Math.round((relativeY * this.frameNaturalHeight) / imgRect.height)\n\n if (this.selectedPoints.x1 === null || this.selectedPoints.y1 === null) {\n this.selectedPoints = { ...this.selectedPoints, x1: xOnNatural, y1: yOnNatural }\n } else if (this.selectedPoints.x2 === null || this.selectedPoints.y2 === null) {\n this.selectedPoints = { ...this.selectedPoints, x2: xOnNatural, y2: yOnNatural }\n } else {\n // 세 번째 클릭부터는 초기화 후 첫 점으로 설정\n this.selectedPoints = { x1: xOnNatural, y1: yOnNatural, x2: null, y2: null }\n }\n\n // Update displayed rect cache (in case layout changed)\n this.displayedImageRect = {\n width: imgRect.width,\n height: imgRect.height,\n left: imgRect.left - wrapperRect.left,\n top: imgRect.top - wrapperRect.top\n }\n }\n\n private _hasTwoPoints(): boolean {\n const { x1, y1, x2, y2 } = this.selectedPoints\n return x1 !== null && y1 !== null && x2 !== null && y2 !== null\n }\n\n private _renderMarkers() {\n if (!this.displayedImageRect) return html``\n const { left, top, width, height } = this.displayedImageRect\n const { x1, y1, x2, y2 } = this.selectedPoints\n const toDisplay = (x: number, y: number) => {\n const dx = left + (x * width) / (this.frameNaturalWidth || 1)\n const dy = top + (y * height) / (this.frameNaturalHeight || 1)\n return { dx, dy }\n }\n\n return html`\n <!-- 첫 번째 점 -->\n ${x1 !== null && y1 !== null\n ? (() => {\n const p = toDisplay(x1, y1)\n return html`<div class=\"point-marker point-1\" style=\"left:${p.dx}px; top:${p.dy}px;\"></div>`\n })()\n : ''}\n <!-- 두 번째 점 -->\n ${x2 !== null && y2 !== null\n ? (() => {\n const p = toDisplay(x2, y2)\n return html`<div class=\"point-marker point-2\" style=\"left:${p.dx}px; top:${p.dy}px;\"></div>`\n })()\n : ''}\n <!-- 두점 사이 선 -->\n ${this._hasTwoPoints()\n ? (() => {\n const p1 = toDisplay(x1 as number, y1 as number)\n const p2 = toDisplay(x2 as number, y2 as number)\n const cx = (p1.dx + p2.dx) / 2\n const cy = (p1.dy + p2.dy) / 2\n\n // 선의 길이와 각도 계산\n const length = Math.sqrt(Math.pow(p2.dx - p1.dx, 2) + Math.pow(p2.dy - p1.dy, 2))\n const angle = Math.atan2(p2.dy - p1.dy, p2.dx - p1.dx) * (180 / Math.PI)\n\n return html`\n <!-- DIV로 그린 선 -->\n <div\n class=\"line-overlay\"\n style=\"\n left: ${p1.dx}px; \n top: ${p1.dy}px; \n width: ${length}px; \n height: 3px;\n transform: rotate(${angle}deg);\n \"\n ></div>\n <!-- 거리 라벨 -->\n ${this.analyzingError\n ? html`<div class=\"distance-label\" style=\"left:${cx}px; top:${cy}px;\">${this.analyzingError}</div>`\n : this.distance && this.unit\n ? html`<div class=\"distance-label\" style=\"left:${cx}px; top:${cy}px;\">${this.distance} ${this.unit}</div>`\n : ''}\n `\n })()\n : ''}\n `\n }\n\n private async _onAnalyzePhoto() {\n if (!this.frame) {\n console.warn('분석할 사진이 없습니다.')\n return\n }\n if (!this._hasTwoPoints()) {\n console.warn('두 점을 먼저 선택해 주세요.')\n return\n }\n\n try {\n this.analyzing = true\n const payload = {\n x1: this.selectedPoints.x1,\n y1: this.selectedPoints.y1,\n x2: this.selectedPoints.x2,\n y2: this.selectedPoints.y2,\n file_path: this.frame.file_path,\n calibration: this.frame.calibration\n }\n\n const form = new FormData()\n form.append('input', JSON.stringify(payload))\n const response = await fetch(`${this.KEYPOINT_RULER_API_BASE_URL}/api/run/keypoint-ruler`, {\n method: 'POST',\n headers: { Authorization: `Basic ${btoa('admin:admin1234')}` },\n body: form\n })\n\n const result = await response.json()\n const { distance, unit, error } = result.result || {}\n this.distance = distance\n this.unit = unit\n this.analyzingError = error\n } catch (error) {\n console.error('사진 분석 실패:', error)\n } finally {\n this.analyzing = false\n }\n }\n\n async _savePhoto() {\n if (!this.frame || !this._hasTwoPoints() || this.distance === null || !this.unit) {\n notify({ message: '분석 완료 후 저장할 수 있습니다.', level: 'warn' })\n return\n }\n\n const checklistId = this.buildingInspection?.checklist?.id\n let file: File\n try {\n file = await this.getCurrentImage()\n } catch (e) {\n notify({ message: '이미지 합성에 실패했습니다.', level: 'error' })\n return\n }\n\n const response = await client.mutate({\n mutation: gql`\n mutation ($attachments: [NewAttachment!]!) {\n createAttachments(attachments: $attachments) {\n id\n }\n }\n `,\n variables: {\n attachments: [{ file, refBy: checklistId, refType: 'Checklist', description: this.buildingInspection?.status }]\n },\n context: { hasUpload: true }\n })\n\n if (!response.errors) {\n notify({ message: '사진 대지를 저장하였습니다.', level: 'info' })\n return\n }\n\n return response.data.createAttachments\n }\n\n async getCurrentImage(): Promise<File> {\n if (!this.imageWrapperEl) {\n throw new Error('이미지 영역을 찾을 수 없습니다.')\n }\n\n try {\n // html2canvas로 .image-wrapper 영역 캡처\n const canvas = await html2canvas(this.imageWrapperEl, {\n backgroundColor: null, // 투명 배경\n scale: 1, // 1:1 스케일\n useCORS: true, // CORS 이미지 허용\n allowTaint: false, // 보안상 false\n logging: false // 로그 비활성화\n })\n\n // 캔버스를 Blob으로 변환\n const blob: Blob = await new Promise(resolve => {\n canvas.toBlob(resolve as any, 'image/png', 1.0)\n })\n\n // File 객체로 변환하여 반환\n return new File([blob!], `inspection_${Date.now()}.png`, {\n type: 'image/png'\n })\n } catch (error) {\n console.error('html2canvas 캡처 실패:', error)\n throw new Error('화면 캡처에 실패했습니다.')\n }\n }\n}\n"]}
1
+ {"version":3,"file":"building-inspection-detail-ai-measurement.js","sourceRoot":"","sources":["../../../client/pages/building-inspection/building-inspection-detail-ai-measurement.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@operato/input/ox-input-switch.js'\nimport '@operato/mini-map/ox-zoomable-image.js'\nimport '@material/web/progress/circular-progress.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, PropertyValues } from 'lit'\nimport { customElement, state, query } from 'lit/decorators.js'\nimport html2canvas from 'html2canvas'\n\nimport { PageView } from '@operato/shell'\nimport { CommonGristStyles, ScrollbarStyles } from '@operato/styles'\nimport { PageLifecycle } from '@operato/shell/dist/src/app/pages/page-view'\nimport { client } from '@operato/graphql'\nimport { notify } from '@operato/layout'\n\nimport './component/building-inspection-detail-header'\n\n@customElement('building-inspection-detail-ai-measurement')\nexport class BuildingInspectionAiMeasurement extends PageView {\n static styles = [\n ScrollbarStyles,\n CommonGristStyles,\n css`\n :host {\n display: grid;\n grid-template-rows: 75px 1fr;\n color: #4e5055;\n width: 100%;\n height: 100%;\n background-color: #f7f7f7;\n }\n\n div[body] {\n display: flex;\n justify-items: center;\n gap: var(--spacing-medium);\n margin: var(--spacing-medium);\n min-height: 0;\n }\n\n div[preview] {\n flex: 1;\n border: 2px solid #ddd;\n border-radius: 10px;\n\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 0;\n overflow: hidden;\n }\n\n div[preview] img {\n max-width: 100%;\n max-height: 100%;\n object-fit: contain;\n }\n\n div[preview] video {\n max-width: 100%;\n max-height: 100%;\n object-fit: contain;\n }\n\n div[preview] md-icon {\n --md-icon-size: 160px;\n }\n\n .loading-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n }\n\n .loading-text {\n font-size: 14px;\n color: #666;\n }\n\n .frame-container {\n width: 100%;\n height: 100%;\n max-height: 100%;\n min-height: 0;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 12px;\n }\n\n .frames-header {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 12px;\n font-size: 20px;\n font-weight: 700;\n color: #333;\n text-align: center;\n padding-block: 8px;\n }\n\n .frames-count {\n font-size: 16px;\n color: #777;\n font-weight: 600;\n }\n\n .frames-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));\n gap: 12px;\n }\n\n .frame-item {\n border: 1px solid #ddd;\n border-radius: 8px;\n overflow: hidden;\n background: #fff;\n cursor: pointer;\n transition:\n box-shadow 0.2s,\n transform 0.1s;\n }\n\n .frame-item:hover {\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n transform: translateY(-1px);\n }\n\n .frame-item img {\n display: block;\n width: 100%;\n height: 120px;\n object-fit: cover;\n }\n\n .image-wrapper {\n position: relative;\n width: 100%;\n height: 100%;\n align-content: center;\n text-align: center;\n }\n\n .point-marker {\n position: absolute;\n width: 14px;\n height: 14px;\n border-radius: 50%;\n border: 2px solid #fff;\n transform: translate(-50%, -50%);\n pointer-events: none;\n box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.25);\n }\n\n .point-1 {\n background-color: #e53935;\n }\n\n .point-2 {\n background-color: #1e88e5;\n }\n\n .line-overlay {\n position: absolute;\n pointer-events: none;\n background-color: #ff9800;\n transform-origin: left center;\n }\n\n .distance-label {\n position: absolute;\n background: rgba(0, 0, 0, 0.7);\n color: #fff;\n font-size: 14px;\n padding: 2px 6px;\n border-radius: 4px;\n transform: translate(-50%, -120%);\n pointer-events: none;\n white-space: nowrap;\n }\n\n div[controls] {\n width: 240px;\n display: flex;\n flex-direction: column;\n justify-content: space-between;\n align-items: center;\n }\n\n div[action-buttons] {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n gap: 15px;\n }\n\n .switch-container {\n display: flex;\n align-items: center;\n gap: 10px;\n font-size: 24px;\n font-weight: bold;\n }\n\n ox-input-switch {\n --ox-simple-switch-fullwidth: 68px;\n --ox-simple-switch-fullheight: 34px;\n --ox-simple-switch-thumbnail-size: 34px;\n }\n\n .camera-shutter {\n display: flex;\n justify-content: center;\n align-items: center;\n cursor: pointer;\n position: relative;\n }\n\n .controls-title {\n font-size: 18px;\n font-weight: 700;\n color: #333;\n margin-bottom: 8px;\n text-align: center;\n }\n\n .camera-shutter md-icon {\n --md-icon-size: 100px;\n }\n\n .camera-shutter input {\n opacity: 0;\n width: 100%;\n height: 100%;\n position: absolute;\n left: 0;\n top: 0;\n cursor: pointer;\n }\n\n button {\n padding: 10px 20px;\n font-size: 16px;\n border-radius: 5px;\n border: none;\n cursor: pointer;\n }\n\n button.save {\n background-color: #4caf50;\n color: white;\n\n &:disabled {\n background-color: #ccc;\n cursor: default;\n }\n }\n\n button.retry {\n background-color: #f0ad4e;\n color: white;\n }\n\n button.photo {\n background-color: #1e88e5;\n color: white;\n\n &:disabled {\n background-color: #ccc;\n cursor: default;\n }\n }\n\n .loading-overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.5);\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n z-index: 2000;\n }\n\n .loading-overlay .loading-container {\n background: white;\n padding: 32px;\n border-radius: 12px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);\n }\n `\n ]\n\n private KEYPOINT_RULER_API_BASE_URL: string = 'https://hatiolab-korea-uni.ettisoft.com'\n\n @state() project: any = {}\n @state() buildingInspection: any = {}\n @state() buildingInspectionId: string = ''\n @state() capturedVideoUrl: string | null = null\n @state() videoFile: File | null = null\n @state() frame: any | null = null\n @state() analyzing: boolean = false\n @state() selectedPoints: { x1: number | null; y1: number | null; x2: number | null; y2: number | null } = {\n x1: null,\n y1: null,\n x2: null,\n y2: null\n }\n @state() frameNaturalWidth: number = 0\n @state() frameNaturalHeight: number = 0\n @state() displayedImageRect: { width: number; height: number; left: number; top: number } | null = null\n @state() distance: number | null = null\n @state() unit: string | null = null\n @state() analyzingError: string | null = null\n\n @query('img#frameImage') private frameImageEl!: HTMLImageElement\n @query('div.image-wrapper') private imageWrapperEl!: HTMLDivElement\n @query('#cameraInput') private cameraInputEl!: HTMLInputElement\n\n get context() {\n return {\n title: '검측 관리 상세 - AI 거리 측정'\n }\n }\n\n render() {\n return html`\n <building-inspection-detail-header\n .buildingInspectionId=${this.buildingInspection?.id}\n .buildingLevelId=${this.buildingInspection?.buildingLevel?.id}\n .projectName=${this.project.name}\n .buildingName=${this.buildingInspection?.buildingLevel?.building?.name}\n .buildingLevelFloor=${this.buildingInspection?.buildingLevel?.floor}\n ></building-inspection-detail-header>\n\n <div body>\n <!-- Preview selected or recorded video -->\n <div preview>\n ${this.frame?.download_url\n ? html`\n <div class=\"image-wrapper\" @click=${this._onImageClick}>\n <img\n id=\"frameImage\"\n src=\"${this.KEYPOINT_RULER_API_BASE_URL + this.frame?.download_url}\"\n crossorigin=\"anonymous\"\n alt=\"frame\"\n loading=\"eager\"\n @load=${this._onImageLoad}\n />\n ${this._renderMarkers()}\n </div>\n `\n : // 동영상 선택 후 동영상 프리뷰\n this.capturedVideoUrl\n ? html`<video src=${this.capturedVideoUrl} controls playsinline></video>`\n : // 초기 상태, 동영상 선택 전\n html`<md-icon>videocam</md-icon>`}\n </div>\n\n <div controls>\n <span class=\"controls-title\">동영상 촬영</span>\n\n <!-- 가운데 버튼 -->\n ${this.frame?.download_url && this._hasTwoPoints() && this.distance && this.unit\n ? // 거리 측정 완료 시, 사진 저장 버튼 표시\n html`<button class=\"save\" ?disabled=${this.analyzing} @click=${this._savePhoto}>사진 저장</button>`\n : // 거리 측정 완료 전, 동영상 촬영 버튼 표시\n html`<div class=\"camera-shutter\">\n <md-icon>camera</md-icon>\n <input id=\"cameraInput\" type=\"file\" accept=\"video/*\" @change=\"${this._onVideoSelected}\" />\n </div>`}\n\n <!-- Action buttons -->\n <div action-buttons>\n ${this.frame\n ? html`\n <button class=\"photo\" ?disabled=${this.analyzing || !this._hasTwoPoints()} @click=${this._onAnalyzePhoto}>\n 사진 분석\n </button>\n `\n : html`\n <button class=\"save\" ?disabled=${this.analyzing || !this.capturedVideoUrl} @click=${this._onAnalyzeVideo}>\n 동영상 분석\n </button>\n `}\n <button class=\"retry\" @click=${this._onRetry}>초기화</button>\n </div>\n </div>\n </div>\n\n <!-- 전체 화면을 덮는 로딩 오버레이 -->\n ${this.analyzing\n ? html`<div class=\"loading-overlay\">\n <div class=\"loading-container\">\n <md-circular-progress indeterminate></md-circular-progress>\n <div class=\"loading-text\">분석 중...</div>\n </div>\n </div>`\n : ''}\n `\n }\n\n protected async updated(changes: PropertyValues): Promise<void> {}\n\n async pageUpdated(changes: any, lifecycle: PageLifecycle) {\n if (this.active) {\n this._onRetry()\n this.buildingInspectionId = lifecycle.resourceId || ''\n await this.initBuildingInspection(this.buildingInspectionId)\n }\n }\n\n async initBuildingInspection(buildingInspectionId: string = '') {\n const response = await client.query({\n query: gql`\n query BuildingInspection($buildingInspectionId: String!) {\n buildingInspection(id: $buildingInspectionId) {\n id\n status\n requestDate\n drawingMarker\n checklist {\n id\n location\n inspectionDrawingType\n }\n buildingLevel {\n id\n floor\n mainDrawing {\n id\n name\n fullpath\n }\n mainDrawingImage\n building {\n id\n name\n buildingComplex {\n id\n }\n }\n }\n }\n }\n `,\n variables: {\n buildingInspectionId\n }\n })\n\n if (response.errors) return\n\n this.buildingInspection = response.data.buildingInspection\n\n await this._getProjectByBuildingComplexId(this.buildingInspection?.buildingLevel?.building?.buildingComplex?.id)\n }\n\n private async _getProjectByBuildingComplexId(buildingComplexId) {\n const response = await client.query({\n query: gql`\n query ProjectByBuildingComplexId($buildingComplexId: String!) {\n project: projectByBuildingComplexId(buildingComplexId: $buildingComplexId) {\n id\n name\n }\n }\n `,\n variables: {\n buildingComplexId\n }\n })\n\n if (response.errors) return\n\n this.project = response.data.project\n }\n\n private _onVideoSelected(event: Event) {\n const input = event.target as HTMLInputElement\n const file = input?.files?.[0]\n if (!file) return\n\n // Only allow video files\n if (!file.type.startsWith('video/')) {\n console.warn('선택된 파일이 동영상이 아닙니다.')\n return\n }\n\n // Revoke old object URL if exists\n if (this.capturedVideoUrl) {\n URL.revokeObjectURL(this.capturedVideoUrl)\n }\n\n this.videoFile = file\n this.capturedVideoUrl = URL.createObjectURL(file)\n }\n\n private async _onAnalyzeVideo() {\n if (!this.videoFile) {\n console.warn('선택된 동영상이 없습니다.')\n return\n }\n\n try {\n this.analyzing = true\n const form = new FormData()\n form.append('files', this.videoFile, this.videoFile.name)\n const response = await fetch(`${this.KEYPOINT_RULER_API_BASE_URL}/api/run/keypoint-ruler`, {\n method: 'POST',\n headers: { Authorization: `Basic ${btoa('admin:admin1234')}` },\n body: form\n })\n\n const data = await response.json()\n\n this.frame = {\n file_id: data?.result?.output_files[0]?.file_id,\n download_url: data?.result?.output_files[0]?.download_url,\n file_path: data?.result?.frames[0]?.file_path,\n calibration: data?.result?.calibration\n }\n } catch (error) {\n console.error('동영상 업로드 실패:', error)\n } finally {\n this.analyzing = false\n }\n }\n\n private _onRetry() {\n // Clear current selection and reopen camera\n if (this.capturedVideoUrl) {\n URL.revokeObjectURL(this.capturedVideoUrl)\n }\n this.capturedVideoUrl = null\n this.videoFile = null\n this.frame = null\n this.selectedPoints = { x1: null, y1: null, x2: null, y2: null }\n this.displayedImageRect = null\n this.frameNaturalWidth = 0\n this.frameNaturalHeight = 0\n this.cameraInputEl.value = ''\n this.distance = null\n this.unit = null\n this.analyzingError = null\n }\n\n private _onImageLoad = () => {\n if (!this.frameImageEl || !this.imageWrapperEl) return\n const img = this.frameImageEl\n const wrapperRect = this.imageWrapperEl.getBoundingClientRect()\n const imgRect = img.getBoundingClientRect()\n this.frameNaturalWidth = img.naturalWidth\n this.frameNaturalHeight = img.naturalHeight\n this.displayedImageRect = {\n width: imgRect.width,\n height: imgRect.height,\n left: imgRect.left - wrapperRect.left,\n top: imgRect.top - wrapperRect.top\n }\n }\n\n private _onImageClick = (event: MouseEvent) => {\n if (!this.frameImageEl || !this.imageWrapperEl) return\n const imgRect = this.frameImageEl.getBoundingClientRect()\n const wrapperRect = this.imageWrapperEl.getBoundingClientRect()\n const clientX = event.clientX\n const clientY = event.clientY\n\n // Ignore clicks outside the displayed image area\n if (clientX < imgRect.left || clientX > imgRect.right || clientY < imgRect.top || clientY > imgRect.bottom) {\n return\n }\n\n // 새로운 마크 입력 시 이전 거리/단위 결과 초기화\n this.distance = null\n this.unit = null\n this.analyzingError = null\n\n const relativeX = clientX - imgRect.left\n const relativeY = clientY - imgRect.top\n\n const xOnNatural = Math.round((relativeX * this.frameNaturalWidth) / imgRect.width)\n const yOnNatural = Math.round((relativeY * this.frameNaturalHeight) / imgRect.height)\n\n if (this.selectedPoints.x1 === null || this.selectedPoints.y1 === null) {\n this.selectedPoints = { ...this.selectedPoints, x1: xOnNatural, y1: yOnNatural }\n } else if (this.selectedPoints.x2 === null || this.selectedPoints.y2 === null) {\n this.selectedPoints = { ...this.selectedPoints, x2: xOnNatural, y2: yOnNatural }\n } else {\n // 세 번째 클릭부터는 초기화 후 첫 점으로 설정\n this.selectedPoints = { x1: xOnNatural, y1: yOnNatural, x2: null, y2: null }\n }\n\n // Update displayed rect cache (in case layout changed)\n this.displayedImageRect = {\n width: imgRect.width,\n height: imgRect.height,\n left: imgRect.left - wrapperRect.left,\n top: imgRect.top - wrapperRect.top\n }\n }\n\n private _hasTwoPoints(): boolean {\n const { x1, y1, x2, y2 } = this.selectedPoints\n return x1 !== null && y1 !== null && x2 !== null && y2 !== null\n }\n\n private _renderMarkers() {\n if (!this.displayedImageRect) return html``\n const { left, top, width, height } = this.displayedImageRect\n const { x1, y1, x2, y2 } = this.selectedPoints\n const toDisplay = (x: number, y: number) => {\n const dx = left + (x * width) / (this.frameNaturalWidth || 1)\n const dy = top + (y * height) / (this.frameNaturalHeight || 1)\n return { dx, dy }\n }\n\n return html`\n <!-- 첫 번째 점 -->\n ${x1 !== null && y1 !== null\n ? (() => {\n const p = toDisplay(x1, y1)\n return html`<div class=\"point-marker point-1\" style=\"left:${p.dx}px; top:${p.dy}px;\"></div>`\n })()\n : ''}\n <!-- 두 번째 점 -->\n ${x2 !== null && y2 !== null\n ? (() => {\n const p = toDisplay(x2, y2)\n return html`<div class=\"point-marker point-2\" style=\"left:${p.dx}px; top:${p.dy}px;\"></div>`\n })()\n : ''}\n <!-- 두점 사이 선 -->\n ${this._hasTwoPoints()\n ? (() => {\n const p1 = toDisplay(x1 as number, y1 as number)\n const p2 = toDisplay(x2 as number, y2 as number)\n const cx = (p1.dx + p2.dx) / 2\n const cy = (p1.dy + p2.dy) / 2\n\n // 선의 길이와 각도 계산\n const length = Math.sqrt(Math.pow(p2.dx - p1.dx, 2) + Math.pow(p2.dy - p1.dy, 2))\n const angle = Math.atan2(p2.dy - p1.dy, p2.dx - p1.dx) * (180 / Math.PI)\n\n return html`\n <!-- DIV로 그린 선 -->\n <div\n class=\"line-overlay\"\n style=\"\n left: ${p1.dx}px; \n top: ${p1.dy}px; \n width: ${length}px; \n height: 3px;\n transform: rotate(${angle}deg);\n \"\n ></div>\n <!-- 거리 라벨 -->\n ${this.analyzingError\n ? html`<div class=\"distance-label\" style=\"left:${cx}px; top:${cy}px;\">${this.analyzingError}</div>`\n : this.distance && this.unit\n ? html`<div class=\"distance-label\" style=\"left:${cx}px; top:${cy}px;\">${this.distance} ${this.unit}</div>`\n : ''}\n `\n })()\n : ''}\n `\n }\n\n private async _onAnalyzePhoto() {\n if (!this.frame) {\n console.warn('분석할 사진이 없습니다.')\n return\n }\n if (!this._hasTwoPoints()) {\n console.warn('두 점을 먼저 선택해 주세요.')\n return\n }\n\n try {\n this.analyzing = true\n const payload = {\n x1: this.selectedPoints.x1,\n y1: this.selectedPoints.y1,\n x2: this.selectedPoints.x2,\n y2: this.selectedPoints.y2,\n file_path: this.frame.file_path,\n calibration: this.frame.calibration\n }\n\n const form = new FormData()\n form.append('input', JSON.stringify(payload))\n const response = await fetch(`${this.KEYPOINT_RULER_API_BASE_URL}/api/run/keypoint-ruler`, {\n method: 'POST',\n headers: { Authorization: `Basic ${btoa('admin:admin1234')}` },\n body: form\n })\n\n const result = await response.json()\n const { distance, unit, error } = result.result || {}\n this.distance = distance\n this.unit = unit\n this.analyzingError = error\n } catch (error) {\n console.error('사진 분석 실패:', error)\n } finally {\n this.analyzing = false\n }\n }\n\n async _savePhoto() {\n if (!this.frame || !this._hasTwoPoints() || this.distance === null || !this.unit) {\n notify({ message: '분석 완료 후 저장할 수 있습니다.', level: 'warn' })\n return\n }\n\n const checklistId = this.buildingInspection?.checklist?.id\n let file: File\n try {\n file = await this.getCurrentImage()\n } catch (e) {\n notify({ message: '이미지 합성에 실패했습니다.', level: 'error' })\n return\n }\n\n const response = await client.mutate({\n mutation: gql`\n mutation ($attachments: [NewAttachment!]!) {\n createAttachments(attachments: $attachments) {\n id\n }\n }\n `,\n variables: {\n attachments: [{ file, refBy: checklistId, refType: 'Checklist', description: this.buildingInspection?.status }]\n },\n context: { hasUpload: true }\n })\n\n if (!response.errors) {\n notify({ message: '사진 대지를 저장하였습니다.', level: 'info' })\n return\n }\n\n return response.data.createAttachments\n }\n\n async getCurrentImage(): Promise<File> {\n if (!this.imageWrapperEl) {\n throw new Error('이미지 영역을 찾을 수 없습니다.')\n }\n\n try {\n // html2canvas로 .image-wrapper 영역 캡처\n const canvas = await html2canvas(this.imageWrapperEl, {\n backgroundColor: null, // 투명 배경\n scale: 1, // 1:1 스케일\n useCORS: true, // CORS 이미지 허용\n allowTaint: false, // 보안상 false\n logging: false // 로그 비활성화\n })\n\n // 캔버스를 Blob으로 변환\n const blob: Blob = await new Promise(resolve => {\n canvas.toBlob(resolve as any, 'image/png', 1.0)\n })\n\n // File 객체로 변환하여 반환\n return new File([blob!], `inspection_${Date.now()}.png`, {\n type: 'image/png'\n })\n } catch (error) {\n console.error('html2canvas 캡처 실패:', error)\n throw new Error('화면 캡처에 실패했습니다.')\n }\n }\n}\n"]}
@@ -59,7 +59,7 @@ let BuildingInspectionDetailHeader = class BuildingInspectionDetailHeader extend
59
59
  AI 길이 측정
60
60
  </md-menu-item>
61
61
  <md-divider></md-divider>
62
- <md-menu-item @click=${this.handleDefectAnalysis}>
62
+ <md-menu-item @click=${() => navigate(`building-inspection-detail-ai-defect/${this.buildingInspectionId}`)}>
63
63
  <md-icon slot="start">manga</md-icon>
64
64
  AI 하자 분석
65
65
  </md-menu-item>
@@ -76,10 +76,6 @@ let BuildingInspectionDetailHeader = class BuildingInspectionDetailHeader extend
76
76
  </div>
77
77
  `;
78
78
  }
79
- handleDefectAnalysis() {
80
- // 하자 분석 기능 - 추후 구현
81
- console.log('하자 분석 기능');
82
- }
83
79
  togglePhotoMenu() {
84
80
  this.isPhotoMenuOpen = !this.isPhotoMenuOpen;
85
81
  }
@@ -1 +1 @@
1
- {"version":3,"file":"building-inspection-detail-header.js","sourceRoot":"","sources":["../../../../client/pages/building-inspection/component/building-inspection-detail-header.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@material/web/menu/menu.js'\nimport '@material/web/menu/menu-item.js'\nimport '@material/web/divider/divider.js'\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { ButtonContainerStyles, ScrollbarStyles } from '@operato/styles'\nimport { navigate } from '@operato/shell'\n\n@customElement('building-inspection-detail-header')\nclass BuildingInspectionDetailHeader extends LitElement {\n static styles = [\n ButtonContainerStyles,\n ScrollbarStyles,\n css`\n md-filled-button {\n --md-filled-button-container-color: #0595e5;\n --md-filled-button-container-height: 30px;\n --md-filled-button-trailing-space: 15px;\n --md-filled-button-leading-space: 15px;\n }\n md-outlined-button {\n --md-outlined-button-container-height: 30px;\n --md-outlined-button-trailing-space: 15px;\n --md-outlined-button-leading-space: 15px;\n }\n\n *[bold] {\n font-weight: bold;\n }\n\n div[header] {\n display: flex;\n margin: 0px var(--spacing-large, 12px);\n\n h2 {\n display: flex;\n gap: 7px;\n flex: 0.5;\n color: #3f71a0;\n font-size: 18px;\n\n md-icon[back] {\n background: linear-gradient(135deg, #3f71a0 0%, #5a8cc7 100%);\n color: white;\n padding: 8px;\n border-radius: 50%;\n cursor: pointer;\n box-shadow: 0 2px 8px rgba(63, 113, 160, 0.3);\n width: 14px;\n height: 14px;\n --md-icon-size: 20px;\n }\n\n md-icon[back]:hover {\n background: linear-gradient(135deg, #2e5c89 0%, #4a7bb0 100%);\n box-shadow: 0 4px 12px rgba(63, 113, 160, 0.4);\n }\n }\n }\n\n div[button-container] {\n display: flex;\n align-items: center;\n justify-content: end;\n flex: 0.5;\n }\n\n md-elevated-button {\n margin-left: var(--spacing-small, 4px);\n letter-spacing: -1px;\n\n --md-elevated-button-container-height: 32px;\n --md-elevated-button-label-text-size: 16px;\n --md-elevated-button-container-color: #0595e5;\n\n --md-elevated-button-label-text-color: var(--md-sys-color-on-primary);\n --md-elevated-button-hover-label-text-color: var(--md-sys-color-on-primary);\n --md-elevated-button-pressed-label-text-color: var(--md-sys-color-on-primary);\n --md-elevated-button-focus-label-text-color: var(--md-sys-color-on-primary);\n --md-elevated-button-icon-color: var(--md-sys-color-on-primary);\n --md-elevated-button-hover-icon-color: var(--md-sys-color-on-primary);\n --md-elevated-button-pressed-icon-color: var(--md-sys-color-on-primary);\n --md-elevated-button-focus-icon-color: var(--md-sys-color-on-primary);\n --_leading-space: var(--spacing-tiny, 2px);\n\n --_with-leading-icon-leading-space: var(--spacing-medium, 8px);\n --_with-leading-icon-trailing-space: var(--spacing-medium, 8px);\n\n md-icon {\n margin-right: -2px;\n }\n }\n\n /* 드롭다운 메뉴 컨테이너 */\n .photo-menu-container {\n position: relative;\n display: inline-block;\n }\n\n md-menu {\n --md-menu-container-color: #fff;\n --md-menu-container-elevation: 2;\n border-radius: 8px;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n min-width: 150px;\n }\n\n md-menu-item {\n --md-menu-item-container-height: 20px;\n --md-menu-item-one-line-container-height: 30px;\n --md-menu-item-label-text-size: 15px;\n --md-menu-item-label-text-color: #333;\n --md-menu-item-hover-state-layer-color: #f5f5f5;\n --md-menu-item-top-space: 0px;\n --md-menu-item-bottom-space: 0px;\n\n cursor: pointer;\n padding: 2px 4px !important;\n min-height: 18px !important;\n }\n\n md-menu-item md-icon {\n margin-right: 2px;\n color: #666;\n --md-icon-size: 14px;\n }\n\n md-divider {\n margin: 1px 0;\n --md-divider-thickness: 1px;\n }\n `\n ]\n\n @property({ type: String }) buildingInspectionId: string = ''\n @property({ type: String }) buildingLevelId: string = ''\n @property({ type: String }) projectName: string = ''\n @property({ type: String }) buildingName: string = ''\n @property({ type: String }) buildingLevelFloor: string = ''\n\n @state() private isPhotoMenuOpen = false\n\n render() {\n const path = window.location.pathname\n\n return html`\n <div header>\n <h2>\n <md-icon slot=\"icon\" back @click=${() => history.back()}>arrow_back</md-icon>\n ${this.projectName || ''} ${this.buildingName || ''} ${this.buildingLevelFloor || ''}층\n </h2>\n <div button-container>\n <md-elevated-button\n ?disabled=${path.includes('building-inspection-list/')}\n href=${`building-inspection-list/${this.buildingLevelId}`}\n >\n <md-icon slot=\"icon\">list_alt</md-icon>검측 리스트\n </md-elevated-button>\n <md-elevated-button\n ?disabled=${path.includes('building-inspection-detail-drawing/')}\n href=${`building-inspection-detail-drawing/${this.buildingInspectionId}`}\n >\n <md-icon slot=\"icon\">fact_check</md-icon>검측도면\n </md-elevated-button>\n <md-elevated-button\n ?disabled=${path.includes('building-inspection-detail-checklist/')}\n href=${`building-inspection-detail-checklist/${this.buildingInspectionId}`}\n >\n <md-icon slot=\"icon\">task</md-icon>검측 체크리스트\n </md-elevated-button>\n <div class=\"photo-menu-container\">\n <md-elevated-button @click=${this.togglePhotoMenu} id=\"photo-menu-anchor\">\n <md-icon slot=\"icon\">arrow_drop_down</md-icon>사진촬영\n </md-elevated-button>\n <md-menu .open=${this.isPhotoMenuOpen} anchor=\"photo-menu-anchor\" @closed=${() => (this.isPhotoMenuOpen = false)}>\n <md-menu-item @click=${() => navigate(`building-inspection-detail-camera/${this.buildingInspectionId}`)}>\n <md-icon slot=\"start\">photo_camera</md-icon>\n 사진 촬영\n </md-menu-item>\n <md-divider></md-divider>\n <md-menu-item @click=${() => navigate(`building-inspection-detail-ai-measurement/${this.buildingInspectionId}`)}>\n <md-icon slot=\"start\">straighten</md-icon>\n AI 길이 측정\n </md-menu-item>\n <md-divider></md-divider>\n <md-menu-item @click=${this.handleDefectAnalysis}>\n <md-icon slot=\"start\">manga</md-icon>\n AI 하자 분석\n </md-menu-item>\n </md-menu>\n </div>\n <md-elevated-button\n ?disabled=${path.includes('building-inspection-detail-history/')}\n href=${`building-inspection-detail-history/${this.buildingInspectionId}`}\n disabled\n >\n <md-icon slot=\"icon\">pending_actions</md-icon>감리이력\n </md-elevated-button>\n </div>\n </div>\n `\n }\n\n private handleDefectAnalysis() {\n // 하자 분석 기능 - 추후 구현\n console.log('하자 분석 기능')\n }\n\n private togglePhotoMenu() {\n this.isPhotoMenuOpen = !this.isPhotoMenuOpen\n }\n}\n"]}
1
+ {"version":3,"file":"building-inspection-detail-header.js","sourceRoot":"","sources":["../../../../client/pages/building-inspection/component/building-inspection-detail-header.ts"],"names":[],"mappings":";AAAA,OAAO,4BAA4B,CAAA;AACnC,OAAO,4BAA4B,CAAA;AACnC,OAAO,iCAAiC,CAAA;AACxC,OAAO,kCAAkC,CAAA;AACzC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAClE,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACxE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAGzC,IAAM,8BAA8B,GAApC,MAAM,8BAA+B,SAAQ,UAAU;IAAvD;;QA6H8B,yBAAoB,GAAW,EAAE,CAAA;QACjC,oBAAe,GAAW,EAAE,CAAA;QAC5B,gBAAW,GAAW,EAAE,CAAA;QACxB,iBAAY,GAAW,EAAE,CAAA;QACzB,uBAAkB,GAAW,EAAE,CAAA;QAE1C,oBAAe,GAAG,KAAK,CAAA;IAkE1C,CAAC;IAhEC,MAAM;QACJ,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAA;QAErC,OAAO,IAAI,CAAA;;;6CAG8B,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE;YACrD,IAAI,CAAC,WAAW,IAAI,EAAE,IAAI,IAAI,CAAC,YAAY,IAAI,EAAE,IAAI,IAAI,CAAC,kBAAkB,IAAI,EAAE;;;;wBAItE,IAAI,CAAC,QAAQ,CAAC,2BAA2B,CAAC;mBAC/C,4BAA4B,IAAI,CAAC,eAAe,EAAE;;;;;wBAK7C,IAAI,CAAC,QAAQ,CAAC,qCAAqC,CAAC;mBACzD,sCAAsC,IAAI,CAAC,oBAAoB,EAAE;;;;;wBAK5D,IAAI,CAAC,QAAQ,CAAC,uCAAuC,CAAC;mBAC3D,wCAAwC,IAAI,CAAC,oBAAoB,EAAE;;;;;yCAK7C,IAAI,CAAC,eAAe;;;6BAGhC,IAAI,CAAC,eAAe,uCAAuC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,eAAe,GAAG,KAAK,CAAC;qCACvF,GAAG,EAAE,CAAC,QAAQ,CAAC,qCAAqC,IAAI,CAAC,oBAAoB,EAAE,CAAC;;;;;qCAKhF,GAAG,EAAE,CAAC,QAAQ,CAAC,6CAA6C,IAAI,CAAC,oBAAoB,EAAE,CAAC;;;;;qCAKxF,GAAG,EAAE,CAAC,QAAQ,CAAC,wCAAwC,IAAI,CAAC,oBAAoB,EAAE,CAAC;;;;;;;wBAOhG,IAAI,CAAC,QAAQ,CAAC,qCAAqC,CAAC;mBACzD,sCAAsC,IAAI,CAAC,oBAAoB,EAAE;;;;;;;KAO/E,CAAA;IACH,CAAC;IAEO,eAAe;QACrB,IAAI,CAAC,eAAe,GAAG,CAAC,IAAI,CAAC,eAAe,CAAA;IAC9C,CAAC;;AAnMM,qCAAM,GAAG;IACd,qBAAqB;IACrB,eAAe;IACf,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAsHF;CACF,AA1HY,CA0HZ;AAE2B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;4EAAkC;AACjC;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;uEAA6B;AAC5B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;mEAAyB;AACxB;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;oEAA0B;AACzB;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;0EAAgC;AAE1C;IAAhB,KAAK,EAAE;;uEAAgC;AAnIpC,8BAA8B;IADnC,aAAa,CAAC,mCAAmC,CAAC;GAC7C,8BAA8B,CAqMnC","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@material/web/menu/menu.js'\nimport '@material/web/menu/menu-item.js'\nimport '@material/web/divider/divider.js'\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { ButtonContainerStyles, ScrollbarStyles } from '@operato/styles'\nimport { navigate } from '@operato/shell'\n\n@customElement('building-inspection-detail-header')\nclass BuildingInspectionDetailHeader extends LitElement {\n static styles = [\n ButtonContainerStyles,\n ScrollbarStyles,\n css`\n md-filled-button {\n --md-filled-button-container-color: #0595e5;\n --md-filled-button-container-height: 30px;\n --md-filled-button-trailing-space: 15px;\n --md-filled-button-leading-space: 15px;\n }\n md-outlined-button {\n --md-outlined-button-container-height: 30px;\n --md-outlined-button-trailing-space: 15px;\n --md-outlined-button-leading-space: 15px;\n }\n\n *[bold] {\n font-weight: bold;\n }\n\n div[header] {\n display: flex;\n margin: 0px var(--spacing-large, 12px);\n\n h2 {\n display: flex;\n gap: 7px;\n flex: 0.5;\n color: #3f71a0;\n font-size: 18px;\n\n md-icon[back] {\n background: linear-gradient(135deg, #3f71a0 0%, #5a8cc7 100%);\n color: white;\n padding: 8px;\n border-radius: 50%;\n cursor: pointer;\n box-shadow: 0 2px 8px rgba(63, 113, 160, 0.3);\n width: 14px;\n height: 14px;\n --md-icon-size: 20px;\n }\n\n md-icon[back]:hover {\n background: linear-gradient(135deg, #2e5c89 0%, #4a7bb0 100%);\n box-shadow: 0 4px 12px rgba(63, 113, 160, 0.4);\n }\n }\n }\n\n div[button-container] {\n display: flex;\n align-items: center;\n justify-content: end;\n flex: 0.5;\n }\n\n md-elevated-button {\n margin-left: var(--spacing-small, 4px);\n letter-spacing: -1px;\n\n --md-elevated-button-container-height: 32px;\n --md-elevated-button-label-text-size: 16px;\n --md-elevated-button-container-color: #0595e5;\n\n --md-elevated-button-label-text-color: var(--md-sys-color-on-primary);\n --md-elevated-button-hover-label-text-color: var(--md-sys-color-on-primary);\n --md-elevated-button-pressed-label-text-color: var(--md-sys-color-on-primary);\n --md-elevated-button-focus-label-text-color: var(--md-sys-color-on-primary);\n --md-elevated-button-icon-color: var(--md-sys-color-on-primary);\n --md-elevated-button-hover-icon-color: var(--md-sys-color-on-primary);\n --md-elevated-button-pressed-icon-color: var(--md-sys-color-on-primary);\n --md-elevated-button-focus-icon-color: var(--md-sys-color-on-primary);\n --_leading-space: var(--spacing-tiny, 2px);\n\n --_with-leading-icon-leading-space: var(--spacing-medium, 8px);\n --_with-leading-icon-trailing-space: var(--spacing-medium, 8px);\n\n md-icon {\n margin-right: -2px;\n }\n }\n\n /* 드롭다운 메뉴 컨테이너 */\n .photo-menu-container {\n position: relative;\n display: inline-block;\n }\n\n md-menu {\n --md-menu-container-color: #fff;\n --md-menu-container-elevation: 2;\n border-radius: 8px;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n min-width: 150px;\n }\n\n md-menu-item {\n --md-menu-item-container-height: 20px;\n --md-menu-item-one-line-container-height: 30px;\n --md-menu-item-label-text-size: 15px;\n --md-menu-item-label-text-color: #333;\n --md-menu-item-hover-state-layer-color: #f5f5f5;\n --md-menu-item-top-space: 0px;\n --md-menu-item-bottom-space: 0px;\n\n cursor: pointer;\n padding: 2px 4px !important;\n min-height: 18px !important;\n }\n\n md-menu-item md-icon {\n margin-right: 2px;\n color: #666;\n --md-icon-size: 14px;\n }\n\n md-divider {\n margin: 1px 0;\n --md-divider-thickness: 1px;\n }\n `\n ]\n\n @property({ type: String }) buildingInspectionId: string = ''\n @property({ type: String }) buildingLevelId: string = ''\n @property({ type: String }) projectName: string = ''\n @property({ type: String }) buildingName: string = ''\n @property({ type: String }) buildingLevelFloor: string = ''\n\n @state() private isPhotoMenuOpen = false\n\n render() {\n const path = window.location.pathname\n\n return html`\n <div header>\n <h2>\n <md-icon slot=\"icon\" back @click=${() => history.back()}>arrow_back</md-icon>\n ${this.projectName || ''} ${this.buildingName || ''} ${this.buildingLevelFloor || ''}층\n </h2>\n <div button-container>\n <md-elevated-button\n ?disabled=${path.includes('building-inspection-list/')}\n href=${`building-inspection-list/${this.buildingLevelId}`}\n >\n <md-icon slot=\"icon\">list_alt</md-icon>검측 리스트\n </md-elevated-button>\n <md-elevated-button\n ?disabled=${path.includes('building-inspection-detail-drawing/')}\n href=${`building-inspection-detail-drawing/${this.buildingInspectionId}`}\n >\n <md-icon slot=\"icon\">fact_check</md-icon>검측도면\n </md-elevated-button>\n <md-elevated-button\n ?disabled=${path.includes('building-inspection-detail-checklist/')}\n href=${`building-inspection-detail-checklist/${this.buildingInspectionId}`}\n >\n <md-icon slot=\"icon\">task</md-icon>검측 체크리스트\n </md-elevated-button>\n <div class=\"photo-menu-container\">\n <md-elevated-button @click=${this.togglePhotoMenu} id=\"photo-menu-anchor\">\n <md-icon slot=\"icon\">arrow_drop_down</md-icon>사진촬영\n </md-elevated-button>\n <md-menu .open=${this.isPhotoMenuOpen} anchor=\"photo-menu-anchor\" @closed=${() => (this.isPhotoMenuOpen = false)}>\n <md-menu-item @click=${() => navigate(`building-inspection-detail-camera/${this.buildingInspectionId}`)}>\n <md-icon slot=\"start\">photo_camera</md-icon>\n 사진 촬영\n </md-menu-item>\n <md-divider></md-divider>\n <md-menu-item @click=${() => navigate(`building-inspection-detail-ai-measurement/${this.buildingInspectionId}`)}>\n <md-icon slot=\"start\">straighten</md-icon>\n AI 길이 측정\n </md-menu-item>\n <md-divider></md-divider>\n <md-menu-item @click=${() => navigate(`building-inspection-detail-ai-defect/${this.buildingInspectionId}`)}>\n <md-icon slot=\"start\">manga</md-icon>\n AI 하자 분석\n </md-menu-item>\n </md-menu>\n </div>\n <md-elevated-button\n ?disabled=${path.includes('building-inspection-detail-history/')}\n href=${`building-inspection-detail-history/${this.buildingInspectionId}`}\n disabled\n >\n <md-icon slot=\"icon\">pending_actions</md-icon>감리이력\n </md-elevated-button>\n </div>\n </div>\n `\n }\n\n private togglePhotoMenu() {\n this.isPhotoMenuOpen = !this.isPhotoMenuOpen\n }\n}\n"]}
@@ -566,7 +566,7 @@ ChecklistView.styles = [
566
566
  width: 270px;
567
567
  }
568
568
  &[criteria] {
569
- width: 90px;
569
+ width: 200px;
570
570
  }
571
571
  &[small] {
572
572
  width: 60px;