@sequent-org/ifc-viewer 1.2.4-ci.58.0 → 1.2.4-ci.60.0
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/package.json +1 -1
- package/src/styles-local.css +43 -0
- package/src/ui/LabelPlacementController.js +115 -8
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sequent-org/ifc-viewer",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "1.2.4-ci.
|
|
4
|
+
"version": "1.2.4-ci.60.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "IFC 3D model viewer component for web applications - fully self-contained with local IFCLoader",
|
|
7
7
|
"main": "src/index.js",
|
package/src/styles-local.css
CHANGED
|
@@ -111,7 +111,18 @@
|
|
|
111
111
|
.pointer-events-none { pointer-events: none; }
|
|
112
112
|
|
|
113
113
|
/* === IFC LABEL MARKERS (UI overlay) === */
|
|
114
|
+
.ifc-label-actions {
|
|
115
|
+
position: absolute;
|
|
116
|
+
left: 12px;
|
|
117
|
+
top: 12px;
|
|
118
|
+
z-index: 9999;
|
|
119
|
+
display: flex;
|
|
120
|
+
gap: 6px;
|
|
121
|
+
pointer-events: auto;
|
|
122
|
+
}
|
|
123
|
+
|
|
114
124
|
.ifc-label-add-btn,
|
|
125
|
+
.ifc-label-hide-btn,
|
|
115
126
|
.ifc-card-add-btn {
|
|
116
127
|
position: absolute;
|
|
117
128
|
left: 12px;
|
|
@@ -132,10 +143,35 @@
|
|
|
132
143
|
}
|
|
133
144
|
|
|
134
145
|
.ifc-label-add-btn:hover,
|
|
146
|
+
.ifc-label-hide-btn:hover,
|
|
135
147
|
.ifc-card-add-btn:hover {
|
|
136
148
|
background: rgba(255, 255, 255, 0.98);
|
|
137
149
|
}
|
|
138
150
|
|
|
151
|
+
.ifc-label-actions .ifc-label-add-btn,
|
|
152
|
+
.ifc-label-actions .ifc-label-hide-btn {
|
|
153
|
+
position: static;
|
|
154
|
+
left: auto;
|
|
155
|
+
top: auto;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.ifc-label-hide-btn--active {
|
|
159
|
+
background: rgba(37, 99, 235, 0.95);
|
|
160
|
+
border-color: rgba(37, 99, 235, 0.95);
|
|
161
|
+
color: #ffffff;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.ifc-label-hide-btn--active:hover {
|
|
165
|
+
background: rgba(29, 78, 216, 0.98);
|
|
166
|
+
border-color: rgba(29, 78, 216, 0.98);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.ifc-label-add-btn:disabled,
|
|
170
|
+
.ifc-label-add-btn--disabled {
|
|
171
|
+
opacity: 0.5;
|
|
172
|
+
cursor: not-allowed;
|
|
173
|
+
}
|
|
174
|
+
|
|
139
175
|
.ifc-label-ghost,
|
|
140
176
|
.ifc-label-marker,
|
|
141
177
|
.ifc-card-ghost,
|
|
@@ -232,6 +268,13 @@
|
|
|
232
268
|
background: rgba(37, 99, 235, 0.12);
|
|
233
269
|
}
|
|
234
270
|
|
|
271
|
+
.ifc-label-menu-item--disabled,
|
|
272
|
+
.ifc-label-menu-item--disabled:hover {
|
|
273
|
+
opacity: 0.5;
|
|
274
|
+
cursor: not-allowed;
|
|
275
|
+
background: transparent;
|
|
276
|
+
}
|
|
277
|
+
|
|
235
278
|
/* === NAVBAR COMPONENT === */
|
|
236
279
|
.navbar {
|
|
237
280
|
display: flex;
|
|
@@ -40,6 +40,7 @@ export class LabelPlacementController {
|
|
|
40
40
|
/** @type {LabelMarker[]} */
|
|
41
41
|
this._markers = [];
|
|
42
42
|
this._selectedId = null;
|
|
43
|
+
this._labelsHidden = false;
|
|
43
44
|
|
|
44
45
|
this._raycaster = new THREE.Raycaster();
|
|
45
46
|
this._ndc = new THREE.Vector2();
|
|
@@ -53,6 +54,8 @@ export class LabelPlacementController {
|
|
|
53
54
|
this._labelDrag = {
|
|
54
55
|
active: false,
|
|
55
56
|
moved: false,
|
|
57
|
+
// true => призрак живёт в document.body и позиционируется по viewport
|
|
58
|
+
ghostInBody: false,
|
|
56
59
|
pointerId: null,
|
|
57
60
|
id: null,
|
|
58
61
|
start: { x: 0, y: 0 },
|
|
@@ -129,6 +132,7 @@ export class LabelPlacementController {
|
|
|
129
132
|
try { document.removeEventListener("pointerup", this._onLabelDragPointerUp); } catch (_) {}
|
|
130
133
|
try { document.removeEventListener("pointercancel", this._onLabelDragPointerCancel); } catch (_) {}
|
|
131
134
|
try { this._ui?.btn?.removeEventListener("click", this._onBtnClick); } catch (_) {}
|
|
135
|
+
try { this._ui?.hideBtn?.removeEventListener("click", this._onHideBtnClick); } catch (_) {}
|
|
132
136
|
try { this._ui?.menu?.removeEventListener("pointerdown", this._onMenuPointerDown); } catch (_) {}
|
|
133
137
|
try { this._ui?.menu?.removeEventListener("click", this._onMenuClick); } catch (_) {}
|
|
134
138
|
try { this._ui?.canvasMenu?.removeEventListener("pointerdown", this._onCanvasMenuPointerDown); } catch (_) {}
|
|
@@ -145,13 +149,13 @@ export class LabelPlacementController {
|
|
|
145
149
|
|
|
146
150
|
try { this._ui?.ghost?.remove?.(); } catch (_) {}
|
|
147
151
|
try { this._ui?.dragGhost?.remove?.(); } catch (_) {}
|
|
148
|
-
try { this._ui?.
|
|
152
|
+
try { this._ui?.actions?.remove?.(); } catch (_) {}
|
|
149
153
|
try { this._ui?.menu?.remove?.(); } catch (_) {}
|
|
150
154
|
try { this._ui?.canvasMenu?.remove?.(); } catch (_) {}
|
|
151
155
|
}
|
|
152
156
|
|
|
153
157
|
startPlacement() {
|
|
154
|
-
if (this._placing) return;
|
|
158
|
+
if (this._placing || this._labelsHidden) return;
|
|
155
159
|
this._placing = true;
|
|
156
160
|
|
|
157
161
|
const controls = this.viewer?.controls;
|
|
@@ -591,11 +595,24 @@ export class LabelPlacementController {
|
|
|
591
595
|
}
|
|
592
596
|
|
|
593
597
|
#createUi() {
|
|
598
|
+
const actions = document.createElement("div");
|
|
599
|
+
actions.className = "ifc-label-actions";
|
|
600
|
+
|
|
594
601
|
const btn = document.createElement("button");
|
|
595
602
|
btn.type = "button";
|
|
596
603
|
btn.className = "ifc-label-add-btn";
|
|
597
604
|
btn.textContent = "+ Добавить метку";
|
|
598
605
|
|
|
606
|
+
const hideBtn = document.createElement("button");
|
|
607
|
+
hideBtn.type = "button";
|
|
608
|
+
hideBtn.className = "ifc-label-hide-btn";
|
|
609
|
+
hideBtn.textContent = "Скрыть метки";
|
|
610
|
+
hideBtn.setAttribute("aria-pressed", "false");
|
|
611
|
+
hideBtn.style.display = "none";
|
|
612
|
+
|
|
613
|
+
actions.appendChild(btn);
|
|
614
|
+
actions.appendChild(hideBtn);
|
|
615
|
+
|
|
599
616
|
const ghost = document.createElement("div");
|
|
600
617
|
ghost.className = "ifc-label-ghost";
|
|
601
618
|
ghost.setAttribute("aria-hidden", "true");
|
|
@@ -615,6 +632,8 @@ export class LabelPlacementController {
|
|
|
615
632
|
const dragGhost = document.createElement("div");
|
|
616
633
|
dragGhost.className = "ifc-label-ghost ifc-label-ghost--drag";
|
|
617
634
|
dragGhost.setAttribute("aria-hidden", "true");
|
|
635
|
+
// Фиксируем позицию, чтобы призрак не обрезался контейнером viewer
|
|
636
|
+
dragGhost.style.position = "fixed";
|
|
618
637
|
dragGhost.style.display = "none";
|
|
619
638
|
dragGhost.style.left = "0px";
|
|
620
639
|
dragGhost.style.top = "0px";
|
|
@@ -666,14 +685,21 @@ export class LabelPlacementController {
|
|
|
666
685
|
|
|
667
686
|
canvasMenu.appendChild(menuAdd);
|
|
668
687
|
|
|
669
|
-
return { btn, ghost, dot, num, dragGhost, dragNum, menu, canvasMenu };
|
|
688
|
+
return { actions, btn, hideBtn, ghost, dot, num, dragGhost, dragNum, menu, canvasMenu, menuAdd };
|
|
670
689
|
}
|
|
671
690
|
|
|
672
691
|
#attachUi() {
|
|
673
692
|
// Важно: container должен быть position:relative (в index.html уже так).
|
|
674
|
-
this.container.appendChild(this._ui.
|
|
693
|
+
this.container.appendChild(this._ui.actions);
|
|
675
694
|
this.container.appendChild(this._ui.ghost);
|
|
676
|
-
|
|
695
|
+
// Призрак перетаскивания добавляем в body, чтобы не обрезался overflow контейнера
|
|
696
|
+
if (document?.body) {
|
|
697
|
+
document.body.appendChild(this._ui.dragGhost);
|
|
698
|
+
this._labelDrag.ghostInBody = true;
|
|
699
|
+
} else {
|
|
700
|
+
this.container.appendChild(this._ui.dragGhost);
|
|
701
|
+
this._labelDrag.ghostInBody = false;
|
|
702
|
+
}
|
|
677
703
|
this.container.appendChild(this._ui.menu);
|
|
678
704
|
this.container.appendChild(this._ui.canvasMenu);
|
|
679
705
|
}
|
|
@@ -688,6 +714,15 @@ export class LabelPlacementController {
|
|
|
688
714
|
};
|
|
689
715
|
this._ui.btn.addEventListener("click", this._onBtnClick, { passive: false });
|
|
690
716
|
|
|
717
|
+
this._onHideBtnClick = (e) => {
|
|
718
|
+
try { e.preventDefault(); } catch (_) {}
|
|
719
|
+
try { e.stopPropagation(); } catch (_) {}
|
|
720
|
+
try { e.stopImmediatePropagation?.(); } catch (_) {}
|
|
721
|
+
if (!this._markers.length) return;
|
|
722
|
+
this.#setLabelsHidden(!this._labelsHidden);
|
|
723
|
+
};
|
|
724
|
+
this._ui.hideBtn.addEventListener("click", this._onHideBtnClick, { passive: false });
|
|
725
|
+
|
|
691
726
|
this._onMenuPointerDown = (e) => {
|
|
692
727
|
// Не даём клику меню попасть в canvas/OrbitControls
|
|
693
728
|
try { e.preventDefault(); } catch (_) {}
|
|
@@ -720,6 +755,7 @@ export class LabelPlacementController {
|
|
|
720
755
|
const target = e.target;
|
|
721
756
|
const action = target?.getAttribute?.("data-action");
|
|
722
757
|
if (action !== "add") return;
|
|
758
|
+
if (this._labelsHidden) return;
|
|
723
759
|
try { e.preventDefault(); } catch (_) {}
|
|
724
760
|
try { e.stopPropagation(); } catch (_) {}
|
|
725
761
|
|
|
@@ -942,9 +978,13 @@ export class LabelPlacementController {
|
|
|
942
978
|
|
|
943
979
|
#updateDragGhostFromClient(clientX, clientY) {
|
|
944
980
|
this._labelDrag.last = { x: clientX, y: clientY };
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
981
|
+
let x = clientX;
|
|
982
|
+
let y = clientY;
|
|
983
|
+
if (!this._labelDrag.ghostInBody) {
|
|
984
|
+
if (!this._containerOffsetValid) this.#refreshContainerOffset();
|
|
985
|
+
x = (clientX - this._containerOffset.left);
|
|
986
|
+
y = (clientY - this._containerOffset.top);
|
|
987
|
+
}
|
|
948
988
|
this._labelDrag.ghostPos.x = x;
|
|
949
989
|
this._labelDrag.ghostPos.y = y;
|
|
950
990
|
this.#applyDragGhostTransform();
|
|
@@ -1008,6 +1048,11 @@ export class LabelPlacementController {
|
|
|
1008
1048
|
const moved = !!this._labelDrag.moved;
|
|
1009
1049
|
const clientX = e?.clientX ?? this._labelDrag.last.x;
|
|
1010
1050
|
const clientY = e?.clientY ?? this._labelDrag.last.y;
|
|
1051
|
+
this.logger?.log?.("[LabelClickDbg]", {
|
|
1052
|
+
phase: "finish",
|
|
1053
|
+
moved: !!moved,
|
|
1054
|
+
id: marker?.id ?? this._labelDrag.id,
|
|
1055
|
+
});
|
|
1011
1056
|
|
|
1012
1057
|
this._labelDrag.active = false;
|
|
1013
1058
|
this._labelDrag.moved = false;
|
|
@@ -1261,8 +1306,17 @@ export class LabelPlacementController {
|
|
|
1261
1306
|
sceneState: data.sceneState || null,
|
|
1262
1307
|
});
|
|
1263
1308
|
this._markers.push(marker);
|
|
1309
|
+
this.#syncHideButton();
|
|
1264
1310
|
|
|
1265
1311
|
const onMarkerPointerDown = (e) => {
|
|
1312
|
+
this.logger?.log?.("[LabelClickDbg]", {
|
|
1313
|
+
phase: "down",
|
|
1314
|
+
button: e?.button,
|
|
1315
|
+
buttons: e?.buttons,
|
|
1316
|
+
clientX: e?.clientX,
|
|
1317
|
+
clientY: e?.clientY,
|
|
1318
|
+
pointerId: e?.pointerId,
|
|
1319
|
+
});
|
|
1266
1320
|
// Важно: не даём клику попасть в canvas/OrbitControls
|
|
1267
1321
|
try { e.preventDefault(); } catch (_) {}
|
|
1268
1322
|
try { e.stopPropagation(); } catch (_) {}
|
|
@@ -1281,6 +1335,14 @@ export class LabelPlacementController {
|
|
|
1281
1335
|
try { marker.el.addEventListener("pointerdown", onMarkerPointerDown); } catch (_) {}
|
|
1282
1336
|
}
|
|
1283
1337
|
const onMarkerPointerUp = (e) => {
|
|
1338
|
+
this.logger?.log?.("[LabelClickDbg]", {
|
|
1339
|
+
phase: "up",
|
|
1340
|
+
button: e?.button,
|
|
1341
|
+
buttons: e?.buttons,
|
|
1342
|
+
clientX: e?.clientX,
|
|
1343
|
+
clientY: e?.clientY,
|
|
1344
|
+
pointerId: e?.pointerId,
|
|
1345
|
+
});
|
|
1284
1346
|
};
|
|
1285
1347
|
try { marker.el.addEventListener("pointerup", onMarkerPointerUp, { capture: true, passive: true }); } catch (_) {
|
|
1286
1348
|
try { marker.el.addEventListener("pointerup", onMarkerPointerUp); } catch (_) {}
|
|
@@ -1336,6 +1398,7 @@ export class LabelPlacementController {
|
|
|
1336
1398
|
}
|
|
1337
1399
|
if (maxNumericId != null) this._nextId = Math.max(1, Math.floor(maxNumericId) + 1);
|
|
1338
1400
|
if (prevSelectedId != null) this.selectLabel(prevSelectedId);
|
|
1401
|
+
this.#syncHideButton();
|
|
1339
1402
|
}
|
|
1340
1403
|
|
|
1341
1404
|
getLabelMarkers() {
|
|
@@ -1396,6 +1459,11 @@ export class LabelPlacementController {
|
|
|
1396
1459
|
for (const m of this._markers) {
|
|
1397
1460
|
if (!m || !m.el) continue;
|
|
1398
1461
|
|
|
1462
|
+
if (this._labelsHidden) {
|
|
1463
|
+
m.el.style.display = "none";
|
|
1464
|
+
continue;
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1399
1467
|
if (!model) {
|
|
1400
1468
|
m.el.style.display = "none";
|
|
1401
1469
|
continue;
|
|
@@ -1426,5 +1494,44 @@ export class LabelPlacementController {
|
|
|
1426
1494
|
m.el.style.transform = `translate3d(${x}px, ${y}px, 0) translate(-50%, -50%)`;
|
|
1427
1495
|
}
|
|
1428
1496
|
}
|
|
1497
|
+
|
|
1498
|
+
#setLabelsHidden(hidden) {
|
|
1499
|
+
const next = !!hidden;
|
|
1500
|
+
if (this._labelsHidden === next) return;
|
|
1501
|
+
this._labelsHidden = next;
|
|
1502
|
+
if (this._labelsHidden) {
|
|
1503
|
+
try { this.cancelPlacement(); } catch (_) {}
|
|
1504
|
+
try { this.#closeContextMenu(); } catch (_) {}
|
|
1505
|
+
try { this.#closeCanvasMenu(); } catch (_) {}
|
|
1506
|
+
}
|
|
1507
|
+
this.#syncHideButton();
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
#syncHideButton() {
|
|
1511
|
+
const btn = this._ui?.hideBtn;
|
|
1512
|
+
if (!btn) return;
|
|
1513
|
+
const hasMarkers = this._markers.length > 0;
|
|
1514
|
+
btn.style.display = hasMarkers ? "block" : "none";
|
|
1515
|
+
if (!hasMarkers && this._labelsHidden) {
|
|
1516
|
+
this._labelsHidden = false;
|
|
1517
|
+
}
|
|
1518
|
+
btn.setAttribute("aria-pressed", this._labelsHidden ? "true" : "false");
|
|
1519
|
+
try { btn.classList.toggle("ifc-label-hide-btn--active", this._labelsHidden); } catch (_) {}
|
|
1520
|
+
this.#syncAddAvailability();
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
#syncAddAvailability() {
|
|
1524
|
+
const disabled = !!this._labelsHidden;
|
|
1525
|
+
const btn = this._ui?.btn;
|
|
1526
|
+
if (btn) {
|
|
1527
|
+
btn.disabled = disabled;
|
|
1528
|
+
try { btn.classList.toggle("ifc-label-add-btn--disabled", disabled); } catch (_) {}
|
|
1529
|
+
}
|
|
1530
|
+
const menuAdd = this._ui?.menuAdd;
|
|
1531
|
+
if (menuAdd) {
|
|
1532
|
+
menuAdd.disabled = disabled;
|
|
1533
|
+
try { menuAdd.classList.toggle("ifc-label-menu-item--disabled", disabled); } catch (_) {}
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1429
1536
|
}
|
|
1430
1537
|
|