@smarterplan/ngx-smarterplan-core 1.4.6 → 1.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/esm2022/lib/helpers.service.mjs +27 -20
  2. package/esm2022/lib/mattertagData.mjs +84 -84
  3. package/esm2022/lib/pipes/time-date-to-local-string.pipe.mjs +3 -8
  4. package/esm2022/lib/services/locale.service.mjs +3 -5
  5. package/esm2022/lib/services/matterport-import.service.mjs +3 -3
  6. package/esm2022/lib/services/matterport-measurement.service.mjs +78 -0
  7. package/esm2022/lib/services/matterport-navigation.service.mjs +157 -0
  8. package/esm2022/lib/services/matterport-object3d.service.mjs +234 -0
  9. package/esm2022/lib/services/matterport-pointer.service.mjs +130 -0
  10. package/esm2022/lib/services/matterport-tag.service.mjs +458 -0
  11. package/esm2022/lib/services/matterport.service.mjs +427 -988
  12. package/esm2022/lib/services/models/equipment.service.mjs +3 -4
  13. package/esm2022/lib/services/models/feature.service.mjs +3 -3
  14. package/esm2022/lib/services/models/measurement.service.mjs +3 -3
  15. package/esm2022/lib/services/models/ticket.service.mjs +3 -4
  16. package/esm2022/lib/services/navigator.service.mjs +10 -2
  17. package/esm2022/lib/services/tag.service.mjs +45 -19
  18. package/esm2022/lib/services/viewer.service.mjs +51 -16
  19. package/esm2022/lib/types.service.mjs +1 -1
  20. package/fesm2022/smarterplan-ngx-smarterplan-core.mjs +2028 -1490
  21. package/fesm2022/smarterplan-ngx-smarterplan-core.mjs.map +1 -1
  22. package/lib/helpers.service.d.ts +6 -0
  23. package/lib/mattertagData.d.ts +52 -10
  24. package/lib/services/matterport-measurement.service.d.ts +35 -0
  25. package/lib/services/matterport-navigation.service.d.ts +38 -0
  26. package/lib/services/matterport-object3d.service.d.ts +31 -0
  27. package/lib/services/matterport-pointer.service.d.ts +31 -0
  28. package/lib/services/matterport-tag.service.d.ts +104 -0
  29. package/lib/services/matterport.service.d.ts +91 -92
  30. package/lib/services/models/equipment.service.d.ts +2 -2
  31. package/lib/services/models/feature.service.d.ts +2 -2
  32. package/lib/services/models/measurement.service.d.ts +2 -2
  33. package/lib/services/models/ticket.service.d.ts +2 -2
  34. package/lib/services/tag.service.d.ts +4 -0
  35. package/lib/services/viewer.service.d.ts +1 -1
  36. package/lib/types.service.d.ts +2 -0
  37. package/package.json +1 -1
  38. package/esm2022/lib/matterport-extensions/hsl-loader/HlsLoader.mjs +0 -66
  39. package/esm2022/lib/matterport-extensions/video-renderer/VideoRenderer.mjs +0 -63
  40. package/lib/matterport-extensions/hsl-loader/HlsLoader.d.ts +0 -26
  41. package/lib/matterport-extensions/video-renderer/VideoRenderer.d.ts +0 -26
@@ -2,7 +2,7 @@ import * as i0 from '@angular/core';
2
2
  import { Injectable, Input, Component, Inject, EventEmitter, isDevMode, Output, Pipe, ViewChild, NgModule } from '@angular/core';
3
3
  import * as i1 from '@ngx-translate/core';
4
4
  import { TranslateModule } from '@ngx-translate/core';
5
- import { Subject, takeUntil, Observable } from 'rxjs';
5
+ import { Subject, BehaviorSubject, takeUntil, Observable } from 'rxjs';
6
6
  import * as i1$2 from '@ng-bootstrap/ng-bootstrap';
7
7
  import { NgbDate, NgbModule } from '@ng-bootstrap/ng-bootstrap';
8
8
  import * as i1$1 from '@angular/router';
@@ -1964,62 +1964,43 @@ class MattertagData {
1964
1964
  mediaSrc;
1965
1965
  sweepID;
1966
1966
  customIconIndex = 0;
1967
- rotation;
1967
+ rotation = null;
1968
1968
  poi;
1969
+ matterportSid = null;
1969
1970
  constructor(type) {
1970
1971
  this.setType(type);
1971
1972
  }
1972
- /*-----------------------------------------------------------------------------
1973
- Setters
1974
- ------------------------------------------------------------------------------*/
1975
- setType(type) {
1976
- this.type = type;
1977
- }
1973
+ /*---------------------------------------------------------------------------
1974
+ Setters
1975
+ ---------------------------------------------------------------------------*/
1976
+ setType(type) { this.type = type; }
1978
1977
  setObject(object, tagType) {
1979
1978
  this.object = object;
1980
1979
  this.setElementID(object.id);
1981
1980
  this.setType(tagType);
1982
1981
  }
1983
- setElementID(ticketID) {
1984
- this.elementID = ticketID;
1985
- }
1982
+ setElementID(id) { this.elementID = id; }
1986
1983
  setPosition(position) {
1987
1984
  this.position = position;
1988
1985
  }
1989
1986
  setNormal(normal) {
1990
1987
  this.normal = normal;
1991
1988
  }
1992
- setMediaSource(source) {
1993
- this.mediaSrc = source;
1994
- }
1995
- setSweepID(sweepID) {
1996
- this.sweepID = sweepID;
1997
- }
1998
- setRotation(rotation) {
1999
- this.rotation = rotation;
2000
- }
2001
- setPoi(poi) {
2002
- this.poi = poi;
2003
- }
2004
- /*-----------------------------------------------------------------------------
2005
- Getters
2006
- ------------------------------------------------------------------------------*/
2007
- // MattertagData
1989
+ setMediaSource(source) { this.mediaSrc = source; }
1990
+ setSweepID(sweepID) { this.sweepID = sweepID; }
1991
+ setRotation(rotation) { this.rotation = rotation; }
1992
+ setPoi(poi) { this.poi = poi; }
1993
+ setMatterportSid(sid) { this.matterportSid = sid; }
1994
+ /*---------------------------------------------------------------------------
1995
+ Getters
1996
+ ---------------------------------------------------------------------------*/
2008
1997
  getData() {
2009
1998
  let label = "New Tag";
2010
1999
  switch (this.type) {
2011
2000
  case PoiType.TICKET:
2012
- label = this.object ? "" : "New Tag"; // if the object exists we inject title in html
2013
- break;
2014
2001
  case PoiType.EQUIPMENT:
2015
- label = this.object ? "" : "New Tag";
2016
- break;
2017
2002
  case PoiType.MEASURE:
2018
- label = this.object ? "" : "New Tag";
2019
- break;
2020
2003
  case PoiType.DATA:
2021
- label = this.object ? "" : "New Tag";
2022
- break;
2023
2004
  case PoiType.DESK:
2024
2005
  label = this.object ? "" : "New Tag";
2025
2006
  break;
@@ -2031,47 +2012,73 @@ class MattertagData {
2031
2012
  break;
2032
2013
  default:
2033
2014
  label = "New Tag";
2034
- break;
2035
2015
  }
2036
- const data = {
2016
+ return {
2037
2017
  label,
2038
2018
  description: "",
2039
- anchorPosition: {
2040
- x: this.position.x * 1,
2041
- y: this.position.y * 1,
2042
- z: this.position.z * 1,
2043
- },
2044
- stemVector: {
2045
- x: this.normal.x * 0.3,
2046
- y: this.normal.y * 0.3,
2047
- z: this.normal.z * 0.3,
2048
- },
2049
- color: {
2050
- // blue disc
2051
- r: 0,
2052
- g: 0,
2053
- b: 1,
2054
- },
2055
- media: {
2056
- type: "mattertag.media.none",
2057
- src: ""
2058
- },
2019
+ anchorPosition: { ...this.position },
2020
+ stemVector: this.getStemVector(),
2021
+ color: { r: 1, g: 1, b: 1 },
2022
+ };
2023
+ }
2024
+ /**
2025
+ * Get the descriptor for sdk.Tag.add(...descriptor)
2026
+ * Format : [{ label, description, anchorPosition, stemVector, color, id? }]
2027
+ * If matterportSid is defined (already known tag) we send it so the SDK
2028
+ * reuse it instead of trying to create a new one and generate an error : "already in use".
2029
+ */
2030
+ getMattertagDescriptor() {
2031
+ const base = this.getData();
2032
+ const descriptor = { ...base };
2033
+ if (this.matterportSid) {
2034
+ descriptor['id'] = this.matterportSid;
2035
+ }
2036
+ return [descriptor];
2037
+ }
2038
+ /**
2039
+ * Get data for sdk.Tag.editPosition() or sdk.Tag.editBillboard().
2040
+ */
2041
+ getTagSdkData() {
2042
+ return {
2043
+ anchorPosition: { ...this.position },
2044
+ stemVector: this.getStemVector(),
2059
2045
  };
2060
- return data;
2061
2046
  }
2062
- getType() {
2063
- return this.type;
2047
+ /** Load the matterportSid from the poi metadata */
2048
+ loadSidFromPoi(poi) {
2049
+ if (!poi?.metadata)
2050
+ return;
2051
+ try {
2052
+ const meta = JSON.parse(poi.metadata);
2053
+ if (meta?.sid && typeof meta.sid === 'string') {
2054
+ this.matterportSid = meta.sid;
2055
+ }
2056
+ }
2057
+ catch { }
2064
2058
  }
2065
- getNormal() {
2066
- return this.normal;
2059
+ /** Get the stem vector for the tag */
2060
+ getStemVector() {
2061
+ return {
2062
+ x: this.normal.x * 0.3,
2063
+ y: this.normal.y * 0.3,
2064
+ z: this.normal.z * 0.3,
2065
+ };
2067
2066
  }
2067
+ getType() { return this.type; }
2068
+ getNormal() { return this.normal; }
2069
+ getPosition() { return this.position; }
2070
+ getSweepID() { return this.sweepID; }
2071
+ getObject() { return this.object; }
2072
+ getRotation() { return this.rotation; }
2073
+ getPoi() { return this.poi; }
2074
+ getMatterportSid() { return this.matterportSid; }
2075
+ /** Get the action mode for the tag */
2068
2076
  getCallbackActionMode() {
2069
2077
  switch (this.type) {
2070
2078
  case PoiType.TICKET:
2071
2079
  return this.object ? MattertagActionMode.POSITION_TICKET : MattertagActionMode.ADD_TICKET;
2072
2080
  case PoiType.OBJECT3D:
2073
2081
  return MattertagActionMode.POSITION_OBJECT3D;
2074
- // return this.object ? MattertagActionMode.POSITION_OBJECT3D : MattertagActionMode.ADD_OBJECT3D;
2075
2082
  case PoiType.EQUIPMENT:
2076
2083
  return this.object ? MattertagActionMode.POSITION_EQUIPMENT : MattertagActionMode.ADD_EQUIPMENT;
2077
2084
  case PoiType.DATA:
@@ -2084,448 +2091,1471 @@ class MattertagData {
2084
2091
  return MattertagActionMode.ADD_TICKET;
2085
2092
  }
2086
2093
  }
2094
+ /** Get the icon for the tag */
2087
2095
  getIcon() {
2088
2096
  switch (this.type) {
2089
- case PoiType.TICKET:
2090
- return "icon_ticket.png";
2091
- case PoiType.EQUIPMENT:
2092
- return "icon_equipment.png";
2093
- case PoiType.OBJECT3D:
2094
- return "icon_object3d.png";
2097
+ case PoiType.TICKET: return "icon_ticket.png";
2098
+ case PoiType.EQUIPMENT: return "icon_equipment.png";
2099
+ case PoiType.OBJECT3D: return "icon_object3d.png";
2100
+ case PoiType.MEASURE: return "icon_measure.png";
2101
+ case PoiType.DATA: return "icon_data.png";
2102
+ case PoiType.DESK: return "icon_data.png";
2103
+ case PoiType.ROOM: return "";
2104
+ default: return "";
2095
2105
  }
2096
- return "";
2097
2106
  }
2107
+ /** Get the coordinate string for the tag */
2098
2108
  getCoordinateString() {
2099
2109
  return JSON.stringify({
2100
2110
  x: this.position.x,
2101
2111
  y: this.position.y,
2102
2112
  z: this.position.z,
2103
- xRotation: this.rotation ? this.rotation.x : null,
2104
- yRotation: this.rotation ? this.rotation.y : null,
2113
+ xRotation: this.rotation?.x ?? null,
2114
+ yRotation: this.rotation?.y ?? null,
2105
2115
  });
2106
2116
  }
2117
+ /** Get the metadata string for the tag */
2107
2118
  getMetadataString() {
2108
2119
  return JSON.stringify({
2109
2120
  icon: this.getIcon(),
2110
2121
  normal: this.getNormal(),
2122
+ sid: this.matterportSid,
2111
2123
  });
2112
2124
  }
2113
- getSweepID() {
2114
- return this.sweepID;
2115
- }
2116
- getObject() {
2117
- return this.object;
2118
- }
2119
- getRotation() {
2120
- return this.rotation;
2121
- }
2122
- getPoi() {
2123
- return this.poi;
2124
- }
2125
2125
  }
2126
2126
 
2127
- const HoverEvent$1 = 'hover';
2128
- const UnhoverEvent$1 = 'unhover';
2129
- const RepaintEvent = 'repaint';
2130
- class NestThermostat extends SceneComponent {
2131
- cv = null;
2132
- planeRenderer = null;
2133
- rootScene = null;
2134
- mixer = null;
2135
- onEnterClip = null;
2136
- mesh = null;
2137
- currentTime = 0;
2138
- nextUpdate = 0;
2139
- temperature = 0;
2140
- tempChangeRange = 5;
2141
- component;
2142
- inputs = {
2143
- loadingState: 'Idle',
2144
- texture: null,
2145
- updateInterval: 1000,
2146
- };
2147
- outputs = {
2148
- painter: null,
2149
- visible: false,
2127
+ class BaseVisibilityService {
2128
+ detailShowing = new Subject();
2129
+ isChangePositionVisible = new Subject();
2130
+ constructor() { }
2131
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: BaseVisibilityService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2132
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: BaseVisibilityService, providedIn: 'root' });
2133
+ }
2134
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: BaseVisibilityService, decorators: [{
2135
+ type: Injectable,
2136
+ args: [{
2137
+ providedIn: 'root',
2138
+ }]
2139
+ }], ctorParameters: () => [] });
2140
+
2141
+ class MatterportMeasurementService {
2142
+ router;
2143
+ lastMeasure = [];
2144
+ distancesLastMeasure = [];
2145
+ lastScreenshotUri;
2146
+ resolution = {
2147
+ width: 500,
2148
+ height: 600,
2150
2149
  };
2151
- events = {
2152
- [HoverEvent$1]: true,
2153
- [UnhoverEvent$1]: true,
2150
+ visibility = {
2151
+ mattertags: false,
2152
+ sweeps: true,
2154
2153
  };
2155
- onInit(modelNode, plane, inputTexture) {
2156
- if (!this.context) {
2157
- this.context = {
2158
- root: modelNode
2159
- };
2160
- }
2161
- this.inputs.texture = inputTexture;
2162
- const root = this.context.root;
2163
- const THREE = this.context.three;
2164
- this.planeRenderer = plane;
2165
- this.planeRenderer.outputs.objectRoot.translateZ(0.05);
2166
- this.planeRenderer.outputs.objectRoot.translateY(0.4);
2167
- this.planeRenderer.outputs.objectRoot.scale.set(0.5, 0.5, 0.5);
2168
- // planeRenderer.inputs.localPosition.z = 0.05;
2169
- // planeRenderer.inputs.localPosition.y = 0.4;
2170
- // planeRenderer.inputs.localScale = {x: 0.5, y: 0.5, z: 0.5};
2171
- this.outputs.painter = this;
2172
- this.mixer = new AnimationMixer(this.planeRenderer.outputs.objectRoot);
2173
- const tm = 0.2;
2174
- const positionTrack = new VectorKeyframeTrack('.scale', [0, tm], [
2175
- 0, 0, 0,
2176
- 0.5, 0.5, 0.5
2177
- ], InterpolateSmooth);
2178
- this.onEnterClip = new AnimationClip(null, tm, [positionTrack]);
2154
+ constructor(router) {
2155
+ this.router = router;
2179
2156
  }
2180
- onInputsUpdated() {
2181
- if (this.inputs.loadingState === 'Loaded') {
2182
- const lines = [];
2183
- //@ts-ignore
2184
- this.context.root.obj3D.traverse((obj) => {
2185
- // we dont want line segments, track them and remove them.
2186
- if (obj.type === 'LineSegments') {
2187
- lines.push(obj);
2188
- }
2189
- else if (obj.type === 'Mesh') {
2190
- this.mesh = obj;
2191
- const materials = this.mesh.material;
2192
- materials.forEach((m, idx) => {
2193
- if (m && m.map.source.data.outerHTML.indexOf('_5b76dbe388862300126c1e14') !== -1) {
2194
- const newMaterial = new MeshBasicMaterial({ map: this.inputs.texture });
2195
- this.mesh.material[idx] = newMaterial;
2196
- }
2197
- });
2198
- }
2199
- });
2200
- // remove the line segments.
2201
- lines.forEach((line) => {
2202
- line.parent.remove(line);
2157
+ /**
2158
+ * Callback after measurement is performed
2159
+ */
2160
+ getDistanceForLastMeasurement(sdk, currentSpaceID) {
2161
+ if (this.lastMeasure.length > 0) {
2162
+ const numberPoints = this.lastMeasure.length;
2163
+ this.distancesLastMeasure = [];
2164
+ for (let index = 1; index < numberPoints; index += 1) {
2165
+ const distance = getDistanceBetweenTwoPoints(this.lastMeasure[index - 1], this.lastMeasure[index]);
2166
+ this.distancesLastMeasure.push(distance);
2167
+ }
2168
+ this.takeScreenShot(sdk).then(() => {
2169
+ this.router.navigate([`visit/${currentSpaceID}/add_measurement`]);
2203
2170
  });
2204
2171
  }
2205
2172
  }
2206
- onEvent(eventType, eventData) {
2207
- if (eventType === HoverEvent$1) {
2208
- const data = eventData;
2209
- if (data.hover) {
2210
- this.outputs.visible = true;
2211
- const onEnterAction = this.mixer.clipAction(this.onEnterClip);
2212
- onEnterAction.stop();
2213
- onEnterAction.loop = LoopOnce;
2214
- onEnterAction.clampWhenFinished = true;
2215
- onEnterAction.play();
2216
- }
2217
- else {
2218
- this.outputs.visible = false;
2219
- }
2220
- }
2173
+ getLastDistances() {
2174
+ return this.distancesLastMeasure;
2221
2175
  }
2222
- paint(context2d, size) {
2223
- const x = 490;
2224
- const y = 490;
2225
- context2d.fillStyle = 'black';
2226
- context2d.beginPath();
2227
- context2d.arc(x, y, 400, 0, Math.PI * 2);
2228
- context2d.fill();
2229
- context2d.fillStyle = '#CF5300';
2230
- context2d.beginPath();
2231
- context2d.arc(x, y, 300, 0, Math.PI * 2);
2232
- context2d.fill();
2233
- context2d.beginPath();
2234
- context2d.strokeStyle = 'orange';
2235
- context2d.arc(x, y, 240, 0.75 * Math.PI, 0.25 * Math.PI);
2236
- context2d.lineCap = 'butt';
2237
- context2d.lineWidth = 80;
2238
- context2d.stroke();
2239
- context2d.fillStyle = 'white';
2240
- context2d.font = '220px Arial';
2241
- context2d.fillText(`${this.temperature}`, x - 115, y + 75);
2242
- this.outputs.visible = true;
2176
+ /**
2177
+ * Takes screenshot and saves base64 in lastScreenshotUri
2178
+ * @param sdk Matterport SDK
2179
+ * @returns Promise
2180
+ */
2181
+ takeScreenShot(sdk) {
2182
+ return sdk.Renderer.takeScreenShot(this.resolution, this.visibility).then((screenShotUri) => {
2183
+ this.lastScreenshotUri = screenShotUri;
2184
+ return Promise.resolve();
2185
+ });
2243
2186
  }
2244
- onTick(delta) {
2245
- this.currentTime += delta;
2246
- if (this.mixer) {
2247
- this.mixer.update(delta / 1000);
2248
- }
2249
- if (this.currentTime > this.nextUpdate) {
2250
- this.nextUpdate += this.inputs.updateInterval;
2251
- this.temperature += (Math.random() * this.tempChangeRange);
2252
- this.temperature = Math.trunc(this.temperature);
2253
- if (this.temperature > 99) {
2254
- this.temperature = 99;
2255
- this.tempChangeRange = -this.tempChangeRange;
2256
- }
2257
- if (this.temperature < 10) {
2258
- this.temperature = 10;
2259
- this.tempChangeRange = -this.tempChangeRange;
2260
- }
2261
- this.component.notify(RepaintEvent);
2262
- this.cv.repaint();
2263
- }
2187
+ getScreenShotUri() {
2188
+ return this.lastScreenshotUri;
2264
2189
  }
2265
- setComponent(component, plane, cv) {
2266
- component.onTick = this.onTick.bind(this);
2267
- // plane.onTick = this.onTick.bind(this);
2268
- this.cv = cv;
2269
- this.component = component;
2270
- this.context = component.context;
2190
+ getLastMeasurement(poseCamera) {
2191
+ return {
2192
+ measure: this.lastMeasure,
2193
+ sweep: poseCamera.sweep,
2194
+ };
2271
2195
  }
2272
- setRootScene(rootScene) {
2273
- this.rootScene = rootScene;
2196
+ setLastMeasure(points) {
2197
+ this.lastMeasure = points;
2198
+ }
2199
+ getResolution() {
2200
+ return this.resolution;
2274
2201
  }
2202
+ getVisibility() {
2203
+ return this.visibility;
2204
+ }
2205
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MatterportMeasurementService, deps: [{ token: i1$1.Router }], target: i0.ɵɵFactoryTarget.Injectable });
2206
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MatterportMeasurementService, providedIn: 'root' });
2275
2207
  }
2276
- const nestThermostatType = 'mp.nestThermostat';
2277
- const makeNestThermostat = function () {
2278
- return new NestThermostat();
2279
- };
2208
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MatterportMeasurementService, decorators: [{
2209
+ type: Injectable,
2210
+ args: [{
2211
+ providedIn: 'root',
2212
+ }]
2213
+ }], ctorParameters: () => [{ type: i1$1.Router }] });
2280
2214
 
2281
- class PlaneRenderer extends SceneComponent {
2282
- rootScene = null;
2283
- mesh;
2284
- pivotNode;
2285
- inputs = {
2286
- texture: null,
2287
- aspect: 1,
2288
- transparent: true,
2289
- visible: true,
2290
- opacity: 1,
2291
- polygonOffset: false,
2292
- polygonOffsetFactor: 0,
2293
- polygonOffsetUnits: 0,
2294
- localScale: { x: 1, y: 1, z: 1 },
2295
- localPosition: { x: 0, y: 0, z: 0 },
2215
+ class MatterportNavigationService {
2216
+ sweepCollection = null;
2217
+ sweeps = null;
2218
+ floors = null;
2219
+ forbiddenSweeps = [];
2220
+ currentSweep = new Subject();
2221
+ onCameraModeChanged = new Subject();
2222
+ /** Emits whenever the set of current rooms changes (sdk.Room.current). */
2223
+ onRoomChanged = new Subject();
2224
+ inTransitionMode = false;
2225
+ inTransitionSweep = false;
2226
+ currentCameraMode = CameraMode.OUTSIDE;
2227
+ currentRooms = [];
2228
+ /** Sequence number of the floor currently visible in sdk.Floor.current (null = single-floor or unknown). */
2229
+ currentFloorSequence = null;
2230
+ constructor() { }
2231
+ async action_toolbox_floorplan(sdk) {
2232
+ if (this.inTransitionMode || this.inTransitionSweep) {
2233
+ console.log('viewer is in transition, cannot go floorplan');
2234
+ return;
2235
+ }
2236
+ try {
2237
+ await sdk.Mode.moveTo('mode.floorplan');
2238
+ }
2239
+ catch (e) {
2240
+ console.log('cannot go to floorplan', e);
2241
+ }
2242
+ }
2243
+ action_toolbox_inside_view(sdk) {
2244
+ sdk.Mode.moveTo('mode.inside');
2245
+ }
2246
+ actionShowAllFloors(sdk) {
2247
+ try {
2248
+ sdk.Floor.showAll();
2249
+ }
2250
+ catch (e) {
2251
+ console.log('cannot show all floors', e);
2252
+ }
2253
+ }
2254
+ async action_toolbox_dollhouse(sdk) {
2255
+ setTimeout(async () => {
2256
+ if (this.inTransitionMode || this.inTransitionSweep) {
2257
+ console.log('viewer is in transition, cannot go dollhouse');
2258
+ return;
2259
+ }
2260
+ try {
2261
+ await sdk.Mode.moveTo('mode.dollhouse');
2262
+ }
2263
+ catch (e) {
2264
+ console.log('cannot go to dollhouse', e);
2265
+ }
2266
+ }, 1200);
2267
+ }
2268
+ async action_go_to_floor(sdk, floorName, matterportFloorSequence = null) {
2269
+ if (!this.floors) {
2270
+ console.log('Floor are not loaded yet');
2271
+ return;
2272
+ }
2273
+ let floorMatterport = this.floors.find((floor) => floor.sequence === matterportFloorSequence);
2274
+ if (!floorMatterport) {
2275
+ floorMatterport = this.floors.find((floor) => floorName.includes(floor.name) && floor.name != '');
2276
+ }
2277
+ if (!floorMatterport) {
2278
+ floorMatterport = this.floors.find((floor) => floorName.includes(floor.id));
2279
+ }
2280
+ if (floorMatterport) {
2281
+ let retry = true;
2282
+ while (retry) {
2283
+ try {
2284
+ const floorIndex = await sdk.Floor.moveTo(floorMatterport.sequence);
2285
+ console.debug("moved to floorIndex", floorIndex);
2286
+ retry = false;
2287
+ }
2288
+ catch (error) {
2289
+ console.log('Cannot move to Floor', error);
2290
+ await wait(100);
2291
+ }
2292
+ }
2293
+ }
2294
+ else {
2295
+ console.warn('No matterport floor found to move to');
2296
+ }
2297
+ }
2298
+ async action_go_to_sweep(sdk, sweep, rotation) {
2299
+ if (this.forbiddenSweeps.includes(sweep)) {
2300
+ console.log('user is not allowed to go to this sweep');
2301
+ return;
2302
+ }
2303
+ setTimeout(async () => {
2304
+ try {
2305
+ this.inTransitionSweep = true;
2306
+ await sdk.Sweep.moveTo(sweep, {
2307
+ transition: sdk.Camera.TransitionType.INSTANT,
2308
+ transitionTime: 1500
2309
+ });
2310
+ }
2311
+ catch (error) {
2312
+ this.inTransitionSweep = false;
2313
+ console.log('Cannot move to sweep', error);
2314
+ }
2315
+ if (rotation) {
2316
+ await sdk.Camera.setRotation(rotation, { speed: 100 });
2317
+ }
2318
+ }, 1000);
2319
+ }
2320
+ async removeForbiddenSweeps(sdk, forbiddenSweeps) {
2321
+ this.forbiddenSweeps = [...forbiddenSweeps];
2322
+ let removed = 0;
2323
+ await Promise.all(forbiddenSweeps.map(async (sweep) => {
2324
+ try {
2325
+ await sdk.Sweep.disable(sweep);
2326
+ removed += 1;
2327
+ }
2328
+ catch (error) {
2329
+ console.log(error);
2330
+ }
2331
+ }));
2332
+ console.log('removed sweeps:', removed);
2333
+ }
2334
+ getCurrentSweep(poseCamera) {
2335
+ return poseCamera?.sweep || null;
2336
+ }
2337
+ setCameraMode(mode) {
2338
+ this.inTransitionSweep = false;
2339
+ switch (mode) {
2340
+ case 'mode.dollhouse':
2341
+ this.currentCameraMode = CameraMode.DOLLHOUSE;
2342
+ break;
2343
+ case 'mode.floorplan':
2344
+ this.currentCameraMode = CameraMode.FLOORPLAN;
2345
+ break;
2346
+ case 'mode.inside':
2347
+ this.currentCameraMode = CameraMode.INSIDE;
2348
+ break;
2349
+ case 'mode.transitioning':
2350
+ this.currentCameraMode = CameraMode.TRANSITIONING;
2351
+ break;
2352
+ default:
2353
+ this.currentCameraMode = CameraMode.OUTSIDE;
2354
+ }
2355
+ this.onCameraModeChanged.next(this.currentCameraMode);
2356
+ }
2357
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MatterportNavigationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2358
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MatterportNavigationService, providedIn: 'root' });
2359
+ }
2360
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MatterportNavigationService, decorators: [{
2361
+ type: Injectable,
2362
+ args: [{
2363
+ providedIn: 'root',
2364
+ }]
2365
+ }], ctorParameters: () => [] });
2366
+
2367
+ class MatterportPointerService {
2368
+ pointerButton = null;
2369
+ getCursorPositionButton = null;
2370
+ textDisplayCursorPositionPanel = null;
2371
+ cursorPositionButtonDisplayed = false;
2372
+ intervalCursorPointerPosition;
2373
+ timerPointer;
2374
+ oldPoseMatterportPosition;
2375
+ constructor() { }
2376
+ init(container, pointerButton, getCursorPositionButton, textDisplayCursorPositionPanel, callbacks) {
2377
+ this.pointerButton = pointerButton;
2378
+ this.getCursorPositionButton = getCursorPositionButton;
2379
+ this.textDisplayCursorPositionPanel = textDisplayCursorPositionPanel;
2380
+ if (this.pointerButton) {
2381
+ this.pointerButton.addEventListener('click', callbacks.onLeftClick);
2382
+ this.pointerButton.addEventListener('contextmenu', callbacks.onRightClick);
2383
+ }
2384
+ if (this.getCursorPositionButton) {
2385
+ this.getCursorPositionButton.addEventListener('click', callbacks.onMiddleClick);
2386
+ }
2387
+ }
2388
+ startPointerTrick(sdk, container, getPose, getPoseCamera, getInteractionMode, getMattertagToFollow) {
2389
+ this.timerPointer = setInterval(() => {
2390
+ this.updatePointerTrick(sdk, container, getPose(), getPoseCamera(), getInteractionMode(), getMattertagToFollow());
2391
+ }, 50);
2392
+ }
2393
+ stopPointerTrick() {
2394
+ clearInterval(this.timerPointer);
2395
+ }
2396
+ startAdminCursorTracker(sdk, container, getPose, getPoseCamera) {
2397
+ if (!!this.getCursorPositionButton &&
2398
+ (document.location.href.indexOf('dev') !== -1 || document.location.href.indexOf('localhost') !== -1)) {
2399
+ this.intervalCursorPointerPosition = setInterval(() => {
2400
+ const poseMatterport = getPose();
2401
+ if (!poseMatterport)
2402
+ return;
2403
+ const nextShow = poseMatterport.time + 1000;
2404
+ if (new Date().getTime() > nextShow) {
2405
+ if (this.cursorPositionButtonDisplayed)
2406
+ return;
2407
+ const size = {
2408
+ w: container.clientWidth,
2409
+ h: container.clientHeight,
2410
+ };
2411
+ const coord = sdk.Conversion.worldToScreen(poseMatterport.position, getPoseCamera(), size);
2412
+ this.getCursorPositionButton.style.left = `${coord.x - 25}px`;
2413
+ this.getCursorPositionButton.style.top = `${coord.y - 22}px`;
2414
+ this.getCursorPositionButton.style.display = 'block';
2415
+ this.cursorPositionButtonDisplayed = true;
2416
+ }
2417
+ }, 500);
2418
+ }
2419
+ }
2420
+ stopAdminCursorTracker() {
2421
+ clearInterval(this.intervalCursorPointerPosition);
2422
+ }
2423
+ updatePointerTrick(sdk, container, poseMatterport, poseCamera, interactionMode, mattertagToFollow) {
2424
+ if (!this.pointerButton)
2425
+ return;
2426
+ if (interactionMode !== 0 && // ViewerInteractions.DEFAULT
2427
+ mattertagToFollow &&
2428
+ poseMatterport &&
2429
+ this.getDistPosition(sdk, container, poseCamera, poseMatterport.position, this.oldPoseMatterportPosition) > 25) {
2430
+ this.pointerButton.style.display = 'none';
2431
+ const size = {
2432
+ w: container.clientWidth,
2433
+ h: container.clientHeight,
2434
+ };
2435
+ const coords = sdk.Conversion.worldToScreen(poseMatterport.position, poseCamera, size);
2436
+ this.pointerButton.style.left = `${coords.x * Math.sign(coords.x) - 25}px`;
2437
+ this.pointerButton.style.top = `${coords.y * Math.sign(coords.x) - 25}px`;
2438
+ this.oldPoseMatterportPosition = {
2439
+ ...poseMatterport.position,
2440
+ };
2441
+ }
2442
+ else {
2443
+ this.pointerButton.style.display = 'block';
2444
+ }
2445
+ }
2446
+ getDistPosition(sdk, container, poseCamera, pos1, pos2) {
2447
+ if (!pos1 || !pos2)
2448
+ return 100; // Force update if one is missing
2449
+ const size = {
2450
+ w: container.clientWidth,
2451
+ h: container.clientHeight,
2452
+ };
2453
+ const coords1 = sdk.Conversion.worldToScreen(pos1, poseCamera, size);
2454
+ const coords2 = sdk.Conversion.worldToScreen(pos2, poseCamera, size);
2455
+ return Math.sqrt((coords1.x - coords2.x) ** 2 + (coords1.y - coords2.y) ** 2);
2456
+ }
2457
+ updateCursorDisplay(poseMatterport) {
2458
+ if (this.textDisplayCursorPositionPanel && poseMatterport) {
2459
+ this.textDisplayCursorPositionPanel.innerHTML = `position:
2460
+ ${pointToString(poseMatterport.position)}\n
2461
+ normal:
2462
+ ${pointToString(poseMatterport.normal)}\n
2463
+ floorId:
2464
+ ${poseMatterport.floorId}`;
2465
+ if (this.getCursorPositionButton) {
2466
+ this.getCursorPositionButton.style.display = 'none';
2467
+ }
2468
+ }
2469
+ }
2470
+ clear(callbacks) {
2471
+ if (this.pointerButton) {
2472
+ this.pointerButton.removeEventListener('click', callbacks.onLeftClick);
2473
+ this.pointerButton.removeEventListener('contextmenu', callbacks.onRightClick);
2474
+ }
2475
+ if (this.getCursorPositionButton) {
2476
+ this.getCursorPositionButton.removeEventListener('click', callbacks.onMiddleClick);
2477
+ }
2478
+ this.stopPointerTrick();
2479
+ this.stopAdminCursorTracker();
2480
+ }
2481
+ setCursorPositionButtonDisplayed(value) {
2482
+ this.cursorPositionButtonDisplayed = value;
2483
+ }
2484
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MatterportPointerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2485
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MatterportPointerService, providedIn: 'root' });
2486
+ }
2487
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MatterportPointerService, decorators: [{
2488
+ type: Injectable,
2489
+ args: [{
2490
+ providedIn: 'root',
2491
+ }]
2492
+ }], ctorParameters: () => [] });
2493
+
2494
+ const HoverEvent$1 = 'hover';
2495
+ const UnhoverEvent$1 = 'unhover';
2496
+ const RepaintEvent = 'repaint';
2497
+ class NestThermostat extends SceneComponent {
2498
+ cv = null;
2499
+ planeRenderer = null;
2500
+ rootScene = null;
2501
+ mixer = null;
2502
+ onEnterClip = null;
2503
+ mesh = null;
2504
+ currentTime = 0;
2505
+ nextUpdate = 0;
2506
+ temperature = 0;
2507
+ tempChangeRange = 5;
2508
+ component;
2509
+ inputs = {
2510
+ loadingState: 'Idle',
2511
+ texture: null,
2512
+ updateInterval: 1000,
2513
+ };
2514
+ outputs = {
2515
+ painter: null,
2516
+ visible: false,
2296
2517
  };
2297
2518
  events = {
2298
- [ComponentInteractionType.CLICK]: true,
2519
+ [HoverEvent$1]: true,
2520
+ [UnhoverEvent$1]: true,
2299
2521
  };
2300
- onInit(modelNode, inputTexture) {
2301
- this.inputs.texture = inputTexture;
2522
+ onInit(modelNode, plane, inputTexture) {
2302
2523
  if (!this.context) {
2303
2524
  this.context = {
2304
2525
  root: modelNode
2305
2526
  };
2306
2527
  }
2307
- this.pivotNode = new Group();
2308
- this.mesh = new Mesh(new PlaneBufferGeometry(1.0, 1.0), new MeshBasicMaterial({
2309
- transparent: this.inputs.transparent,
2310
- map: this.inputs.texture,
2311
- opacity: this.inputs.opacity,
2312
- polygonOffset: this.inputs.polygonOffset,
2313
- polygonOffsetFactor: this.inputs.polygonOffsetFactor,
2314
- polygonOffsetUnits: this.inputs.polygonOffsetUnits,
2315
- }));
2316
- this.mesh.scale.set(this.inputs.localScale.x, this.inputs.localScale.y / this.inputs.aspect, this.inputs.localScale.z);
2317
- this.mesh.position.set(this.inputs.localPosition.x, this.inputs.localPosition.y, this.inputs.localPosition.z);
2318
- this.mesh.updateMatrixWorld();
2319
- this.pivotNode.add(this.mesh);
2320
- this.outputs.objectRoot = this.pivotNode;
2321
- this.outputs.collider = this.pivotNode;
2322
- this.mesh.visible = this.inputs.visible;
2323
- }
2324
- onEvent(eventType, eventData) {
2325
- this.notify(eventType, eventData);
2528
+ this.inputs.texture = inputTexture;
2529
+ const root = this.context.root;
2530
+ const THREE = this.context.three;
2531
+ this.planeRenderer = plane;
2532
+ this.planeRenderer.outputs.objectRoot.translateZ(0.05);
2533
+ this.planeRenderer.outputs.objectRoot.translateY(0.4);
2534
+ this.planeRenderer.outputs.objectRoot.scale.set(0.5, 0.5, 0.5);
2535
+ // planeRenderer.inputs.localPosition.z = 0.05;
2536
+ // planeRenderer.inputs.localPosition.y = 0.4;
2537
+ // planeRenderer.inputs.localScale = {x: 0.5, y: 0.5, z: 0.5};
2538
+ this.outputs.painter = this;
2539
+ this.mixer = new AnimationMixer(this.planeRenderer.outputs.objectRoot);
2540
+ const tm = 0.2;
2541
+ const positionTrack = new VectorKeyframeTrack('.scale', [0, tm], [
2542
+ 0, 0, 0,
2543
+ 0.5, 0.5, 0.5
2544
+ ], InterpolateSmooth);
2545
+ this.onEnterClip = new AnimationClip(null, tm, [positionTrack]);
2326
2546
  }
2327
- onInputsUpdated(oldInputs) {
2328
- if (oldInputs.transparent !== this.inputs.transparent) {
2329
- this.mesh.material.transparent = this.inputs.transparent;
2330
- }
2331
- if (oldInputs.texture !== this.inputs.texture) {
2332
- const material = this.mesh.material;
2333
- material.map = this.inputs.texture;
2334
- material.needsUpdate = true;
2335
- }
2336
- if (oldInputs.visible !== this.inputs.visible) {
2337
- this.mesh.visible = this.inputs.visible;
2338
- }
2339
- if (oldInputs.polygonOffset !== this.inputs.polygonOffset) {
2340
- const material = this.mesh.material;
2341
- material.polygonOffset = this.inputs.polygonOffset;
2342
- material.polygonOffsetFactor = this.inputs.polygonOffsetFactor;
2343
- material.polygonOffsetUnits = this.inputs.polygonOffsetUnits;
2547
+ onInputsUpdated() {
2548
+ if (this.inputs.loadingState === 'Loaded') {
2549
+ const lines = [];
2550
+ //@ts-ignore
2551
+ this.context.root.obj3D.traverse((obj) => {
2552
+ // we dont want line segments, track them and remove them.
2553
+ if (obj.type === 'LineSegments') {
2554
+ lines.push(obj);
2555
+ }
2556
+ else if (obj.type === 'Mesh') {
2557
+ this.mesh = obj;
2558
+ const materials = this.mesh.material;
2559
+ materials.forEach((m, idx) => {
2560
+ if (m && m.map.source.data.outerHTML.indexOf('_5b76dbe388862300126c1e14') !== -1) {
2561
+ const newMaterial = new MeshBasicMaterial({ map: this.inputs.texture });
2562
+ this.mesh.material[idx] = newMaterial;
2563
+ }
2564
+ });
2565
+ }
2566
+ });
2567
+ // remove the line segments.
2568
+ lines.forEach((line) => {
2569
+ line.parent.remove(line);
2570
+ });
2571
+ }
2572
+ }
2573
+ onEvent(eventType, eventData) {
2574
+ if (eventType === HoverEvent$1) {
2575
+ const data = eventData;
2576
+ if (data.hover) {
2577
+ this.outputs.visible = true;
2578
+ const onEnterAction = this.mixer.clipAction(this.onEnterClip);
2579
+ onEnterAction.stop();
2580
+ onEnterAction.loop = LoopOnce;
2581
+ onEnterAction.clampWhenFinished = true;
2582
+ onEnterAction.play();
2583
+ }
2584
+ else {
2585
+ this.outputs.visible = false;
2586
+ }
2587
+ }
2588
+ }
2589
+ paint(context2d, size) {
2590
+ const x = 490;
2591
+ const y = 490;
2592
+ context2d.fillStyle = 'black';
2593
+ context2d.beginPath();
2594
+ context2d.arc(x, y, 400, 0, Math.PI * 2);
2595
+ context2d.fill();
2596
+ context2d.fillStyle = '#CF5300';
2597
+ context2d.beginPath();
2598
+ context2d.arc(x, y, 300, 0, Math.PI * 2);
2599
+ context2d.fill();
2600
+ context2d.beginPath();
2601
+ context2d.strokeStyle = 'orange';
2602
+ context2d.arc(x, y, 240, 0.75 * Math.PI, 0.25 * Math.PI);
2603
+ context2d.lineCap = 'butt';
2604
+ context2d.lineWidth = 80;
2605
+ context2d.stroke();
2606
+ context2d.fillStyle = 'white';
2607
+ context2d.font = '220px Arial';
2608
+ context2d.fillText(`${this.temperature}`, x - 115, y + 75);
2609
+ this.outputs.visible = true;
2610
+ }
2611
+ onTick(delta) {
2612
+ this.currentTime += delta;
2613
+ if (this.mixer) {
2614
+ this.mixer.update(delta / 1000);
2615
+ }
2616
+ if (this.currentTime > this.nextUpdate) {
2617
+ this.nextUpdate += this.inputs.updateInterval;
2618
+ this.temperature += (Math.random() * this.tempChangeRange);
2619
+ this.temperature = Math.trunc(this.temperature);
2620
+ if (this.temperature > 99) {
2621
+ this.temperature = 99;
2622
+ this.tempChangeRange = -this.tempChangeRange;
2623
+ }
2624
+ if (this.temperature < 10) {
2625
+ this.temperature = 10;
2626
+ this.tempChangeRange = -this.tempChangeRange;
2627
+ }
2628
+ this.component.notify(RepaintEvent);
2629
+ this.cv.repaint();
2630
+ }
2631
+ }
2632
+ setComponent(component, plane, cv) {
2633
+ component.onTick = this.onTick.bind(this);
2634
+ // plane.onTick = this.onTick.bind(this);
2635
+ this.cv = cv;
2636
+ this.component = component;
2637
+ this.context = component.context;
2638
+ }
2639
+ setRootScene(rootScene) {
2640
+ this.rootScene = rootScene;
2641
+ }
2642
+ }
2643
+ const nestThermostatType = 'mp.nestThermostat';
2644
+ const makeNestThermostat = function () {
2645
+ return new NestThermostat();
2646
+ };
2647
+
2648
+ class PlaneRenderer extends SceneComponent {
2649
+ rootScene = null;
2650
+ mesh;
2651
+ pivotNode;
2652
+ inputs = {
2653
+ texture: null,
2654
+ aspect: 1,
2655
+ transparent: true,
2656
+ visible: true,
2657
+ opacity: 1,
2658
+ polygonOffset: false,
2659
+ polygonOffsetFactor: 0,
2660
+ polygonOffsetUnits: 0,
2661
+ localScale: { x: 1, y: 1, z: 1 },
2662
+ localPosition: { x: 0, y: 0, z: 0 },
2663
+ };
2664
+ events = {
2665
+ [ComponentInteractionType.CLICK]: true,
2666
+ };
2667
+ onInit(modelNode, inputTexture) {
2668
+ this.inputs.texture = inputTexture;
2669
+ if (!this.context) {
2670
+ this.context = {
2671
+ root: modelNode
2672
+ };
2673
+ }
2674
+ this.pivotNode = new Group();
2675
+ this.mesh = new Mesh(new PlaneBufferGeometry(1.0, 1.0), new MeshBasicMaterial({
2676
+ transparent: this.inputs.transparent,
2677
+ map: this.inputs.texture,
2678
+ opacity: this.inputs.opacity,
2679
+ polygonOffset: this.inputs.polygonOffset,
2680
+ polygonOffsetFactor: this.inputs.polygonOffsetFactor,
2681
+ polygonOffsetUnits: this.inputs.polygonOffsetUnits,
2682
+ }));
2683
+ this.mesh.scale.set(this.inputs.localScale.x, this.inputs.localScale.y / this.inputs.aspect, this.inputs.localScale.z);
2684
+ this.mesh.position.set(this.inputs.localPosition.x, this.inputs.localPosition.y, this.inputs.localPosition.z);
2685
+ this.mesh.updateMatrixWorld();
2686
+ this.pivotNode.add(this.mesh);
2687
+ this.outputs.objectRoot = this.pivotNode;
2688
+ this.outputs.collider = this.pivotNode;
2689
+ this.mesh.visible = this.inputs.visible;
2690
+ }
2691
+ onEvent(eventType, eventData) {
2692
+ this.notify(eventType, eventData);
2693
+ }
2694
+ onInputsUpdated(oldInputs) {
2695
+ if (oldInputs.transparent !== this.inputs.transparent) {
2696
+ this.mesh.material.transparent = this.inputs.transparent;
2697
+ }
2698
+ if (oldInputs.texture !== this.inputs.texture) {
2699
+ const material = this.mesh.material;
2700
+ material.map = this.inputs.texture;
2701
+ material.needsUpdate = true;
2702
+ }
2703
+ if (oldInputs.visible !== this.inputs.visible) {
2704
+ this.mesh.visible = this.inputs.visible;
2705
+ }
2706
+ if (oldInputs.polygonOffset !== this.inputs.polygonOffset) {
2707
+ const material = this.mesh.material;
2708
+ material.polygonOffset = this.inputs.polygonOffset;
2709
+ material.polygonOffsetFactor = this.inputs.polygonOffsetFactor;
2710
+ material.polygonOffsetUnits = this.inputs.polygonOffsetUnits;
2711
+ }
2712
+ this.mesh.scale.set(this.inputs.localScale.x, this.inputs.localScale.y / this.inputs.aspect, this.inputs.localScale.z);
2713
+ this.mesh.position.set(this.inputs.localPosition.x, this.inputs.localPosition.y, this.inputs.localPosition.z);
2714
+ }
2715
+ onDestroy() {
2716
+ this.outputs.collider = null;
2717
+ this.outputs.objectRoot = null;
2718
+ this.mesh.material.dispose();
2719
+ this.mesh.geometry.dispose();
2720
+ }
2721
+ setRootScene(rootScene) {
2722
+ this.rootScene = rootScene;
2723
+ }
2724
+ }
2725
+ const planeRendererType = 'mp.planeRenderer';
2726
+ function makePlaneRenderer() {
2727
+ return new PlaneRenderer();
2728
+ }
2729
+
2730
+ class CanvasRenderer extends SceneComponent {
2731
+ canvas;
2732
+ renderContext2D;
2733
+ inputs = {
2734
+ painter: null,
2735
+ textureRes: { w: 1024, h: 1024 }, // dim for thermostat
2736
+ };
2737
+ outputs = {
2738
+ texture: null,
2739
+ };
2740
+ events = {
2741
+ repaint: true,
2742
+ };
2743
+ onInit() {
2744
+ // set up canvas 2d context
2745
+ this.canvas = document.createElement('canvas');
2746
+ this.renderContext2D = this.canvas.getContext('2d');
2747
+ this.outputs.texture = new CanvasTexture(this.canvas);
2748
+ this.resize(this.inputs.textureRes);
2749
+ this.repaint();
2750
+ }
2751
+ setCanvasNestThermostatPainter(sc) {
2752
+ this.inputs.painter = sc;
2753
+ }
2754
+ onInputsUpdated(oldInputs) {
2755
+ if (oldInputs.textureRes.w !== this.inputs.textureRes.w ||
2756
+ oldInputs.textureRes.h !== this.inputs.textureRes.h) {
2757
+ this.resize(this.inputs.textureRes);
2758
+ }
2759
+ if (oldInputs.painter !== this.inputs.painter) {
2760
+ this.repaint();
2761
+ }
2762
+ }
2763
+ onEvent(eventType, _eventData) {
2764
+ if (eventType === 'repaint') {
2765
+ this.repaint();
2766
+ }
2767
+ }
2768
+ onDestroy() {
2769
+ this.outputs.texture.dispose();
2770
+ this.outputs.texture = null;
2771
+ }
2772
+ resize(size) {
2773
+ this.canvas.width = size.w;
2774
+ this.canvas.height = size.h;
2775
+ }
2776
+ repaint() {
2777
+ if (this.inputs.painter) {
2778
+ this.inputs.painter.paint(this.renderContext2D, this.inputs.textureRes);
2779
+ this.outputs.texture.needsUpdate = true;
2780
+ }
2781
+ }
2782
+ }
2783
+ const canvasRendererType = 'mp.canvasRenderer';
2784
+ function makeCanvasRenderer() {
2785
+ return new CanvasRenderer();
2786
+ }
2787
+
2788
+ const HoverEvent = 'hover';
2789
+ const UnhoverEvent = 'unhover';
2790
+ class TvPlayer extends SceneComponent {
2791
+ rootScene = null;
2792
+ mesh = null;
2793
+ component;
2794
+ inputs = {
2795
+ loadingState: 'Idle',
2796
+ texture: null,
2797
+ updateInterval: 1000,
2798
+ };
2799
+ events = {
2800
+ [HoverEvent]: true,
2801
+ [UnhoverEvent]: true,
2802
+ };
2803
+ intervalVideoTexture;
2804
+ onInit(modelNode) {
2805
+ if (!this.context) {
2806
+ this.context = {
2807
+ root: modelNode
2808
+ };
2809
+ }
2810
+ const root = this.context.root;
2811
+ const THREE = this.context.three;
2812
+ }
2813
+ onDestroy() {
2814
+ super.onDestroy();
2815
+ clearInterval(this.intervalVideoTexture);
2816
+ }
2817
+ onInputsUpdated() {
2818
+ if (this.inputs.loadingState === 'Loaded') {
2819
+ const lines = [];
2820
+ //@ts-ignore
2821
+ this.context.root.obj3D.traverse((obj) => {
2822
+ // we dont want line segments, track them and remove them.
2823
+ if (obj.type === 'LineSegments') {
2824
+ lines.push(obj);
2825
+ }
2826
+ else if (obj.type === 'Mesh') {
2827
+ this.mesh = obj;
2828
+ let video, videoImage, videoImageContext, videoTexture;
2829
+ // create the video element
2830
+ video = document.createElement('video');
2831
+ // video.src = "./../assets/video/video_samsic.mp4";
2832
+ video.src = "./../assets/video/121222-04.mp4";
2833
+ video.load(); // must call after setting/changing source
2834
+ video.loop = true;
2835
+ video.play();
2836
+ videoImage = document.createElement('canvas');
2837
+ // videoImage.width = 640;
2838
+ videoImage.width = 1280;
2839
+ // videoImage.height = 360;
2840
+ videoImage.height = 720;
2841
+ videoImageContext = videoImage.getContext('2d');
2842
+ // background color if no video present
2843
+ videoImageContext.fillStyle = '#000000';
2844
+ videoImageContext.fillRect(0, 0, videoImage.width, videoImage.height);
2845
+ videoTexture = new Texture(videoImage);
2846
+ videoTexture.minFilter = LinearFilter;
2847
+ videoTexture.magFilter = LinearFilter;
2848
+ const movieMaterial = new MeshBasicMaterial({ map: videoTexture });
2849
+ this.mesh.material = movieMaterial;
2850
+ const render = () => {
2851
+ if (video.readyState === video.HAVE_ENOUGH_DATA) {
2852
+ videoImageContext.drawImage(video, 0, 0);
2853
+ if (videoTexture)
2854
+ videoTexture.needsUpdate = true;
2855
+ }
2856
+ const camera = (this.context.scene.children.find(u => u.constructor.name === 'CameraRig')).camera;
2857
+ this.context.renderer.render(this.context.scene, camera);
2858
+ };
2859
+ this.intervalVideoTexture = setInterval(() => {
2860
+ render();
2861
+ }, 180);
2862
+ }
2863
+ });
2864
+ // remove the line segments.
2865
+ lines.forEach((line) => {
2866
+ line.parent.remove(line);
2867
+ });
2868
+ }
2869
+ }
2870
+ setComponent(component) {
2871
+ this.component = component;
2872
+ this.context = component.context;
2873
+ }
2874
+ setRootScene(rootScene) {
2875
+ this.rootScene = rootScene;
2876
+ }
2877
+ }
2878
+ const TvPlayerType = 'mp.TvPlayer';
2879
+ const makeTvPlayer = function () {
2880
+ return new TvPlayer();
2881
+ };
2882
+
2883
+ class MatterportObject3DService {
2884
+ dictionnaryObjects3D = new Map();
2885
+ dictionnarySceneObjects3D = new Map();
2886
+ lastObject3D; // Three JS object
2887
+ objectControl;
2888
+ securityCameraAnimator;
2889
+ azimuthalCrown;
2890
+ threeJSScene;
2891
+ noLightForObjects = true;
2892
+ constructor() { }
2893
+ async init3DObjectViewer(sdk) {
2894
+ const [sceneObject] = await sdk.Scene.createObjects(1);
2895
+ const node = sceneObject.addNode();
2896
+ node.start();
2897
+ this.threeJSScene = node.obj3D.parent;
2898
+ const nodeControl = sceneObject.addNode();
2899
+ this.objectControl = nodeControl.addComponent('mp.transformControls');
2900
+ nodeControl.start();
2901
+ }
2902
+ async add3DObject(sdk, obj, mode) {
2903
+ return new Promise(async (resolve) => {
2904
+ const [sceneObject] = await sdk.Scene.createObjects(1);
2905
+ let isAnimatedSecurityCamera = (obj.object === "security_camera");
2906
+ let isNestThermostat = (obj.object === "nest_thermostat");
2907
+ let isSmarterplanPromotionalVideo = (obj.object === 'video');
2908
+ let isAzimuthalCrown = (obj.object === 'azimuth');
2909
+ const modelNode = sceneObject.addNode();
2910
+ let component = null;
2911
+ const initial = {
2912
+ url: `/assets/3Dobjects/objects/${obj.object}${obj.format.indexOf('.') === -1 ? '.' + obj.format : obj.format}`,
2913
+ localRotation: { "x": 0, "y": 0, "z": 0 },
2914
+ localPosition: { "x": 0, "y": 0, "z": 0 },
2915
+ visible: true,
2916
+ colliderEnabled: true
2917
+ };
2918
+ switch (obj.format) {
2919
+ case '.obj':
2920
+ case 'obj':
2921
+ component = modelNode.addComponent('mp.objLoader', initial);
2922
+ break;
2923
+ case '.fbx':
2924
+ case 'fbx':
2925
+ component = modelNode.addComponent('mp.fbxLoader', initial);
2926
+ break;
2927
+ case '.gltf':
2928
+ case 'gltf':
2929
+ case '.glb':
2930
+ case 'glb':
2931
+ component = modelNode.addComponent('mp.gltfLoader', initial);
2932
+ break;
2933
+ case '.dae':
2934
+ case 'dae':
2935
+ component = modelNode.addComponent('mp.daeLoader', initial);
2936
+ break;
2937
+ default:
2938
+ console.log('Format not supported');
2939
+ break;
2940
+ }
2941
+ if (this.noLightForObjects) {
2942
+ const lightsNode = sceneObject.addNode();
2943
+ lightsNode.addComponent('mp.ambientLight', {
2944
+ intensity: 1,
2945
+ color: { r: 1.0, g: 1.0, b: 1.0 },
2946
+ });
2947
+ this.noLightForObjects = false;
2948
+ }
2949
+ modelNode.obj3D.position.set(obj.position.x, obj.position.y, obj.position.z);
2950
+ modelNode.obj3D.rotation.set(obj.rotation.x, obj.rotation.y, obj.rotation.z);
2951
+ modelNode.obj3D.scale.set(obj.scale.x, obj.scale.y, obj.scale.z);
2952
+ if (isAzimuthalCrown) {
2953
+ this.azimuthalCrown = modelNode;
2954
+ }
2955
+ if (mode && !isNestThermostat) {
2956
+ this.attachGizmoControlTo3DObject(sdk, modelNode, sceneObject, mode, true, true).catch((e) => console.log(e));
2957
+ }
2958
+ if (this.lastObject3D && typeof this.lastObject3D.id === 'string') {
2959
+ modelNode.obj3D.uuid = this.lastObject3D.id;
2960
+ }
2961
+ else if (obj.id) {
2962
+ modelNode.obj3D.uuid = obj.id;
2963
+ }
2964
+ this.lastObject3D = modelNode.obj3D;
2965
+ this.dictionnaryObjects3D.set(modelNode.obj3D.uuid, modelNode);
2966
+ this.dictionnarySceneObjects3D.set(modelNode.obj3D.uuid, sceneObject);
2967
+ if (isAnimatedSecurityCamera) {
2968
+ const animator = modelNode.addComponent('mp.securityCamera', {
2969
+ "nearPlane": 0.1, "farPlane": 10, "horizontalFOV": 52, "aspect": 1.7777777777777777,
2970
+ "localPosition": { "x": 0.3, "y": 0.18, "z": 0 },
2971
+ "localRotation": { "x": -15, "y": -90, "z": 0 },
2972
+ "color": 65280, "panPeriod": 5, "panAngle": -45
2973
+ });
2974
+ this.securityCameraAnimator = animator;
2975
+ if (!obj.viewFrustum) {
2976
+ setTimeout(() => { animator.toggleViewFrustum(); }, 1000);
2977
+ }
2978
+ }
2979
+ if (isNestThermostat) {
2980
+ const cv = new CanvasRenderer();
2981
+ cv.onInit();
2982
+ const plane = new PlaneRenderer();
2983
+ plane.outputs = { objectRoot: new Object3D(), collider: new Object3D() };
2984
+ const inputTexture = cv.outputs.texture;
2985
+ plane.setRootScene(this.threeJSScene);
2986
+ plane.onInit(modelNode, inputTexture);
2987
+ const sc = new NestThermostat();
2988
+ sc.setComponent(component, plane, cv);
2989
+ sc.setRootScene(this.threeJSScene);
2990
+ sc.onInit(modelNode, plane, inputTexture);
2991
+ cv.setCanvasNestThermostatPainter(sc);
2992
+ setTimeout(() => { sc.inputs.loadingState = "Loaded"; sc.onInputsUpdated(); }, 5000);
2993
+ }
2994
+ if (isSmarterplanPromotionalVideo) {
2995
+ const sc = new TvPlayer();
2996
+ sc.setComponent(component);
2997
+ sc.onInit(modelNode);
2998
+ setTimeout(() => { sc.inputs.loadingState = "Loaded"; sc.onInputsUpdated(); }, 5000);
2999
+ }
3000
+ sceneObject.start();
3001
+ resolve(this.lastObject3D);
3002
+ });
3003
+ }
3004
+ async deleteObject3D(uuid) {
3005
+ const obj = this.dictionnaryObjects3D.get(uuid);
3006
+ if (obj)
3007
+ obj.obj3D.visible = false;
3008
+ }
3009
+ getObject3DModelNodeFromDictionnary(uuid) {
3010
+ let obj = this.dictionnaryObjects3D.get(uuid);
3011
+ if (!obj)
3012
+ return null;
3013
+ if (obj.obj3D.uuid != uuid) {
3014
+ obj.obj3D.uuid = uuid;
3015
+ }
3016
+ return obj;
3017
+ }
3018
+ toggleObjectVisibility(objectId) {
3019
+ let obj = this.dictionnaryObjects3D.get(objectId);
3020
+ if (obj)
3021
+ obj.obj3D.visible = !obj.obj3D.visible;
3022
+ }
3023
+ isObjectVisible(objectId) {
3024
+ let obj = this.dictionnaryObjects3D.get(objectId);
3025
+ return obj ? obj.obj3D.visible : false;
3026
+ }
3027
+ async pointCameraTo3DObject(sdk, objectId, createTagCallback, goToTagCallback, deleteLastTagCallback, getTagFromElementId) {
3028
+ let obj = this.dictionnaryObjects3D.get(objectId);
3029
+ const poiObject = {
3030
+ coordinate: JSON.stringify(obj.obj3D.position),
3031
+ type: PoiType.OBJECT3D,
3032
+ elementID: objectId,
3033
+ };
3034
+ const objectDb = { id: objectId };
3035
+ try {
3036
+ await createTagCallback(PoiType.OBJECT3D, objectDb, poiObject);
3037
+ }
3038
+ catch (err) { }
3039
+ let result = getTagFromElementId(objectId);
3040
+ await goToTagCallback(result.tag);
3041
+ deleteLastTagCallback();
3042
+ }
3043
+ getSceneNodeFromObject3DId(uuid) {
3044
+ return this.dictionnarySceneObjects3D.get(uuid);
3045
+ }
3046
+ async displayAzimutalCrown(poseCamera) {
3047
+ if (this.azimuthalCrown && poseCamera) {
3048
+ this.azimuthalCrown.obj3D.position.set(poseCamera.position.x, poseCamera.position.y, poseCamera.position.z);
3049
+ }
3050
+ }
3051
+ async attachGizmoControlTo3DObject(sdk, modelNode, sceneObject, mode, visible, isNewObject) {
3052
+ let node = sceneObject.addNode();
3053
+ if (!node) {
3054
+ const [newSceneObject] = await sdk.Scene.createObjects(1);
3055
+ node = newSceneObject.addNode();
3056
+ }
3057
+ const myControl = node.addComponent('mp.transformControls');
3058
+ node.start();
3059
+ myControl.transformControls.visible = visible;
3060
+ myControl.inputs.selection = modelNode;
3061
+ myControl.inputs.mode = mode;
3062
+ modelNode.obj3D.controls = myControl;
3063
+ if (isNewObject) {
3064
+ if (!this.lastObject3D || !this.lastObject3D.controls) {
3065
+ try {
3066
+ modelNode.obj3D.uuid = this.lastObject3D.uuid || this.lastObject3D.id;
3067
+ }
3068
+ catch (e) {
3069
+ console.log(`id obj in Scene was not assigned to id from DB`);
3070
+ }
3071
+ this.lastObject3D = modelNode.obj3D;
3072
+ }
3073
+ }
3074
+ else {
3075
+ this.lastObject3D = modelNode.obj3D;
3076
+ }
3077
+ return modelNode;
3078
+ }
3079
+ removeGizmoFromLastObject() {
3080
+ if (this.lastObject3D && this.lastObject3D.controls) {
3081
+ this.lastObject3D.controls.transformControls.visible = false;
3082
+ this.lastObject3D.controls.transformControls.dispose();
3083
+ this.lastObject3D.controls = null;
3084
+ }
3085
+ }
3086
+ toggleViewFrustum() {
3087
+ if (this.securityCameraAnimator) {
3088
+ this.securityCameraAnimator.toggleViewFrustum();
3089
+ }
3090
+ }
3091
+ clear() {
3092
+ this.dictionnaryObjects3D.clear();
3093
+ this.dictionnarySceneObjects3D.clear();
3094
+ this.lastObject3D = null;
3095
+ this.azimuthalCrown = null;
3096
+ this.securityCameraAnimator = null;
3097
+ this.noLightForObjects = true;
3098
+ }
3099
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MatterportObject3DService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3100
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MatterportObject3DService, providedIn: 'root' });
3101
+ }
3102
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MatterportObject3DService, decorators: [{
3103
+ type: Injectable,
3104
+ args: [{
3105
+ providedIn: 'root',
3106
+ }]
3107
+ }], ctorParameters: () => [] });
3108
+
3109
+ class MatterportTagService {
3110
+ navigationService;
3111
+ dictionnaryTags = new Map();
3112
+ tagAddQueue = Promise.resolve();
3113
+ mattertagToFollow = null;
3114
+ onGoToTag = new Subject();
3115
+ mattertagIDs = [];
3116
+ tagsAttachments = {};
3117
+ tagMessengerOn = false;
3118
+ tagService = null;
3119
+ tagsCurrentOpacity = new Map();
3120
+ tagsCurrentEnabled = new Map();
3121
+ constructor(navigationService) {
3122
+ this.navigationService = navigationService;
3123
+ }
3124
+ setTagService(service) {
3125
+ this.tagService = service;
3126
+ }
3127
+ /** Opens a specific tag in the Matterport viewer by its ID. */
3128
+ async goToTag(sdk, sid) {
3129
+ if (!sdk)
3130
+ return;
3131
+ try {
3132
+ this.onGoToTag.next(sid);
3133
+ await sdk.Tag.open(sid);
3134
+ }
3135
+ catch (e) {
3136
+ if (isDevMode())
3137
+ console.warn('Cannot open tag', sid, e);
3138
+ }
3139
+ }
3140
+ /** Opens the most recently created tag in the viewer. */
3141
+ async goToLastTag(sdk) {
3142
+ if (!sdk || this.mattertagIDs.length === 0)
3143
+ return;
3144
+ const lastSid = this.mattertagIDs[this.mattertagIDs.length - 1];
3145
+ return this.goToTag(sdk, lastSid);
3146
+ }
3147
+ /**
3148
+ * Adds a Mattertag to the viewer, serializing calls to prevent SDK race conditions.
3149
+ * Reuses existing IDs if the tag is already present in the session.
3150
+ */
3151
+ async addMattertagToViewer(sdk, mattertagData) {
3152
+ if (!sdk)
3153
+ return null;
3154
+ this.tagAddQueue = this.tagAddQueue.then(async () => {
3155
+ const data = mattertagData.getData();
3156
+ const existingId = data?.id || null;
3157
+ try {
3158
+ const sidList = await sdk.Tag.add(data);
3159
+ if (sidList && sidList.length > 0) {
3160
+ const mattertagID = sidList[0];
3161
+ this.mattertagIDs.push(mattertagID);
3162
+ this.dictionnaryTags.set(mattertagID, mattertagData);
3163
+ return mattertagID;
3164
+ }
3165
+ }
3166
+ catch (error) {
3167
+ if (error?.message?.includes('already in use') && existingId) {
3168
+ if (!this.dictionnaryTags.has(existingId)) {
3169
+ this.mattertagIDs.push(existingId);
3170
+ this.dictionnaryTags.set(existingId, mattertagData);
3171
+ }
3172
+ return existingId;
3173
+ }
3174
+ if (isDevMode())
3175
+ console.warn('Failed to add tag to viewer', error);
3176
+ }
3177
+ return null;
3178
+ });
3179
+ return this.tagAddQueue;
3180
+ }
3181
+ /**
3182
+ * Internal method to add and configure a Mattertag with icon, opacity, and HTML content.
3183
+ * Handles existing tags gracefully and queues operations to avoid SDK conflicts.
3184
+ */
3185
+ async _doAddMattertag(sdk, mattertagData, setTagIconAndOpacity, injectHtmlInTag) {
3186
+ const descriptor = mattertagData.getMattertagDescriptor();
3187
+ //console.log('descriptor', descriptor);
3188
+ const sid = descriptor[0]['id'];
3189
+ if (sid && this.dictionnaryTags.has(sid)) {
3190
+ await setTagIconAndOpacity(sid, mattertagData);
3191
+ await injectHtmlInTag(mattertagData.getType(), mattertagData.getObject(), sid);
3192
+ return sid;
3193
+ }
3194
+ this.tagAddQueue = this.tagAddQueue.then(async () => {
3195
+ try {
3196
+ const [createdSid] = await sdk.Tag.add(...descriptor);
3197
+ this.mattertagIDs.push(createdSid);
3198
+ this.dictionnaryTags.set(createdSid, mattertagData);
3199
+ await wait(100);
3200
+ await setTagIconAndOpacity(createdSid, mattertagData);
3201
+ injectHtmlInTag(mattertagData.getType(), mattertagData.getObject(), createdSid).catch(err => {
3202
+ if (isDevMode())
3203
+ console.warn('HTML injection failed for', createdSid, err);
3204
+ });
3205
+ return createdSid;
3206
+ }
3207
+ catch (e) {
3208
+ if (e?.message?.includes('already in use') && sid) {
3209
+ if (!this.dictionnaryTags.has(sid)) {
3210
+ this.mattertagIDs.push(sid);
3211
+ this.dictionnaryTags.set(sid, mattertagData);
3212
+ }
3213
+ await setTagIconAndOpacity(sid, mattertagData);
3214
+ await injectHtmlInTag(mattertagData.getType(), mattertagData.getObject(), sid);
3215
+ return sid;
3216
+ }
3217
+ throw e;
3218
+ }
3219
+ });
3220
+ return this.tagAddQueue;
3221
+ }
3222
+ getDistance(p1, p2) {
3223
+ return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2) + Math.pow(p2.z - p1.z, 2));
3224
+ }
3225
+ /**
3226
+ * Checks if a tag is within the bounding box of any active room (adding a slight margin to account for wall-mounted tags).
3227
+ * If there are no active rooms (e.g., hallway), falls back to returning true.
3228
+ */
3229
+ isTagInRooms(mattertagData, rooms) {
3230
+ if (!rooms || rooms.length === 0)
3231
+ return false;
3232
+ const tagPos = mattertagData.getPosition();
3233
+ const margin = 1.5;
3234
+ return rooms.some((room) => {
3235
+ if (!room.bounds)
3236
+ return false;
3237
+ const { min, max } = room.bounds;
3238
+ return (tagPos.x >= min.x - margin && tagPos.x <= max.x + margin &&
3239
+ tagPos.y >= min.y - margin && tagPos.y <= max.y + margin &&
3240
+ tagPos.z >= min.z - margin && tagPos.z <= max.z + margin);
3241
+ });
3242
+ }
3243
+ /**
3244
+ * Computes the final context-aware opacity for a single tag.
3245
+ * @param currentFloorSequence Authoritative current floor sequence from sdk.Floor.current
3246
+ * (null = single-floor model or unknown).
3247
+ * @param currentRooms Array of active rooms the camera is in.
3248
+ */
3249
+ /*computeSingleTagContextOpacity(
3250
+ mattertagData: MattertagData,
3251
+ poseCamera: any,
3252
+ sweepCollection: any,
3253
+ currentFloorSequence: number | null,
3254
+ currentRooms: any[]
3255
+ ): number {
3256
+ const mode = this.navigationService.currentCameraMode;
3257
+
3258
+ // 1. Check Floor
3259
+ const tagFloorId = this.getFloorIdForTag(mattertagData, sweepCollection);
3260
+ const currentFloorId = currentFloorSequence?.toString();
3261
+ if (tagFloorId && currentFloorId && tagFloorId !== currentFloorId) {
3262
+ return 0;
3263
+ }
3264
+
3265
+ // 2. Si Dollhouse -> Pleine opacité
3266
+ //if (mode !== CameraMode.INSIDE) return 1;
3267
+
3268
+ // 3. Si Inside -> Check pièce + Distance progressive
3269
+ //if (!this.isTagInRooms(mattertagData, currentRooms)) return 0;
3270
+
3271
+ return 1;
3272
+ }*/
3273
+ /** Returns the ID of the most recently added tag. */
3274
+ getLastTag() {
3275
+ if (this.mattertagIDs.length === 0)
3276
+ return null;
3277
+ return this.mattertagIDs[this.mattertagIDs.length - 1];
3278
+ }
3279
+ /**
3280
+ * Updates the visibility (opacity) of all tags based on the current camera mode,
3281
+ * floor and room context. Must be called after each sweep, floor or mode change.
3282
+ *
3283
+ * Rules:
3284
+ * - DOLLHOUSE / FLOORPLAN → all tags hidden (opacity 0).
3285
+ * - INSIDE → only tags whose sweep belongs to the current floor are shown;
3286
+ * proximity-based opacity is applied via computeTagOpacity().
3287
+ * Tags on other floors are hidden (opacity 0).
3288
+ * - TRANSITIONING → no change (skip update to avoid flickering).
3289
+ *
3290
+ * @param sdk Matterport SDK instance.
3291
+ * @param poseCamera Current camera pose.
3292
+ * @param sweepCollection Full collection of sweeps.
3293
+ * @param currentCameraMode Current camera mode.
3294
+ * @param mattertagToFollow ID of the cursor tag (never touched).
3295
+ */
3296
+ async updateTagsVisibilityForContext(sdk, sweepCollection, currentCameraMode, mattertagToFollow, currentFloorSequence = null, currentRooms = []) {
3297
+ if (!sdk || this.dictionnaryTags.size === 0)
3298
+ return;
3299
+ const isInteriorMode = currentCameraMode === CameraMode.INSIDE;
3300
+ const currentFloorId = currentFloorSequence !== null ? currentFloorSequence.toString() : null;
3301
+ const promises = [];
3302
+ for (const [tagId, mattertagData] of this.dictionnaryTags) {
3303
+ if (tagId === mattertagToFollow)
3304
+ continue;
3305
+ const tagFloorId = this.getFloorIdForTag(mattertagData, sweepCollection);
3306
+ const isOnCurrentFloor = !tagFloorId || !currentFloorId || tagFloorId === currentFloorId;
3307
+ let targetOpacity = 0;
3308
+ let shouldBeEnabled = false;
3309
+ // Calculation of opacity and enable status
3310
+ if (isOnCurrentFloor) {
3311
+ if (!isInteriorMode) {
3312
+ targetOpacity = 1;
3313
+ shouldBeEnabled = true;
3314
+ }
3315
+ else {
3316
+ targetOpacity = 0.9;
3317
+ shouldBeEnabled = true;
3318
+ }
3319
+ }
3320
+ // Apply changes
3321
+ // Tooltip and click management
3322
+ const lastEnabled = this.tagsCurrentEnabled.get(tagId);
3323
+ if (lastEnabled !== shouldBeEnabled) {
3324
+ this.tagsCurrentEnabled.set(tagId, shouldBeEnabled);
3325
+ promises.push(sdk.Tag.allowAction(tagId, {
3326
+ opening: shouldBeEnabled,
3327
+ navigating: shouldBeEnabled,
3328
+ docking: false
3329
+ }).catch(() => { }));
3330
+ }
3331
+ // Opacity management
3332
+ const lastOpacity = this.tagsCurrentOpacity.get(tagId) ?? -1;
3333
+ if (Math.abs(lastOpacity - targetOpacity) > 0.05) {
3334
+ this.tagsCurrentOpacity.set(tagId, targetOpacity);
3335
+ promises.push(sdk.Tag.editOpacity(tagId, targetOpacity).catch(() => { }));
3336
+ }
2344
3337
  }
2345
- this.mesh.scale.set(this.inputs.localScale.x, this.inputs.localScale.y / this.inputs.aspect, this.inputs.localScale.z);
2346
- this.mesh.position.set(this.inputs.localPosition.x, this.inputs.localPosition.y, this.inputs.localPosition.z);
2347
- }
2348
- onDestroy() {
2349
- this.outputs.collider = null;
2350
- this.outputs.objectRoot = null;
2351
- this.mesh.material.dispose();
2352
- this.mesh.geometry.dispose();
3338
+ await Promise.all(promises);
2353
3339
  }
2354
- setRootScene(rootScene) {
2355
- this.rootScene = rootScene;
3340
+ /** Removes a specific tag from the viewer and local dictionaries. */
3341
+ async deleteMattertagFromId(sdk, mattertagID) {
3342
+ try {
3343
+ if (sdk)
3344
+ await sdk.Tag.remove(mattertagID);
3345
+ this.dictionnaryTags.delete(mattertagID);
3346
+ const index = this.mattertagIDs.indexOf(mattertagID);
3347
+ if (index > -1)
3348
+ this.mattertagIDs.splice(index, 1);
3349
+ }
3350
+ catch (e) {
3351
+ if (isDevMode())
3352
+ console.warn('Cannot delete tag', mattertagID, e);
3353
+ }
2356
3354
  }
2357
- }
2358
- const planeRendererType = 'mp.planeRenderer';
2359
- function makePlaneRenderer() {
2360
- return new PlaneRenderer();
2361
- }
2362
-
2363
- class CanvasRenderer extends SceneComponent {
2364
- canvas;
2365
- renderContext2D;
2366
- inputs = {
2367
- painter: null,
2368
- textureRes: { w: 1024, h: 1024 }, // dim for thermostat
2369
- };
2370
- outputs = {
2371
- texture: null,
2372
- };
2373
- events = {
2374
- repaint: true,
2375
- };
2376
- onInit() {
2377
- // set up canvas 2d context
2378
- this.canvas = document.createElement('canvas');
2379
- this.renderContext2D = this.canvas.getContext('2d');
2380
- this.outputs.texture = new CanvasTexture(this.canvas);
2381
- this.resize(this.inputs.textureRes);
2382
- this.repaint();
3355
+ /** Removes the last added tag from the viewer. */
3356
+ async deleteLastMattertag(sdk) {
3357
+ const lastSid = this.getLastTag();
3358
+ if (lastSid)
3359
+ await this.deleteMattertagFromId(sdk, lastSid);
2383
3360
  }
2384
- setCanvasNestThermostatPainter(sc) {
2385
- this.inputs.painter = sc;
3361
+ /** Clears all tags from the viewer and resets local state. */
3362
+ async action_delete_all_mattertags(sdk) {
3363
+ if (sdk) {
3364
+ const sidsToRemove = [...this.mattertagIDs];
3365
+ for (const sid of sidsToRemove) {
3366
+ try {
3367
+ await sdk.Tag.remove(sid);
3368
+ }
3369
+ catch (e) { }
3370
+ }
3371
+ }
3372
+ this.clear();
2386
3373
  }
2387
- onInputsUpdated(oldInputs) {
2388
- if (oldInputs.textureRes.w !== this.inputs.textureRes.w ||
2389
- oldInputs.textureRes.h !== this.inputs.textureRes.h) {
2390
- this.resize(this.inputs.textureRes);
3374
+ /**
3375
+ * Creates and configures a Mattertag from a POI, handling coordinates, normals, and sweep ID.
3376
+ * Skips creation if a tag for the object already exists.
3377
+ */
3378
+ async createMattertagFromPOI(sdk, tagType, object, poi, poseCamera, getTagFromElementId, setTagIconAndOpacity, injectHtmlInTag) {
3379
+ const { tag } = getTagFromElementId(object.id);
3380
+ if (tag)
3381
+ return;
3382
+ const mattertagData = new MattertagData(tagType);
3383
+ mattertagData.setObject(object, tagType);
3384
+ if (poi.coordinate) {
3385
+ try {
3386
+ mattertagData.setPosition(JSON.parse(poi.coordinate));
3387
+ }
3388
+ catch (e) {
3389
+ if (isDevMode())
3390
+ console.warn('Error parsing POI coordinates', poi.coordinate);
3391
+ }
2391
3392
  }
2392
- if (oldInputs.painter !== this.inputs.painter) {
2393
- this.repaint();
3393
+ if (poi.metadata) {
3394
+ try {
3395
+ const tagMetadata = JSON.parse(poi.metadata);
3396
+ mattertagData.setNormal(tagMetadata.normal || { x: 0, y: -0.15, z: 0 });
3397
+ }
3398
+ catch (e) {
3399
+ mattertagData.setNormal({ x: 0, y: -0.15, z: 0 });
3400
+ }
2394
3401
  }
2395
- }
2396
- onEvent(eventType, _eventData) {
2397
- if (eventType === 'repaint') {
2398
- this.repaint();
3402
+ if (poi.matterportSweepID) {
3403
+ mattertagData.setSweepID(poi.matterportSweepID);
2399
3404
  }
3405
+ mattertagData.setPoi(poi);
3406
+ const createdTagID = await this.addMattertagToViewer(sdk, mattertagData);
3407
+ if (createdTagID && sdk) {
3408
+ await wait(100);
3409
+ await setTagIconAndOpacity(createdTagID, mattertagData);
3410
+ injectHtmlInTag(tagType, object, createdTagID).catch(err => {
3411
+ if (isDevMode())
3412
+ console.warn('HTML injection failed for', createdTagID, err);
3413
+ });
3414
+ }
3415
+ return createdTagID;
2400
3416
  }
2401
- onDestroy() {
2402
- this.outputs.texture.dispose();
2403
- this.outputs.texture = null;
2404
- }
2405
- resize(size) {
2406
- this.canvas.width = size.w;
2407
- this.canvas.height = size.h;
3417
+ /** Updates the icon, opacity, and HTML content of an existing tag. */
3418
+ async updateMatterTagContentForTagID(sdk, mattertagID, object, poiType, setTagIconAndOpacity, injectHtmlInTag) {
3419
+ const mattertagData = this.dictionnaryTags.get(mattertagID);
3420
+ if (!mattertagData)
3421
+ return;
3422
+ if (object) {
3423
+ mattertagData.setObject(object, poiType);
3424
+ }
3425
+ await setTagIconAndOpacity(mattertagID, mattertagData);
3426
+ await injectHtmlInTag(poiType, object, mattertagID);
2408
3427
  }
2409
- repaint() {
2410
- if (this.inputs.painter) {
2411
- this.inputs.painter.paint(this.renderContext2D, this.inputs.textureRes);
2412
- this.outputs.texture.needsUpdate = true;
3428
+ /** Registers a custom texture and applies it to a tag, optionally adjusting opacity. */
3429
+ async addNewIconAndSetForTag(sdk, mattertagID, iconPath, applyOpacity, opacity) {
3430
+ if (!sdk)
3431
+ return;
3432
+ try {
3433
+ const iconName = `custom-icon-${mattertagID}`;
3434
+ await sdk.Asset.registerTexture(iconName, iconPath);
3435
+ await sdk.Tag.editIcon(mattertagID, iconName);
3436
+ if (applyOpacity) {
3437
+ await sdk.Tag.editOpacity(mattertagID, opacity);
3438
+ }
3439
+ }
3440
+ catch (error) {
3441
+ if (isDevMode())
3442
+ console.warn('Error setting custom icon or opacity', error);
2413
3443
  }
2414
3444
  }
2415
- }
2416
- const canvasRendererType = 'mp.canvasRenderer';
2417
- function makeCanvasRenderer() {
2418
- return new CanvasRenderer();
2419
- }
2420
-
2421
- const HoverEvent = 'hover';
2422
- const UnhoverEvent = 'unhover';
2423
- class TvPlayer extends SceneComponent {
2424
- rootScene = null;
2425
- mesh = null;
2426
- component;
2427
- inputs = {
2428
- loadingState: 'Idle',
2429
- texture: null,
2430
- updateInterval: 1000,
2431
- };
2432
- events = {
2433
- [HoverEvent]: true,
2434
- [UnhoverEvent]: true,
2435
- };
2436
- intervalVideoTexture;
2437
- onInit(modelNode) {
2438
- if (!this.context) {
2439
- this.context = {
2440
- root: modelNode
2441
- };
3445
+ /** Finds the tag ID and sweep ID associated with a specific element ID. */
3446
+ getTagFromElementId(elementID, sweeps) {
3447
+ let tagID = null;
3448
+ let sweepID = null;
3449
+ for (let [mattertagID, mattertagData] of this.dictionnaryTags) {
3450
+ if (mattertagData.elementID === elementID) {
3451
+ tagID = mattertagID;
3452
+ const sweep = mattertagData.getSweepID();
3453
+ if (sweep && sweeps && sweeps.includes(sweep)) {
3454
+ sweepID = sweep;
3455
+ }
3456
+ }
2442
3457
  }
2443
- const root = this.context.root;
2444
- const THREE = this.context.three;
3458
+ return { tag: tagID, sweep: sweepID };
2445
3459
  }
2446
- onDestroy() {
2447
- super.onDestroy();
2448
- clearInterval(this.intervalVideoTexture);
3460
+ /** Retrieves the floor identifier associated with a tag based on its sweep data.
3461
+ * Handles both the object form ({ floorInfo: { id, sequence }, floor: { id } })
3462
+ * and the primitive form (floor: number) used by Matterport SDK v3.
3463
+ */
3464
+ getFloorIdForTag(mattertagData, sweepCollection) {
3465
+ const sweepId = mattertagData.getSweepID();
3466
+ if (!sweepId || !sweepCollection)
3467
+ return null;
3468
+ const sweepData = sweepCollection[sweepId];
3469
+ if (!sweepData)
3470
+ return null;
3471
+ // SDK v3: sweep.floor is a primitive number (the sequence index)
3472
+ if (typeof sweepData.floor === 'number') {
3473
+ return sweepData.floor.toString();
3474
+ }
3475
+ // Older / extended formats
3476
+ return sweepData?.floorInfo?.id
3477
+ ?? sweepData?.floorInfo?.sequence?.toString()
3478
+ ?? sweepData?.floorId
3479
+ ?? sweepData?.floor?.id
3480
+ ?? sweepData?.floor?.sequence?.toString()
3481
+ ?? null;
2449
3482
  }
2450
- onInputsUpdated() {
2451
- if (this.inputs.loadingState === 'Loaded') {
2452
- const lines = [];
2453
- //@ts-ignore
2454
- this.context.root.obj3D.traverse((obj) => {
2455
- // we dont want line segments, track them and remove them.
2456
- if (obj.type === 'LineSegments') {
2457
- lines.push(obj);
3483
+ /**
3484
+ * Injects custom HTML or fallback billboard media into a Mattertag sandbox.
3485
+ * Attaches event listeners for interactive elements within the tag.
3486
+ */
3487
+ async injectHtmlInTag(sdk, tagType, object, tagID) {
3488
+ if (!this.tagService) {
3489
+ console.error('[MatterportTagService] tagService is not initialized.');
3490
+ return;
3491
+ }
3492
+ try {
3493
+ let html = await this.tagService.getHtmlToInject(tagType, object);
3494
+ if (html && html.trim().length > 0 && sdk) {
3495
+ const scriptTag = this.tagService.getScriptForTag(object, tagType);
3496
+ html += `${scriptTag}`;
3497
+ const [sandboxId, messenger] = await sdk.Tag.registerSandbox(html);
3498
+ const attachmentID = this.tagsAttachments[tagID];
3499
+ if (attachmentID) {
3500
+ try {
3501
+ await sdk.Tag.detach(tagID, attachmentID);
3502
+ }
3503
+ catch (e) { }
2458
3504
  }
2459
- else if (obj.type === 'Mesh') {
2460
- this.mesh = obj;
2461
- let video, videoImage, videoImageContext, videoTexture;
2462
- // create the video element
2463
- video = document.createElement('video');
2464
- // video.src = "./../assets/video/video_samsic.mp4";
2465
- video.src = "./../assets/video/121222-04.mp4";
2466
- video.load(); // must call after setting/changing source
2467
- video.loop = true;
2468
- video.play();
2469
- videoImage = document.createElement('canvas');
2470
- // videoImage.width = 640;
2471
- videoImage.width = 1280;
2472
- // videoImage.height = 360;
2473
- videoImage.height = 720;
2474
- videoImageContext = videoImage.getContext('2d');
2475
- // background color if no video present
2476
- videoImageContext.fillStyle = '#000000';
2477
- videoImageContext.fillRect(0, 0, videoImage.width, videoImage.height);
2478
- videoTexture = new Texture(videoImage);
2479
- videoTexture.minFilter = LinearFilter;
2480
- videoTexture.magFilter = LinearFilter;
2481
- const movieMaterial = new MeshBasicMaterial({ map: videoTexture });
2482
- this.mesh.material = movieMaterial;
2483
- const render = () => {
2484
- if (video.readyState === video.HAVE_ENOUGH_DATA) {
2485
- videoImageContext.drawImage(video, 0, 0);
2486
- if (videoTexture)
2487
- videoTexture.needsUpdate = true;
2488
- }
2489
- const camera = (this.context.scene.children.find(u => u.constructor.name === 'CameraRig')).camera;
2490
- this.context.renderer.render(this.context.scene, camera);
2491
- };
2492
- this.intervalVideoTexture = setInterval(() => {
2493
- render();
2494
- }, 180);
3505
+ this.tagsAttachments[tagID] = sandboxId;
3506
+ await sdk.Tag.attach(tagID, sandboxId);
3507
+ // Attach the "View informations" handler in tooltip
3508
+ messenger.on(TagAction.DETAIL_CLICK, this.tagService.onActionDetailClick.bind(this.tagService));
3509
+ messenger.on(TagAction.TICKET_CLICK, this.tagService.onActionDetailClick.bind(this.tagService));
3510
+ messenger.on(TagAction.AUDIO_CLICK, (audioCommentID) => this.tagService.onActionAudioClick(audioCommentID));
3511
+ messenger.on(TagAction.VIDEO_CLICK, (videoUrl) => this.tagService.onActionVideoClick(videoUrl));
3512
+ messenger.on(TagAction.IMAGE_CLICK, (imageCommentID) => this.tagService.onActionImageClick(imageCommentID));
3513
+ messenger.on(TagAction.DOC_CLICK, (docUrl) => this.tagService.onActionDocClick(docUrl));
3514
+ messenger.on(TagAction.YOUTUBE_CLICK, (youtubeUrl) => this.tagService.onActionYoutubeClick(youtubeUrl));
3515
+ }
3516
+ else if (sdk) {
3517
+ const { comment, tagDescription } = this.tagService.getBillboardMediaToEmbed(object);
3518
+ if (comment) {
3519
+ const attachmentID = await sdk.Tag.registerAttachment({
3520
+ type: 'media',
3521
+ data: {
3522
+ url: comment.externalLink,
3523
+ label: object.title || 'Media',
3524
+ },
3525
+ });
3526
+ await sdk.Tag.attach(tagID, attachmentID);
3527
+ const billboardAttachmentID = await sdk.Tag.registerAttachment({
3528
+ type: 'billboard',
3529
+ data: {
3530
+ title: object.title,
3531
+ description: tagDescription,
3532
+ },
3533
+ });
3534
+ await sdk.Tag.attach(tagID, billboardAttachmentID);
3535
+ this.tagsAttachments[tagID] = billboardAttachmentID;
2495
3536
  }
2496
- });
2497
- // remove the line segments.
2498
- lines.forEach((line) => {
2499
- line.parent.remove(line);
2500
- });
3537
+ }
3538
+ }
3539
+ catch (error) {
3540
+ console.error('[MatterportTagService] Error in injectHtmlInTag:', error);
2501
3541
  }
2502
3542
  }
2503
- setComponent(component) {
2504
- this.component = component;
2505
- this.context = component.context;
2506
- }
2507
- setRootScene(rootScene) {
2508
- this.rootScene = rootScene;
3543
+ /** Clears all local tag data and resets the addition queue. */
3544
+ clear() {
3545
+ this.dictionnaryTags.clear();
3546
+ this.tagAddQueue = Promise.resolve();
3547
+ this.mattertagToFollow = null;
3548
+ this.mattertagIDs = [];
2509
3549
  }
3550
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MatterportTagService, deps: [{ token: MatterportNavigationService }], target: i0.ɵɵFactoryTarget.Injectable });
3551
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MatterportTagService, providedIn: 'root' });
2510
3552
  }
2511
- const TvPlayerType = 'mp.TvPlayer';
2512
- const makeTvPlayer = function () {
2513
- return new TvPlayer();
2514
- };
2515
-
2516
- class BaseVisibilityService {
2517
- detailShowing = new Subject();
2518
- isChangePositionVisible = new Subject();
2519
- constructor() { }
2520
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: BaseVisibilityService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2521
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: BaseVisibilityService, providedIn: 'root' });
2522
- }
2523
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: BaseVisibilityService, decorators: [{
3553
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MatterportTagService, decorators: [{
2524
3554
  type: Injectable,
2525
3555
  args: [{
2526
3556
  providedIn: 'root',
2527
3557
  }]
2528
- }], ctorParameters: () => [] });
3558
+ }], ctorParameters: () => [{ type: MatterportNavigationService }] });
2529
3559
 
2530
3560
  class Config {
2531
3561
  my_config;
@@ -2537,85 +3567,154 @@ class MatterportService {
2537
3567
  activeRoute;
2538
3568
  visibilityService;
2539
3569
  ngZone;
2540
- slots = []; //SlotNode[] = [];
2541
- nodes = [];
3570
+ measurementService;
3571
+ navigationService;
3572
+ pointerService;
3573
+ object3DService;
3574
+ matterportTagService;
2542
3575
  sdk;
2543
3576
  container;
2544
3577
  // Position camera
2545
3578
  poseMatterport;
2546
3579
  poseCamera;
2547
3580
  lastCameraPosition = { x: 0.0, y: 0.0, z: 0.0 };
2548
- azimuthalCrown;
2549
- // Pointer trick
2550
- pointerButton;
2551
- // Display and get current position of cursor (for Admins only)
2552
- getCursorPositionButton;
2553
- cursorPositionButtonDisplayed = false;
2554
- intervalCursorPointerPosition;
2555
- textDisplayCursorPositionPanel;
2556
- oldPoseMatterportPosition;
2557
- // Measure mode
2558
3581
  isMeasureModeOn = false;
2559
3582
  interactionMode = ViewerInteractions.DEFAULT;
2560
- // List of created Mattertag IDs in the current viewer session
2561
- mattertagIDs = [];
2562
- // Dictionnary of MattertagID and its data (mattertagData)
2563
- dictionnaryTags = new Map();
2564
- dictionnaryObjects3D = new Map();
2565
- dictionnarySceneObjects3D = new Map();
2566
- threeJSScene; // global root scene (not used now!)
2567
- lastMeasure = [];
2568
- distancesLastMeasure = [];
2569
- resolution = {
2570
- width: 500,
2571
- height: 600,
2572
- };
2573
- visibility = {
2574
- mattertags: false,
2575
- sweeps: true,
2576
- };
2577
- tagsAttachments = {};
2578
- lastScreenshotUri;
2579
- sweeps;
2580
- currentSweep = new Subject();
3583
+ cameraMode = 'INSIDE';
3584
+ get mattertagIDs() {
3585
+ return this.matterportTagService.mattertagIDs;
3586
+ }
3587
+ set mattertagIDs(value) {
3588
+ this.matterportTagService.mattertagIDs = value;
3589
+ }
3590
+ get dictionnaryTags() {
3591
+ return this.matterportTagService.dictionnaryTags;
3592
+ }
3593
+ get dictionnaryObjects3D() {
3594
+ return this.object3DService.dictionnaryObjects3D;
3595
+ }
3596
+ get dictionnarySceneObjects3D() {
3597
+ return this.object3DService.dictionnarySceneObjects3D;
3598
+ }
3599
+ get threeJSScene() {
3600
+ return this.object3DService.threeJSScene;
3601
+ }
3602
+ set threeJSScene(value) {
3603
+ this.object3DService.threeJSScene = value;
3604
+ }
3605
+ get azimuthalCrown() {
3606
+ return this.object3DService.azimuthalCrown;
3607
+ }
3608
+ set azimuthalCrown(value) {
3609
+ this.object3DService.azimuthalCrown = value;
3610
+ }
3611
+ get tagsAttachments() {
3612
+ return this.matterportTagService.tagsAttachments;
3613
+ }
3614
+ get sweeps() {
3615
+ return this.navigationService.sweeps;
3616
+ }
3617
+ set sweeps(value) {
3618
+ this.navigationService.sweeps = value;
3619
+ }
3620
+ get sweepCollection() {
3621
+ return this.navigationService.sweepCollection;
3622
+ }
3623
+ set sweepCollection(value) {
3624
+ this.navigationService.sweepCollection = value;
3625
+ }
3626
+ get currentSweep() {
3627
+ return this.navigationService.currentSweep;
3628
+ }
2581
3629
  //camera position with rotation
2582
3630
  currentCameraPose = new Subject();
2583
- floors;
3631
+ get floors() {
3632
+ return this.navigationService.floors;
3633
+ }
3634
+ set floors(value) {
3635
+ this.navigationService.floors = value;
3636
+ }
2584
3637
  _currentSpaceID;
2585
- lastObject3D; // Three JS object
2586
- currentFloor;
3638
+ get lastObject3D() {
3639
+ return this.object3DService.lastObject3D;
3640
+ }
3641
+ set lastObject3D(value) {
3642
+ this.object3DService.lastObject3D = value;
3643
+ }
3644
+ get currentRooms() {
3645
+ return this.navigationService.currentRooms;
3646
+ }
3647
+ set currentRooms(value) {
3648
+ this.navigationService.currentRooms = value;
3649
+ }
2587
3650
  get currentSpaceID() {
2588
3651
  return this._currentSpaceID;
2589
3652
  }
2590
3653
  set currentSpaceID(value) {
2591
3654
  this._currentSpaceID = value;
2592
3655
  }
2593
- timerPointer;
2594
- forbiddenSweeps = [];
3656
+ get forbiddenSweeps() {
3657
+ return this.navigationService.forbiddenSweeps;
3658
+ }
3659
+ set forbiddenSweeps(value) {
3660
+ this.navigationService.forbiddenSweeps = value;
3661
+ }
2595
3662
  tagAction = new Subject();
2596
- mattertagToFollow;
3663
+ get mattertagToFollow() {
3664
+ return this.matterportTagService.mattertagToFollow;
3665
+ }
3666
+ set mattertagToFollow(value) {
3667
+ this.matterportTagService.mattertagToFollow = value;
3668
+ }
3669
+ get inTransitionMode() {
3670
+ return this.navigationService.inTransitionMode;
3671
+ }
3672
+ set inTransitionMode(value) {
3673
+ this.navigationService.inTransitionMode = value;
3674
+ }
3675
+ get inTransitionSweep() {
3676
+ return this.navigationService.inTransitionSweep;
3677
+ }
3678
+ set inTransitionSweep(value) {
3679
+ this.navigationService.inTransitionSweep = value;
3680
+ }
3681
+ noLightForObjects = true;
3682
+ get currentCameraMode() {
3683
+ return this.navigationService.currentCameraMode;
3684
+ }
3685
+ set currentCameraMode(value) {
3686
+ this.navigationService.currentCameraMode = value;
3687
+ }
3688
+ get onCameraModeChanged() {
3689
+ return this.navigationService.onCameraModeChanged;
3690
+ }
3691
+ get onRoomChanged() {
3692
+ return this.navigationService.onRoomChanged;
3693
+ }
3694
+ get onGoToTag() {
3695
+ return this.matterportTagService.onGoToTag;
3696
+ }
2597
3697
  tagService;
2598
3698
  config;
2599
- inTransitionMode = false;
2600
- inTransitionSweep = false;
2601
- noLightForObjects = true;
2602
- currentCameraMode = CameraMode.OUTSIDE;
2603
- onCameraModeChanged = new Subject();
2604
- onGoToTag = new Subject();
2605
- tagMessengerOn = false;
2606
3699
  SPModule;
2607
- objectControl;
2608
- securityCameraAnimator;
3700
+ get objectControl() {
3701
+ return this.object3DService.objectControl;
3702
+ }
3703
+ get securityCameraAnimator() {
3704
+ return this.object3DService.securityCameraAnimator;
3705
+ }
2609
3706
  /**
2610
3707
  * Actions on left click when positioning mattertag in visit
2611
3708
  */
2612
- pointerLeftClickHandler = () => {
3709
+ pointerLeftClickHandler = (evt) => {
2613
3710
  if (this.mattertagToFollow) {
2614
3711
  const mattertagData = this.dictionnaryTags.get(this.mattertagToFollow);
2615
- mattertagData.setPosition({ ...this.poseMatterport.position }); // copy!! not the reference
2616
- mattertagData.setNormal({ ...this.poseMatterport.normal }); // copy!! not the reference
2617
- this.dictionnaryTags.set(this.mattertagToFollow, mattertagData);
2618
- this.updateMatterTagContentForTagID(this.mattertagToFollow);
3712
+ if (mattertagData) {
3713
+ mattertagData.setPosition({ ...this.poseMatterport.position });
3714
+ mattertagData.setNormal({ ...this.poseMatterport.normal });
3715
+ this.dictionnaryTags.set(this.mattertagToFollow, mattertagData);
3716
+ this.updateMatterTagContentForTagID(this.mattertagToFollow);
3717
+ }
2619
3718
  }
2620
3719
  this.onValidatedMattertag();
2621
3720
  };
@@ -2625,52 +3724,25 @@ class MatterportService {
2625
3724
  alert('action cancelled');
2626
3725
  };
2627
3726
  pointerMiddleClickHandler = (e) => {
2628
- this.textDisplayCursorPositionPanel.innerHTML = `position:
2629
- ${this.pointToString(this.poseMatterport.position)}\n
2630
- normal:
2631
- ${this.pointToString(this.poseMatterport.normal)}\n
2632
- floorId:
2633
- ${this.poseMatterport.floorId}`;
2634
- // this.textDisplayCursorPositionPanel.style.display = 'visible';
2635
- this.getCursorPositionButton.style.display = 'none';
3727
+ this.pointerService.updateCursorDisplay(this.poseMatterport);
2636
3728
  };
2637
- constructor(config, router, activeRoute, visibilityService, ngZone) {
3729
+ constructor(config, router, activeRoute, visibilityService, ngZone, measurementService, navigationService, pointerService, object3DService, matterportTagService) {
2638
3730
  this.router = router;
2639
3731
  this.activeRoute = activeRoute;
2640
3732
  this.visibilityService = visibilityService;
2641
3733
  this.ngZone = ngZone;
3734
+ this.measurementService = measurementService;
3735
+ this.navigationService = navigationService;
3736
+ this.pointerService = pointerService;
3737
+ this.object3DService = object3DService;
3738
+ this.matterportTagService = matterportTagService;
2642
3739
  this.config = config;
2643
- // TODO: only for dev!
2644
- if (!!this.getCursorPositionButton &&
2645
- (document.location.href.indexOf('dev') !== -1 || document.location.href.indexOf('localhost') !== -1)) {
2646
- this.intervalCursorPointerPosition = setInterval(() => {
2647
- if (!this.poseMatterport) {
2648
- return;
2649
- }
2650
- const nextShow = this.poseMatterport.time + 1000;
2651
- if (new Date().getTime() > nextShow) {
2652
- if (this.cursorPositionButtonDisplayed) {
2653
- return;
2654
- }
2655
- const size = {
2656
- w: this.container.clientWidth,
2657
- h: this.container.clientHeight,
2658
- };
2659
- const coord = this.sdk.Conversion.worldToScreen(this.poseMatterport.position, this.poseCamera, size);
2660
- this.getCursorPositionButton.style.left = `${coord.x - 25}px`;
2661
- this.getCursorPositionButton.style.top = `${coord.y - 22}px`;
2662
- this.getCursorPositionButton.style.display = 'block';
2663
- // this.textDisplayCursorPositionPanel.style.display = 'none';
2664
- this.cursorPositionButtonDisplayed = true;
2665
- }
2666
- }, 500);
2667
- }
2668
3740
  }
2669
3741
  /**
2670
- * Initializes Matterport and all listeners/data
2671
- * @param tagService BaseTagService (to inject html)
2672
- * @param module SpModule (Museum, Immo) to subscribe only to needed functionnnalities
2673
- * @returns boolean
3742
+ * Initializes the Matterport SDK, sets up event listeners, and configures assets.
3743
+ * @param tagService Service used to generate and inject HTML into tags.
3744
+ * @param module Application module (e.g., IMMO, MUSEUM) to enable specific features.
3745
+ * @returns Promise resolving to true when initialization is complete.
2674
3746
  */
2675
3747
  async initSdk(tagService, module = SpModule.IMMO) {
2676
3748
  if (this.sdk) {
@@ -2678,39 +3750,35 @@ class MatterportService {
2678
3750
  await this.action_delete_all_mattertags();
2679
3751
  }
2680
3752
  this.tagService = tagService;
3753
+ this.matterportTagService.setTagService(tagService);
2681
3754
  this.SPModule = module;
2682
3755
  return new Promise((resolve, reject) => {
2683
3756
  // Retrieve DOM elements
2684
- this.pointerButton = document.querySelector('#viewer-pointer-trick');
3757
+ const pointerBtn = document.querySelector('#viewer-pointer-trick');
2685
3758
  this.container = document.querySelector('#viewer-module');
2686
3759
  const iframe = document.querySelector('#viewer-module');
2687
3760
  if (!iframe) {
2688
3761
  return;
2689
3762
  }
2690
- // Add listeners
2691
- if (this.pointerButton) {
2692
- this.pointerButton.addEventListener('click', this.pointerLeftClickHandler);
2693
- // cancel on right click
2694
- this.pointerButton.addEventListener('contextmenu', this.pointerRightClickHandler);
2695
- }
2696
- // Retrieve DOM elements
2697
- this.getCursorPositionButton = document.querySelector('#button');
2698
- this.textDisplayCursorPositionPanel = document.querySelector('#text');
2699
- if (this.getCursorPositionButton) {
2700
- // get position on Matterport "model" on middle click
2701
- this.getCursorPositionButton.addEventListener('click', this.pointerMiddleClickHandler);
2702
- }
3763
+ const cursorBtn = document.querySelector('#button');
3764
+ const textPanel = document.querySelector('#text');
3765
+ this.pointerService.init(this.container, pointerBtn, cursorBtn, textPanel, {
3766
+ onLeftClick: this.pointerLeftClickHandler.bind(this),
3767
+ onRightClick: this.pointerRightClickHandler.bind(this),
3768
+ onMiddleClick: this.pointerMiddleClickHandler.bind(this),
3769
+ });
2703
3770
  // Load SDK
2704
- console.log('Loading Matterport SDK');
2705
3771
  const showcaseWindow = iframe.contentWindow;
2706
- iframe.addEventListener('load', async function () {
3772
+ iframe.addEventListener('load', async () => {
2707
3773
  try {
2708
- this.sdk = await showcaseWindow.MP_SDK.connect(showcaseWindow, 'qn9wsasuy5h2fzrbrn1nzr0id', '3.5');
3774
+ this.sdk = await showcaseWindow.MP_SDK.connect(showcaseWindow, 'qn9wsasuy5h2fzrbrn1nzr0id', '');
2709
3775
  }
2710
3776
  catch (e) {
2711
3777
  console.error(e);
2712
3778
  return;
2713
3779
  }
3780
+ this.addCustomCss();
3781
+ this.pointerService.startAdminCursorTracker(this.sdk, this.container, () => this.poseMatterport, () => this.poseCamera);
2714
3782
  // Load Mattertag icons and custom subscriptions
2715
3783
  switch (module) {
2716
3784
  case SpModule.IMMO:
@@ -2721,17 +3789,11 @@ class MatterportService {
2721
3789
  this.sdk.Asset.registerTexture('icon-object3d', this.config.my_config.icon_object3d);
2722
3790
  this.sdk.Measurements.data.subscribe({
2723
3791
  onAdded: function (index, item, collection) {
2724
- // console.log(
2725
- // "item added to the collection",
2726
- // index,
2727
- // item,
2728
- // );
2729
- // this.measurements[index] = item;
2730
- this.lastMeasure = item.points;
3792
+ this.measurementService.setLastMeasure(item.points);
2731
3793
  }.bind(this),
2732
3794
  onCollectionUpdated: function (collection) {
2733
3795
  // console.log('the entire up-to-date collection', collection);
2734
- this.getDistanceForLastMeasurement();
3796
+ this.measurementService.getDistanceForLastMeasurement(this.sdk, this.currentSpaceID);
2735
3797
  }.bind(this),
2736
3798
  });
2737
3799
  this.sdk.Measurements.mode.subscribe(function (measurementModeState) {
@@ -2755,7 +3817,11 @@ class MatterportService {
2755
3817
  this.sdk.Asset.registerTexture('icon-position', this.config.my_config.icon_position);
2756
3818
  // Current Room (used for getting bounding box and eventually lazy load mattertag or object3D inside current Room)
2757
3819
  this.sdk.Room.current.subscribe((currentRoom) => {
2758
- console.log(`hello current Room ${JSON.stringify(currentRoom)}`);
3820
+ const rooms = Array.isArray(currentRoom) ? currentRoom : (currentRoom ? [currentRoom] : []);
3821
+ this.currentRooms = rooms;
3822
+ this.navigationService.onRoomChanged.next(rooms);
3823
+ // Refresh tag visibility whenever the room context changes
3824
+ this.refreshTagsVisibility();
2759
3825
  });
2760
3826
  // 3D position's pointer
2761
3827
  this.sdk.Pointer.intersection.subscribe(function (intersection) {
@@ -2768,64 +3834,41 @@ class MatterportService {
2768
3834
  z: 0,
2769
3835
  };
2770
3836
  }
2771
- if (this.interactionMode !== ViewerInteractions.DEFAULT &&
2772
- this.mattertagToFollow) {
3837
+ if (this.interactionMode !== ViewerInteractions.DEFAULT && this.mattertagToFollow) {
2773
3838
  // follow the pointer and changes the position of the last tag
2774
3839
  // (we are about to validate, but it exists in sdk already)
2775
3840
  this.enable_following_tag(this.mattertagToFollow);
2776
3841
  }
2777
- if (!!this.getCursorPositionButton && !!this.getCursorPositionButton.style &&
2778
- (document.URL.indexOf('https://dev.smarterplan.io') !== -1 || document.location.href.indexOf('localhost') !== -1)) {
2779
- this.getCursorPositionButton.style.display = 'none';
2780
- this.cursorPositionButtonDisplayed = false;
3842
+ if (this.pointerService) {
3843
+ this.pointerService.setCursorPositionButtonDisplayed(false);
2781
3844
  }
2782
3845
  }.bind(this));
2783
3846
  //Camera mode
2784
3847
  this.sdk.Mode.current.subscribe((mode) => {
2785
- this.inTransitionSweep = false;
2786
- switch (mode) {
2787
- case 'mode.dollhouse':
2788
- this.currentCameraMode = CameraMode.DOLLHOUSE;
2789
- break;
2790
- case 'mode.floorplan':
2791
- this.currentCameraMode = CameraMode.FLOORPLAN;
2792
- break;
2793
- case 'mode.inside':
2794
- this.currentCameraMode = CameraMode.INSIDE;
2795
- break;
2796
- case 'mode.transitioning':
2797
- this.currentCameraMode = CameraMode.TRANSITIONING;
2798
- break;
2799
- default:
2800
- this.currentCameraMode = CameraMode.OUTSIDE;
3848
+ if (this.cameraMode !== mode) {
3849
+ this.navigationService.setCameraMode(mode);
3850
+ // Refresh tag visibility on every mode change (INSIDE vs DOLLHOUSE vs FLOORPLAN)
3851
+ this.refreshTagsVisibility();
3852
+ this.cameraMode = mode;
2801
3853
  }
2802
- this.onCameraModeChanged.next(this.currentCameraMode);
2803
3854
  });
2804
- // Camera's viewpoint
2805
- this.sdk.Camera.pose.subscribe(function subscr(pose) {
2806
- this.poseCamera = pose;
2807
- this.currentCameraPose.next(pose);
2808
- // console.log('Current position is ', pose.position);
2809
- // console.log('Rotation angle is ', pose.rotation);
2810
- // console.log("Sweep UUID is", pose.sweep);
2811
- }.bind(this));
3855
+ setTimeout(() => {
3856
+ if (this.sdk && this.poseCamera) {
3857
+ this.matterportTagService.updateTagsVisibilityForContext(this.sdk, this.sweepCollection, this.navigationService.currentCameraMode, this.mattertagToFollow, this.navigationService.currentFloorSequence, this.navigationService.currentRooms);
3858
+ }
3859
+ }, 1000);
2812
3860
  // subscribe to sweeps
2813
3861
  this.sdk.Sweep.data.subscribe({
2814
3862
  onCollectionUpdated: function subscr(collection) {
2815
- // console.log("Sweep collection updated");
2816
3863
  this.sweeps = Object.keys(collection);
3864
+ this.sweepCollection = collection;
2817
3865
  }.bind(this),
2818
3866
  });
2819
3867
  // subscribe to current sweep
2820
3868
  this.sdk.Sweep.current.subscribe(function subscr(currentSweep) {
2821
- // Change to the current sweep has occurred.
2822
- if (currentSweep.sid === '') {
2823
- // console.log('Not currently stationed at a sweep position');
2824
- }
2825
- else {
2826
- // console.log("emmiting sweep", currentSweep.sid);
2827
- this.currentSweep.next(currentSweep.sid);
2828
- }
3869
+ if (currentSweep.sid === '')
3870
+ return;
3871
+ this.currentSweep.next(currentSweep.sid);
2829
3872
  }.bind(this));
2830
3873
  // Subscribe to Floor data
2831
3874
  this.sdk.Floor.data.subscribe({
@@ -2833,25 +3876,23 @@ class MatterportService {
2833
3876
  this.floors = Object.values(collection);
2834
3877
  }.bind(this),
2835
3878
  });
2836
- // Handler générique pour les sélections de tags
2837
- const handleTagSelection = async (sid, source, evt) => {
3879
+ // Subscribe to current floor authoritative source for multi-floor filtering
3880
+ this.sdk.Floor.current.subscribe((currentFloor) => {
3881
+ const seq = currentFloor?.sequence ?? null;
3882
+ this.navigationService.currentFloorSequence = seq;
2838
3883
  if (isDevMode()) {
2839
- console.log(`[MatterportService] handleTagSelection from ${source} for sid: ${sid}`, { evt });
3884
+ console.log('[Floor.current] sequence =', seq, '| full =', currentFloor);
2840
3885
  }
2841
- // Exécution de l'action personnalisée (ouverture du panneau de détail via navigation)
3886
+ this.refreshTagsVisibility();
3887
+ });
3888
+ // Generic handler for tag selection events
3889
+ const handleTagSelection = async (sid, source, evt) => {
2842
3890
  try {
2843
3891
  const mattertagData = this.dictionnaryTags.get(sid);
2844
- if (!mattertagData) {
2845
- if (isDevMode()) {
2846
- console.log('no mattertagData to display', sid);
2847
- }
3892
+ if (!mattertagData)
2848
3893
  return;
2849
- }
2850
3894
  const url = tagService.getUrlForSeeDetails(mattertagData.getObject(), mattertagData.getType());
2851
3895
  if (url !== '') {
2852
- if (isDevMode()) {
2853
- console.log(`[MatterportService] Navigating to detail URL: ${url}`);
2854
- }
2855
3896
  this.visibilityService.detailShowing.next(true);
2856
3897
  this.ngZone.run(() => {
2857
3898
  this.router.navigate([url]);
@@ -2859,48 +3900,39 @@ class MatterportService {
2859
3900
  }
2860
3901
  }
2861
3902
  catch (error) {
2862
- if (isDevMode()) {
2863
- console.log('Cannot show details for tag', error);
2864
- }
3903
+ if (isDevMode())
3904
+ console.warn('Cannot show details for tag', error);
2865
3905
  }
2866
3906
  };
2867
- // Fonction pour désactiver les actions natives sur un tag (SDK v3)
3907
+ // Disable native Matterport actions
2868
3908
  const disableNativeActions = (sid) => {
2869
- if (!this.sdk)
2870
- return;
3909
+ if (!this.sdk || sid === this.mattertagToFollow)
3910
+ return; // ignore when we are in positioning mode
2871
3911
  try {
2872
- if (this.sdk.Tag && this.sdk.Tag.allowAction) {
2873
- // opening: false désactive le billboard natif
2874
- // navigating: false désactive le déplacement de la caméra vers le tag
2875
- this.sdk.Tag.allowAction(sid, { opening: false, navigating: true, docking: false });
2876
- /*if (isDevMode()) {
2877
- console.log(`[MatterportService] Disabled native actions for tag ${sid}`);
2878
- }*/
2879
- }
2880
- else if (this.sdk.Mattertag && this.sdk.Mattertag.preventAction) {
2881
- // Fallback ancienne API
2882
- this.sdk.Mattertag.preventAction(sid, { opening: true, navigating: true });
2883
- if (isDevMode()) {
2884
- console.log(`[MatterportService] Prevented native actions (legacy) for tag ${sid}`);
2885
- }
3912
+ if (this.sdk.Tag?.allowAction) {
3913
+ this.sdk.Tag.allowAction(sid, { opening: true, navigating: true, docking: false });
2886
3914
  }
2887
3915
  }
2888
3916
  catch (e) {
2889
- if (isDevMode()) {
2890
- console.warn(`[MatterportService] Error disabling actions for ${sid}`, e);
3917
+ // silently ignore already removed tags
3918
+ if (isDevMode() && !e?.message?.includes('not found')) {
3919
+ console.warn('MatterportService: Error disabling actions for', sid, e);
2891
3920
  }
2892
3921
  }
2893
3922
  };
2894
- // Attendre que l'application soit en phase PLAYING
3923
+ // Wait for the application to reach PLAYING phase
2895
3924
  if (this.sdk.App && this.sdk.App.state) {
2896
- if (isDevMode()) {
2897
- console.log('[MatterportService] Waiting for App.Phase.PLAYING...');
2898
- }
2899
3925
  this.sdk.App.state.waitUntil((s) => s.phase === this.sdk.App.Phase.PLAYING).then(() => {
2900
- if (isDevMode()) {
2901
- console.log('[MatterportService] App is PLAYING.');
3926
+ this.sdk.Camera.pose.subscribe((pose) => {
3927
+ this.poseCamera = pose;
3928
+ this.matterportTagService.updateTagsVisibilityForContext(this.sdk, this.sweepCollection, this.navigationService.currentCameraMode, this.mattertagToFollow, this.navigationService.currentFloorSequence, this.navigationService.currentRooms);
3929
+ });
3930
+ setTimeout(() => {
3931
+ this.matterportTagService.updateTagsVisibilityForContext(this.sdk, this.sweepCollection, this.navigationService.currentCameraMode, this.mattertagToFollow, this.navigationService.currentFloorSequence, this.navigationService.currentRooms);
3932
+ }, 1500);
3933
+ if (this.interactionMode === ViewerInteractions.DEFAULT) {
3934
+ this.navigationService.action_toolbox_inside_view(this.sdk);
2902
3935
  }
2903
- // 1. Désactiver les actions natives pour tous les tags existants et futurs
2904
3936
  if (this.sdk.Tag && this.sdk.Tag.data) {
2905
3937
  this.sdk.Tag.data.subscribe({
2906
3938
  onAdded: (sid) => {
@@ -2910,21 +3942,14 @@ class MatterportService {
2910
3942
  disableNativeActions(sid);
2911
3943
  },
2912
3944
  onCollectionUpdated: (collection) => {
2913
- Object.keys(collection).forEach(sid => disableNativeActions(sid));
2914
- }
2915
- });
2916
- }
2917
- // 2. Écouter les sélections via l'observable openTags (Méthode moderne)
2918
- if (this.sdk.Tag && this.sdk.Tag.openTags) {
2919
- this.sdk.Tag.openTags.subscribe({
2920
- onChanged: (newState) => {
2921
- if (newState.selected && newState.selected.size > 0) {
2922
- const sid = Array.from(newState.selected)[0];
2923
- handleTagSelection(sid, 'Tag.openTags.selected');
2924
- }
3945
+ Object.keys(collection).forEach((sid) => disableNativeActions(sid));
2925
3946
  }
2926
3947
  });
2927
3948
  }
3949
+ resolve(true);
3950
+ })
3951
+ .catch((error) => {
3952
+ reject(error);
2928
3953
  });
2929
3954
  }
2930
3955
  // Fallback pour l'événement global tag.click
@@ -2933,224 +3958,118 @@ class MatterportService {
2933
3958
  console.log(`[MatterportService] sdk.on('tag.click') received for sid: ${sid}`);
2934
3959
  }
2935
3960
  handleTagSelection(sid, 'global.tag.click');
2936
- // On s'assure qu'il est bien désactivé même si on a raté l'évènement d'ajout
2937
3961
  disableNativeActions(sid);
2938
3962
  });
2939
3963
  // Pointer trick
2940
3964
  // Create a div that will appear when the cursor is still
2941
3965
  // It will intercept the click of the mouse before it trigerring the Matterport's tour navigation
2942
- this.timerPointer = setInterval(this.updatePointerTrick.bind(this), 50);
3966
+ this.pointerService.startPointerTrick(this.sdk, this.container, () => this.poseMatterport, () => this.poseCamera, () => this.interactionMode, () => this.mattertagToFollow);
2943
3967
  /**
2944
- * Transitions
2945
- */
3968
+ * Transitions
3969
+ */
2946
3970
  this.sdk.on('viewmode.changestart', function (to, from) {
2947
- // console.log('Starting to move to ' + to + ' from ' + from);
2948
- this.inTransitionMode = true;
3971
+ console.debug('Starting to move to ' + to + ' from ' + from);
3972
+ this.navigationService.inTransitionMode = true;
2949
3973
  }.bind(this));
2950
3974
  this.sdk.on('viewmode.changeend', function (oldMode, newMode) {
2951
- // console.log('Ended to move to ' + newMode + ' from ' + oldMode);
3975
+ console.debug('Ended to move to ' + newMode + ' from ' + oldMode);
2952
3976
  if (newMode !== 'mode.transitioning') {
2953
- this.inTransitionMode = false;
3977
+ this.navigationService.inTransitionMode = false;
2954
3978
  }
2955
3979
  }.bind(this));
2956
3980
  this.sdk.on(this.sdk.Sweep.Event.ENTER, function (oldSweep, newSweep) {
2957
- // console.log('Leaving sweep ' + oldSweep);
2958
- // console.log('Entering sweep ' + newSweep);
2959
- this.inTransitionSweep = false;
3981
+ console.debug('Leaving sweep ' + oldSweep);
3982
+ console.debug('Entering sweep ' + newSweep);
3983
+ this.navigationService.inTransitionSweep = false;
2960
3984
  this.displayAzimutalCrown();
3985
+ // Refresh on sweep is removed because we only update on room or floor changes now.
2961
3986
  }.bind(this));
2962
3987
  this.sdk.on(this.sdk.Sweep.Event.EXIT, function (fromSweep, toSweep) {
2963
- // console.log('Leaving sweep ' + fromSweep);
2964
- // console.log('Transitioning to sweep ' + toSweep);
2965
- this.inTransitionSweep = true;
3988
+ console.debug('Leaving sweep ' + fromSweep);
3989
+ console.debug('Transitioning to sweep ' + toSweep);
3990
+ this.navigationService.inTransitionSweep = true;
2966
3991
  }.bind(this));
2967
- // TODO: get scene with getter instead!
2968
- const [sceneObject] = await this.sdk.Scene.createObjects(1);
2969
- const node = sceneObject.addNode();
2970
- node.start();
2971
- this.threeJSScene = node.obj3D.parent;
2972
- // await this.sdk.Scene.configure((renderer: any, three: any) => {
2973
- // renderer.physicallyCorrectLights = true;
2974
- // renderer.outputEncoding = three.sRGBEncoding;
2975
- // renderer.shadowMap.enabled = true;
2976
- // renderer.shadowMap.bias = 0.0001;
2977
- // renderer.shadowMap.type = three.PCFSoftShadowMap;
2978
- // });
2979
- // TODO: wait for MP ticket resolution before decomment these!
2980
- // Wait until Showcase is actually playing....
2981
- // this.sdk.Tag.data.subscribe({
2982
- // onAdded: async function (index, item, collection) {
2983
- //
2984
- // let thisOpacity = 0.2;
2985
- // this.sdk.Tag.editOpacity(index, thisOpacity);
2986
- //
2987
- // let source = null;
2988
- // try {
2989
- // source = await Promise.all([
2990
- // this.sdk.Sensor.createSource(this.sdk.Sensor.SourceType.SPHERE, {
2991
- // origin: item.anchorPosition,
2992
- // radius: Number(3),
2993
- // userData: {
2994
- // id: index + '-sphere-source',
2995
- // },
2996
- // })
2997
- // ]);
2998
- // } catch (e) {
2999
- // console.log('could not create Sphere sensor')
3000
- // console.error(e);
3001
- // }
3002
- // if (!source) {
3003
- // return;
3004
- // }
3005
- //
3006
- // let sensor = null;
3007
- // try {
3008
- // sensor = await this.sdk.Sensor.createSensor(this.sdk.Sensor.SensorType.CAMERA);
3009
- // } catch (e) {
3010
- // console.log('could not create Camera sensor')
3011
- // console.error(e);
3012
- // }
3013
- // if (!sensor) {
3014
- // return;
3015
- // }
3016
- //
3017
- // sensor.addSource(...source);
3018
- // sensor.readings.subscribe({
3019
- // onUpdated(source, reading) {
3020
- // console.log(thisOpacity);
3021
- // let oldOpacity = thisOpacity;
3022
- // if (reading.inRange) {
3023
- // thisOpacity = 1;
3024
- // console.log(index + ' is inRange');
3025
- // } else if (reading.inView) {
3026
- // console.log(index + ' is inView but not inRange');
3027
- // thisOpacity = 0.5;
3028
- // } else {
3029
- // thisOpacity = 0.2;
3030
- // console.log(index + ' is not inView or inRange');
3031
- // }
3032
- //
3033
- // this.sdk.Tag.editOpacity(index, thisOpacity);
3034
- // /*
3035
- // let inc = 0.01;
3036
- // if (oldOpacity > thisOpacity) {
3037
- // inc = -0.01;
3038
- // }
3039
- //
3040
- // for(var i = oldOpacity; i != thisOpacity; i=i+inc) {
3041
- // setTimeout(function() {
3042
- // mpSdk.Tag.editOpacity(index, i);
3043
- // console.log('Delay', i);
3044
- // },10);
3045
- // }
3046
- // */
3047
- // }
3048
- // });
3049
- // //sensor.showDebug(true);
3050
- // }.bind(this),
3051
- // onCollectionUpdated: function (collection) {
3052
- // console.log('Collection received. There are ', Object.keys(collection).length, ' Tags in the collection', collection);
3053
- // }
3054
- // });
3992
+ await this.init3DObjectViewer();
3055
3993
  resolve(true);
3056
- }.bind(this));
3994
+ });
3057
3995
  });
3058
3996
  }
3997
+ getSweepUUIDForSid(sid) {
3998
+ const collection = this.sweepCollection;
3999
+ if (!collection || !collection[sid])
4000
+ return null;
4001
+ return collection[sid].uuid ?? null;
4002
+ }
3059
4003
  setLightingOff() {
3060
4004
  this.noLightForObjects = true;
3061
4005
  }
3062
- pointToString(point) {
3063
- var x = point.x.toFixed(3);
3064
- var y = point.y.toFixed(3);
3065
- var z = point.z.toFixed(3);
3066
- return `{ x: ${x}, y: ${y}, z: ${z} }`;
3067
- }
3068
- //
3069
- // ---------- Measurements related ----------
3070
- //
3071
- /**
3072
- * Callback after measurement is performed
3073
- */
3074
4006
  getDistanceForLastMeasurement() {
3075
- if (this.lastMeasure.length > 0) {
3076
- const numberPoints = this.lastMeasure.length;
3077
- this.distancesLastMeasure = [];
3078
- for (let index = 1; index < numberPoints; index += 1) {
3079
- const distance = getDistanceBetweenTwoPoints(this.lastMeasure[index - 1], this.lastMeasure[index]);
3080
- this.distancesLastMeasure.push(distance);
3081
- }
3082
- this.takeScreenShot().then((res) => {
3083
- this.router.navigate([`visit/${this.currentSpaceID}/add_measurement`]);
3084
- });
3085
- }
4007
+ this.measurementService.getDistanceForLastMeasurement(this.sdk, this.currentSpaceID);
3086
4008
  }
3087
4009
  getLastDistances() {
3088
- return this.distancesLastMeasure;
4010
+ return this.measurementService.getLastDistances();
3089
4011
  }
3090
4012
  /**
3091
4013
  * Takes screenshot and saves base64 in lastScreenshotUri
3092
4014
  * @returns Promise
3093
4015
  */
3094
4016
  takeScreenShot() {
3095
- return this.sdk.Renderer.takeScreenShot(this.resolution, this.visibility).then(function (screenShotUri) {
3096
- // base64 string
3097
- this.lastScreenshotUri = screenShotUri;
3098
- return Promise.resolve();
3099
- }.bind(this));
4017
+ return this.measurementService.takeScreenShot(this.sdk);
3100
4018
  }
3101
4019
  getScreenShotUri() {
3102
- return this.lastScreenshotUri;
4020
+ return this.measurementService.getScreenShotUri();
3103
4021
  }
3104
4022
  getLastMeasurement() {
3105
- const data = {
3106
- measure: this.lastMeasure,
3107
- sweep: this.poseCamera.sweep,
3108
- };
3109
- return data;
4023
+ return this.measurementService.getLastMeasurement(this.poseCamera);
3110
4024
  }
3111
4025
  //
3112
4026
  // ---------- Utils ----------
3113
4027
  //
4028
+ getFloorIdForTag(mattertagData) {
4029
+ return this.matterportTagService.getFloorIdForTag(mattertagData, this.sweepCollection);
4030
+ }
3114
4031
  /**
3115
4032
  * Styling of pointer
3116
4033
  */
3117
4034
  updatePointerTrick() {
3118
- if (this.interactionMode !== ViewerInteractions.DEFAULT &&
3119
- this.mattertagToFollow &&
3120
- this.poseMatterport &&
3121
- this.getDistPosition(this.poseMatterport.position, this.oldPoseMatterportPosition) > 25) {
3122
- this.pointerButton.style.display = 'none';
3123
- const size = {
3124
- w: this.container.clientWidth,
3125
- h: this.container.clientHeight,
3126
- };
3127
- const coords = this.sdk.Conversion.worldToScreen(this.poseMatterport.position, this.poseCamera, size);
3128
- this.pointerButton.style.left = `${coords.x * Math.sign(coords.x) - 25}px`;
3129
- this.pointerButton.style.top = `${coords.y * Math.sign(coords.x) - 25}px`;
3130
- this.oldPoseMatterportPosition = {
3131
- ...this.poseMatterport.position,
3132
- };
3133
- }
3134
- else {
3135
- this.pointerButton.style.display = 'block';
3136
- }
4035
+ this.pointerService.updatePointerTrick(this.sdk, this.container, this.poseMatterport, this.poseCamera, this.interactionMode, this.mattertagToFollow);
4036
+ }
4037
+ /**
4038
+ * Triggers a tag visibility refresh based on current camera mode, floor and sweep.
4039
+ * Delegates all logic to MatterportTagService.updateTagsVisibilityForContext().
4040
+ * Called automatically on Mode, Sweep, Floor and Room changes.
4041
+ */
4042
+ refreshTagsVisibility() {
4043
+ Promise.resolve().then(() => {
4044
+ if (isDevMode() && this.sweepCollection && this.poseCamera?.sweep) {
4045
+ const sweepData = this.sweepCollection[this.poseCamera.sweep];
4046
+ console.debug('[refreshTagsVisibility] sweepData for current sweep:', JSON.stringify(sweepData));
4047
+ console.debug('[refreshTagsVisibility] currentFloorSequence:', this.navigationService.currentFloorSequence);
4048
+ console.debug('[refreshTagsVisibility] currentCameraMode:', this.currentCameraMode);
4049
+ }
4050
+ this.matterportTagService.updateTagsVisibilityForContext(this.sdk, this.sweepCollection, this.currentCameraMode, this.mattertagToFollow, this.navigationService.currentFloorSequence, this.navigationService.currentRooms).catch(err => {
4051
+ if (isDevMode())
4052
+ console.warn('[MatterportService] refreshTagsVisibility error', err);
4053
+ });
4054
+ });
3137
4055
  }
3138
4056
  /**
3139
4057
  * Realtime mattertag following the cursor
3140
4058
  * @param mattertag string
3141
4059
  */
3142
4060
  enable_following_tag(mattertag) {
4061
+ const OFFSET = 0.0001; // quasi-invisible mais suffit à éviter l'interception
3143
4062
  this.sdk.Tag.editPosition(mattertag, {
3144
4063
  anchorPosition: {
3145
- x: this.poseMatterport.position.x * 1,
3146
- y: this.poseMatterport.position.y * 1,
3147
- z: this.poseMatterport.position.z * 1,
4064
+ x: this.poseMatterport.position.x + OFFSET,
4065
+ y: this.poseMatterport.position.y,
4066
+ z: this.poseMatterport.position.z + OFFSET,
3148
4067
  },
3149
4068
  stemVector: {
3150
4069
  x: this.poseMatterport.normal.x * 0.3,
3151
4070
  y: this.poseMatterport.normal.y * 0.3,
3152
4071
  z: this.poseMatterport.normal.z * 0.3,
3153
- },
4072
+ }
3154
4073
  });
3155
4074
  }
3156
4075
  /**
@@ -3160,58 +4079,46 @@ class MatterportService {
3160
4079
  * @param pos2
3161
4080
  */
3162
4081
  getDistPosition(pos1, pos2) {
3163
- const size = {
3164
- w: this.container.clientWidth,
3165
- h: this.container.clientHeight,
3166
- };
3167
- const coords1 = this.sdk.Conversion.worldToScreen(pos1, this.poseCamera, size);
3168
- const coords2 = this.sdk.Conversion.worldToScreen(pos2, this.poseCamera, size);
3169
- return Math.sqrt((coords1.x - coords2.x) ** 2 + (coords1.y - coords2.y) ** 2);
4082
+ return this.pointerService.getDistPosition(this.sdk, this.container, this.poseCamera, pos1, pos2);
3170
4083
  }
3171
4084
  //
3172
4085
  // ---------- Mattertag related ----------
3173
4086
  //
3174
- /**
3175
- * Creates the Mattertag that will follow the cursor
3176
- * @param mattertagData MattertagData
3177
- */
4087
+ /** Creates a temporary Mattertag that follows the user's cursor for positioning. */
3178
4088
  async addCursorMattertag(mattertagData) {
3179
4089
  if (!this.poseMatterport)
3180
4090
  return;
3181
4091
  this.mattertagToFollow = await this.addMattertagToViewer(mattertagData);
3182
- console.log('following the tag', this.mattertagToFollow);
4092
+ await wait(50);
3183
4093
  this.sdk.Tag.editIcon(this.mattertagToFollow, 'icon-position');
3184
4094
  this.sdk.Tag.editOpacity(this.mattertagToFollow, 1);
3185
- }
3186
- /**
3187
- * Adds Mattertag to viewer for an existing object with coordinates (in mattertagData.poi)
3188
- * (position, injected html, set icon)
3189
- * @param mattertagData
3190
- * returns mattertagID
3191
- */
3192
- async addMattertagToViewer(mattertagData) {
3193
- let sidList;
3194
- if (!this.sdk) {
3195
- return null;
3196
- }
4095
+ this.sdk.Tag.editPosition(this.mattertagToFollow, {
4096
+ anchorPosition: { ...this.poseMatterport.position },
4097
+ stemVector: {
4098
+ x: this.poseMatterport.normal.x * 0.3,
4099
+ y: this.poseMatterport.normal.y * 0.3,
4100
+ z: this.poseMatterport.normal.z * 0.3,
4101
+ }
4102
+ });
3197
4103
  try {
3198
- sidList = await this.sdk.Tag.add(mattertagData.getData());
3199
- }
3200
- catch (error) {
3201
- console.log('Tag does not belong to the visit', error, mattertagData.getData());
3202
- }
3203
- if (sidList) {
3204
- const mattertagID = sidList[0];
3205
- // console.log("added tag", mattertagData.getType(), mattertagID);
3206
- this.mattertagIDs.push(mattertagID);
3207
- this.dictionnaryTags.set(mattertagID, mattertagData);
3208
- return mattertagID;
4104
+ await this.sdk.Tag.allowAction(this.mattertagToFollow, {
4105
+ opening: true,
4106
+ navigating: false,
4107
+ docking: false
4108
+ });
3209
4109
  }
3210
- return null;
4110
+ catch (e) { }
3211
4111
  }
3212
- /**
3213
- * Actions when position of mattertag is validated by left click
3214
- */
4112
+ get tagAddQueue() {
4113
+ return this.matterportTagService.tagAddQueue;
4114
+ }
4115
+ set tagAddQueue(value) {
4116
+ this.matterportTagService.tagAddQueue = value;
4117
+ }
4118
+ async addMattertagToViewer(mattertagData) {
4119
+ return this.matterportTagService.addMattertagToViewer(this.sdk, mattertagData);
4120
+ }
4121
+ /** Triggers callback actions after a tag's position is validated by a left click. */
3215
4122
  onValidatedMattertag() {
3216
4123
  this.setInteractionMode(ViewerInteractions.DEFAULT);
3217
4124
  const lastTag = this.getLastTag();
@@ -3222,34 +4129,21 @@ class MatterportService {
3222
4129
  this.callbackAfterMattertagValidation(callbackMode);
3223
4130
  }
3224
4131
  }
3225
- /**
3226
- * Registers new icon (path to image) and set its for Mattertag
3227
- * @param mattertagID string
3228
- * @param iconPath string
3229
- */
4132
+ /** Registers a new icon texture and applies it to a specific Mattertag. */
3230
4133
  async addNewIconAndSetForTag(mattertagID, iconPath) {
3231
- await this.sdk.Asset.registerTexture(`icon_${mattertagID}`, iconPath);
3232
- this.sdk.Tag.editIcon(mattertagID, `icon_${mattertagID}`);
4134
+ await this.matterportTagService.addNewIconAndSetForTag(this.sdk, mattertagID, iconPath, false, 1);
3233
4135
  }
3234
- /**
3235
- * Changes icon of Mattertag (the iconName should be registered = one of default ones)
3236
- * @param mattertagID string
3237
- * @param iconName string
3238
- */
4136
+ /** Applies a pre-registered icon to a Mattertag. */
3239
4137
  async setRegistredIconForTag(mattertagID, iconName) {
3240
4138
  try {
3241
4139
  this.sdk.Tag.editIcon(mattertagID, iconName);
3242
4140
  }
3243
4141
  catch (e) {
3244
- console.log('could not edit Icon with name ', iconName, '. Is it registered?');
4142
+ if (isDevMode())
4143
+ console.warn('Could not edit icon', iconName, e);
3245
4144
  }
3246
4145
  }
3247
- /**
3248
- * Sets default icon for a tag (registered in initSdk) OR uses tagIcon from POI (available from MattertagData)
3249
- * @param mattertagID string
3250
- * @param mattertagData MattertagData
3251
- * @returns
3252
- */
4146
+ /** Sets the appropriate icon and opacity for a tag based on its type and POI metadata. */
3253
4147
  async setTagIconAndOpacity(mattertagID, mattertagData) {
3254
4148
  if (this.SPModule === SpModule.HOTEL) {
3255
4149
  const room = mattertagData.getObject();
@@ -3294,36 +4188,89 @@ class MatterportService {
3294
4188
  }
3295
4189
  try {
3296
4190
  this.sdk.Tag.editIcon(mattertagID, iconName);
3297
- this.sdk.Tag.editOpacity(mattertagID, opacity);
3298
4191
  }
3299
4192
  catch (error) {
3300
4193
  console.log('Error editIcon or opacity', error);
3301
4194
  }
3302
4195
  }
3303
- /**
3304
- * Moves viewer to last tag created
3305
- */
3306
- goToLastTag() {
3307
- setTimeout(() => {
3308
- const lastTag = this.getLastTag();
3309
- this.goToTag(lastTag);
3310
- }, 2000);
4196
+ /** Navigates the camera to the most recently created tag. */
4197
+ async goToLastTag() {
4198
+ await this.matterportTagService.goToLastTag(this.sdk);
3311
4199
  }
3312
- /**
3313
- * Moves viewer to Mattertag with ID provided
3314
- * @param mattertagID string
3315
- * @returns
3316
- */
4200
+ /** Navigates the camera to a specific Mattertag by its ID. */
3317
4201
  async goToTag(mattertagID) {
3318
- if (mattertagID === '')
4202
+ if (!mattertagID /* || this.inTransitionSweep*/) {
3319
4203
  return;
4204
+ }
3320
4205
  try {
3321
4206
  this.onGoToTag.next(mattertagID);
3322
4207
  await this.sdk.Sweep.current.waitUntil((currentSweep) => currentSweep !== '');
3323
- await this.sdk.Mattertag.navigateToTag(mattertagID, this.sdk.Mattertag.Transition.FLY);
4208
+ const mtData = this.dictionnaryTags.get(mattertagID);
4209
+ if (mtData) {
4210
+ const position = mtData.getPosition();
4211
+ await this.sdk.Camera.lookAt(position, {
4212
+ transition: this.sdk.Camera.TransitionType.FLY
4213
+ });
4214
+ await this.sdk.Tag.open(mattertagID);
4215
+ }
4216
+ else {
4217
+ // Fallback for cases where we don't have local data yet
4218
+ await this.sdk.Tag.open(mattertagID);
4219
+ }
3324
4220
  }
3325
4221
  catch (error) {
3326
- console.log('cannot navigate to tag', error);
4222
+ if (isDevMode())
4223
+ console.warn('Cannot navigate to tag', error);
4224
+ }
4225
+ }
4226
+ /**
4227
+ * Computes the context-aware opacity for a single tag at the moment it is rendered.
4228
+ * - Cursor tag: always use provided defaultOpacity.
4229
+ * - DOLLHOUSE / FLOORPLAN: 0 (hidden).
4230
+ * - INSIDE: floor-filtered proximity opacity via MatterportTagService.
4231
+ * - Other / unknown: defaultOpacity.
4232
+ */
4233
+ /*private _computeContextOpacityForTag(
4234
+ mattertagID: string,
4235
+ mattertagData: MattertagData,
4236
+ defaultOpacity: number
4237
+ ): number {
4238
+ if (mattertagID === this.mattertagToFollow) {
4239
+ return defaultOpacity;
4240
+ }
4241
+ return this.matterportTagService.computeSingleTagContextOpacity(
4242
+ mattertagData,
4243
+ this.poseCamera,
4244
+ this.sweepCollection,
4245
+ this.navigationService.currentFloorSequence,
4246
+ this.navigationService.currentRooms
4247
+ );
4248
+ }*/
4249
+ addCustomCss() {
4250
+ const css = `
4251
+ .footer-ui,
4252
+ .showcase-footer,
4253
+ .mp-nova-btn-group,
4254
+ .footer-divider,
4255
+ .logo-link,
4256
+ .footer-logo,
4257
+ .bottom-ui,
4258
+ [data-testid="showcase-footer"] {
4259
+ opacity: 0 !important;
4260
+ visibility: hidden !important;
4261
+ pointer-events: none !important;
4262
+ }
4263
+ `;
4264
+ // 1st method: Via Scene SDK
4265
+ if (this.sdk?.Scene?.addStyle) {
4266
+ this.sdk.Scene.addStyle(css);
4267
+ }
4268
+ // 2nd method: Direct injection in iFrame DOM (Fallback)
4269
+ const iframe = document.querySelector('#viewer-module');
4270
+ if (iframe?.contentWindow?.document) {
4271
+ const style = iframe.contentWindow.document.createElement('style');
4272
+ style.textContent = css;
4273
+ iframe.contentWindow.document.head.appendChild(style);
3327
4274
  }
3328
4275
  }
3329
4276
  /**
@@ -3333,11 +4280,18 @@ class MatterportService {
3333
4280
  * @param tagType PoiType
3334
4281
  */
3335
4282
  async updateMatterTagContentForTagID(mattertagID, object = null, tagType = null) {
3336
- this.sdk.Tag.editBillboard(mattertagID, this.dictionnaryTags.get(mattertagID).getData());
3337
- if (object && tagType) {
3338
- await this.injectHtmlInTag(tagType, object, mattertagID);
3339
- }
3340
- await this.setTagIconAndOpacity(mattertagID, this.dictionnaryTags.get(mattertagID));
4283
+ if (this.sdk) {
4284
+ await this.sdk.Tag.editBillboard(mattertagID, this.dictionnaryTags.get(mattertagID).getData());
4285
+ }
4286
+ // Mise à jour de l'icône immédiate
4287
+ await this.matterportTagService.updateMatterTagContentForTagID(this.sdk, mattertagID, object, tagType, this.setTagIconAndOpacity.bind(this),
4288
+ // On passe une fonction wrapper qui exécute l'injection HTML sans bloquer
4289
+ async (type, obj, sid) => {
4290
+ this.injectHtmlInTag(type, obj, sid).catch(err => {
4291
+ if (isDevMode())
4292
+ console.warn('Async HTML update failed for', sid, err);
4293
+ });
4294
+ });
3341
4295
  }
3342
4296
  /**
3343
4297
  * Updates injected html for Mattertag
@@ -3353,17 +4307,13 @@ class MatterportService {
3353
4307
  * @param mattertagID string
3354
4308
  */
3355
4309
  deleteMattertagFromId(mattertagID) {
3356
- this.sdk.Tag.remove(mattertagID);
3357
- this.dictionnaryTags.delete(mattertagID);
4310
+ this.matterportTagService.deleteMattertagFromId(this.sdk, mattertagID);
3358
4311
  }
3359
4312
  /**
3360
4313
  * Deletes latest created mattertag
3361
4314
  */
3362
- deleteLastMattertag() {
3363
- const mattertagID = this.mattertagIDs.pop();
3364
- if (mattertagID) {
3365
- this.deleteMattertagFromId(mattertagID);
3366
- }
4315
+ async deleteLastMattertag() {
4316
+ await this.matterportTagService.deleteLastMattertag(this.sdk);
3367
4317
  }
3368
4318
  /**
3369
4319
  * Legacy: used to be called action_add_mattertag_from_POI
@@ -3374,35 +4324,7 @@ class MatterportService {
3374
4324
  * @returns
3375
4325
  */
3376
4326
  async createMattertagFromPOI(tagType, object, poi) {
3377
- // check if tag exists already
3378
- const { tag, sweep } = this.getTagFromElementId(object.id);
3379
- if (tag) {
3380
- // console.log("tag exists", object)
3381
- return;
3382
- }
3383
- const mattertagData = new MattertagData(tagType);
3384
- mattertagData.setObject(object, tagType);
3385
- if (poi.coordinate) {
3386
- mattertagData.setPosition(JSON.parse(poi.coordinate));
3387
- }
3388
- if (poi.metadata) {
3389
- const tagMetadata = JSON.parse(poi.metadata);
3390
- if (tagMetadata.normal) {
3391
- mattertagData.setNormal(tagMetadata.normal);
3392
- }
3393
- else {
3394
- mattertagData.setNormal({ x: 0, y: -0.15, z: 0 });
3395
- }
3396
- }
3397
- if (poi.matterportSweepID) {
3398
- mattertagData.setSweepID(poi.matterportSweepID);
3399
- }
3400
- mattertagData.setPoi(poi);
3401
- const createdTagID = await this.addMattertagToViewer(mattertagData);
3402
- if (createdTagID && this.sdk) {
3403
- await this.setTagIconAndOpacity(createdTagID, mattertagData);
3404
- await this.injectHtmlInTag(tagType, object, createdTagID);
3405
- }
4327
+ return this.matterportTagService.createMattertagFromPOI(this.sdk, tagType, object, poi, this.poseCamera, this.getTagFromElementId.bind(this), this.setTagIconAndOpacity.bind(this), this.injectHtmlInTag.bind(this));
3406
4328
  }
3407
4329
  /**
3408
4330
  * Inject custom HTML as Mattertag content
@@ -3411,63 +4333,10 @@ class MatterportService {
3411
4333
  * @param tagID string
3412
4334
  */
3413
4335
  async injectHtmlInTag(tagType, object, tagID) {
3414
- let html = await this.tagService.getHtmlToInject(tagType, object);
3415
- if (html !== '' && this.sdk) {
3416
- const scriptTag = this.tagService.getScriptForTag(object, tagType);
3417
- html += `${scriptTag}`;
3418
- // create and register the sandbox
3419
- const [sandboxId, messenger] = await this.sdk.Tag.registerSandbox(html);
3420
- // detach previous sandbox from a tag
3421
- let attachmentID = this.tagsAttachments[tagID];
3422
- if (attachmentID) {
3423
- this.sdk.Tag.detach(tagID, attachmentID);
3424
- }
3425
- this.tagsAttachments[tagID] = sandboxId;
3426
- // attach the sandbox to a tag
3427
- this.sdk.Tag.attach(tagID, sandboxId);
3428
- // receive data from the sandbox
3429
- // tagMessengerOn allows to go here only once
3430
- if (!this.tagMessengerOn) {
3431
- this.tagMessengerOn = true;
3432
- const imageClick = (featureID) => {
3433
- // console.log("image click handler", featureID);
3434
- this.tagService.onActionImageClick(featureID);
3435
- };
3436
- const audioClick = (audioCommentID) => {
3437
- // console.log("audio click handler", audioCommentID);
3438
- this.tagService.onActionAudioClick(audioCommentID);
3439
- };
3440
- const videoClick = (url) => {
3441
- this.tagService.onActionVideoClick(url);
3442
- };
3443
- messenger.on(TagAction.DETAIL_CLICK, this.tagService.onActionDetailClick.bind(this.tagService));
3444
- messenger.on(TagAction.TICKET_CLICK, this.tagService.onActionDetailClick.bind(this.tagService));
3445
- messenger.on(TagAction.AUDIO_CLICK, audioClick);
3446
- messenger.on(TagAction.IMAGE_CLICK, imageClick);
3447
- messenger.on(TagAction.VIDEO_CLICK, videoClick);
3448
- messenger.on(TagAction.DOC_CLICK, this.tagService.onActionDocClick.bind(this.tagService));
3449
- messenger.on(TagAction.YOUTUBE_CLICK, this.tagService.onActionYoutubeClick.bind(this.tagService));
3450
- }
3451
- }
3452
- else {
3453
- // if html is empty (case of EMBED content), we edit billboard for Feature and attach new content
3454
- const { comment, tagDescription } = this.tagService.getBillboardMediaToEmbed(object);
3455
- if (comment) {
3456
- // register the media
3457
- const [attachmentID] = await this.sdk.Tag.registerAttachment(comment.externalLink);
3458
- // attach
3459
- this.sdk.Tag.attach(tagID, attachmentID);
3460
- this.sdk.Tag.editBillboard(tagID, {
3461
- label: object.title,
3462
- description: tagDescription,
3463
- });
3464
- }
3465
- }
4336
+ return this.matterportTagService.injectHtmlInTag(this.sdk, tagType, object, tagID);
3466
4337
  }
3467
4338
  async action_delete_all_mattertags() {
3468
- await this.sdk.Tag.remove(...this.mattertagIDs);
3469
- this.mattertagIDs = [];
3470
- this.dictionnaryTags.clear();
4339
+ await this.matterportTagService.action_delete_all_mattertags(this.sdk);
3471
4340
  }
3472
4341
  /**
3473
4342
  * Deletes Mattertag from visit associated with object ID (ticketID, etc)
@@ -3476,36 +4345,21 @@ class MatterportService {
3476
4345
  async deleteMattertagForObject(elementID) {
3477
4346
  const matterTagID = this.getTagFromElementId(elementID).tag;
3478
4347
  if (matterTagID) {
3479
- try {
3480
- await this.sdk.Tag.remove(matterTagID);
3481
- this.dictionnaryTags.delete(matterTagID);
3482
- const index = this.mattertagIDs.indexOf(matterTagID);
3483
- this.mattertagIDs.splice(index, 1);
3484
- }
3485
- catch (error) {
3486
- console.log('Cannot delete tag', matterTagID, error);
3487
- }
4348
+ this.tagAddQueue = this.tagAddQueue.then(async () => {
4349
+ await this.matterportTagService.deleteMattertagFromId(this.sdk, matterTagID);
4350
+ });
3488
4351
  }
4352
+ return this.tagAddQueue;
3489
4353
  }
3490
4354
  /**
3491
4355
  * uuid from threejs
3492
4356
  * @param uuid
3493
4357
  */
3494
4358
  async deleteObject3D(uuid) {
3495
- this.dictionnaryObjects3D.get(uuid).obj3D.visible = false;
4359
+ return this.object3DService.deleteObject3D(uuid);
3496
4360
  }
3497
4361
  getObject3DModelNodeFromDictionnary(uuid) {
3498
- let obj = this.dictionnaryObjects3D.get(uuid);
3499
- if (!obj) {
3500
- console.log("weird thing again");
3501
- return null;
3502
- }
3503
- //might break things or fix everything ..?
3504
- if (obj.obj3D.uuid != uuid) {
3505
- console.log("we have THE problem");
3506
- obj.obj3D.uuid = uuid; //not enugh to fix the pb
3507
- }
3508
- return obj;
4362
+ return this.object3DService.getObject3DModelNodeFromDictionnary(uuid);
3509
4363
  }
3510
4364
  /**
3511
4365
  * Creates MattertagData and start repositioning (creates temporary mattertag that follows the cursor)
@@ -3546,10 +4400,10 @@ class MatterportService {
3546
4400
  this.setInteractionMode(ViewerInteractions.DEFAULT);
3547
4401
  }
3548
4402
  setLastObject3D(lastObject3D) {
3549
- this.lastObject3D = lastObject3D;
4403
+ this.object3DService.lastObject3D = lastObject3D;
3550
4404
  }
3551
4405
  getLastObject3D() {
3552
- return this.lastObject3D;
4406
+ return this.object3DService.lastObject3D;
3553
4407
  }
3554
4408
  /**
3555
4409
  * Performs callback after mattertag position was validated (creation of object or repositioning)
@@ -3645,7 +4499,6 @@ class MatterportService {
3645
4499
  this.dictionnaryTags.get(mattertagID).elementID = object.id;
3646
4500
  try {
3647
4501
  await this.updateMatterTagContentForTagID(mattertagID, object, poiType);
3648
- // TODO: fix this
3649
4502
  await this.updateMatterTagPosInSdkViewer(mattertagID, object, poiType, poi);
3650
4503
  }
3651
4504
  catch (e) {
@@ -3669,7 +4522,7 @@ class MatterportService {
3669
4522
  z: 0,
3670
4523
  },
3671
4524
  };
3672
- this.sdk.Mattertag.editPosition(mattertagID, newPosition);
4525
+ //this.sdk.Mattertag.editPosition(mattertagID, newPosition);
3673
4526
  this.sdk.Tag.editPosition(mattertagID, newPosition);
3674
4527
  this.dictionnaryTags
3675
4528
  .get(mattertagID)
@@ -3682,7 +4535,6 @@ class MatterportService {
3682
4535
  if (currentTag.elementID === object.id && tagId !== mattertagID) {
3683
4536
  this.dictionnaryTags.delete(tagId);
3684
4537
  this.sdk.Tag.remove(tagId);
3685
- this.sdk.Mattertag.remove(tagId);
3686
4538
  break;
3687
4539
  }
3688
4540
  }
@@ -3692,19 +4544,8 @@ class MatterportService {
3692
4544
  * @param elementID string
3693
4545
  * @returns {tag: string | null, sweep: string | null}
3694
4546
  */
3695
- getTagFromElementId(elementID) {
3696
- let tagID = null;
3697
- let sweepID = null;
3698
- for (let [mattertagID, mattertagData] of this.dictionnaryTags) {
3699
- if (mattertagData.elementID === elementID) {
3700
- tagID = mattertagID;
3701
- const sweep = mattertagData.getSweepID();
3702
- if (sweep && this.sweeps && this.sweeps.includes(sweep)) {
3703
- sweepID = sweep;
3704
- }
3705
- }
3706
- }
3707
- return { tag: tagID, sweep: sweepID };
4547
+ getTagFromElementId(elementID) {
4548
+ return this.matterportTagService.getTagFromElementId(elementID, this.sweeps);
3708
4549
  }
3709
4550
  /**
3710
4551
  * Gets latest tag created in the visit (when following the cursor to position)
@@ -3738,42 +4579,16 @@ class MatterportService {
3738
4579
  // ---------- Viewer related (switch views, go to) ----------
3739
4580
  //
3740
4581
  async action_toolbox_floorplan() {
3741
- if (this.inTransitionMode || this.inTransitionSweep) {
3742
- console.log('viewer is in transition, cannot go floorplan');
3743
- return;
3744
- }
3745
- try {
3746
- await this.sdk.Mode.moveTo('mode.floorplan');
3747
- }
3748
- catch (e) {
3749
- console.log('cannot go to floorplan', e);
3750
- }
4582
+ return this.navigationService.action_toolbox_floorplan(this.sdk);
3751
4583
  }
3752
4584
  action_toolbox_inside_view() {
3753
- this.sdk.Mode.moveTo('mode.inside');
4585
+ this.navigationService.action_toolbox_inside_view(this.sdk);
3754
4586
  }
3755
4587
  actionShowAllFloors() {
3756
- try {
3757
- this.sdk.Floor.showAll();
3758
- }
3759
- catch (e) {
3760
- console.log('cannot show all floors', e);
3761
- }
4588
+ this.navigationService.actionShowAllFloors(this.sdk);
3762
4589
  }
3763
4590
  async action_toolbox_dollhouse() {
3764
- setTimeout(async () => {
3765
- // console.log("mode: ", this.inTransitionMode, " sweep: ", this.inTransitionSweep);
3766
- if (this.inTransitionMode || this.inTransitionSweep) {
3767
- console.log('viewer is in transition, cannot go dollhouse');
3768
- return;
3769
- }
3770
- try {
3771
- await this.sdk.Mode.moveTo('mode.dollhouse');
3772
- }
3773
- catch (e) {
3774
- console.log('cannot go to dollhouse', e);
3775
- }
3776
- }, 1200);
4591
+ return this.navigationService.action_toolbox_dollhouse(this.sdk);
3777
4592
  }
3778
4593
  action_toolbox_mesure() {
3779
4594
  const newState = !this.isMeasureModeOn;
@@ -3787,70 +4602,13 @@ class MatterportService {
3787
4602
  }
3788
4603
  }
3789
4604
  async action_go_to_floor(floorName, matterportFloorSequence = null) {
3790
- if (!this.floors) {
3791
- console.log('Floor are not loaded yet');
3792
- return;
3793
- }
3794
- // console.log(this.floors);
3795
- // look up for sequence number (the safest method)
3796
- let floorMatterport = this.floors.find((floor) => floor.sequence === matterportFloorSequence);
3797
- if (!floorMatterport) {
3798
- floorMatterport = this.floors.find((floor) => floorName.includes(floor.name) && floor.name != '');
3799
- }
3800
- if (!floorMatterport) {
3801
- floorMatterport = this.floors.find((floor) => floorName.includes(floor.id));
3802
- }
3803
- // console.log(floorMatterport)
3804
- if (floorMatterport) {
3805
- let retry = true;
3806
- while (retry) {
3807
- try {
3808
- const floorIndex = await this.sdk.Floor.moveTo(floorMatterport.sequence);
3809
- // console.log("moved to floorIndex", floorIndex);
3810
- retry = false;
3811
- }
3812
- catch (error) {
3813
- console.log('Cannot move to Floor', error);
3814
- await wait(100);
3815
- }
3816
- }
3817
- }
3818
- else {
3819
- console.warn('No matterport floor found to move to');
3820
- }
4605
+ return this.navigationService.action_go_to_floor(this.sdk, floorName, matterportFloorSequence);
3821
4606
  }
3822
- async action_go_to_sweep(sweep, rotation = null) {
3823
- if (this.forbiddenSweeps.includes(sweep)) {
3824
- console.log('user is not allowed to go to this sweep');
3825
- return;
3826
- }
3827
- // console.log("going to sweep", sweep, "with rotation:", rotation);
3828
- setTimeout(async () => {
3829
- if (this.inTransitionMode || this.inTransitionSweep) {
3830
- console.log('Cannot go to sweep, in transition');
3831
- return;
3832
- }
3833
- try {
3834
- this.inTransitionSweep = true;
3835
- await this.sdk.Sweep.moveTo(sweep, {
3836
- transition: 'transition.instant',
3837
- transitionTime: 1500,
3838
- });
3839
- }
3840
- catch (error) {
3841
- this.inTransitionSweep = false;
3842
- console.log('Cannot move to sweep', error);
3843
- }
3844
- if (rotation) {
3845
- await this.sdk.Camera.setRotation(rotation, { speed: 100 }); // speed is degrees per second
3846
- }
3847
- }, 1000);
4607
+ async action_go_to_sweep(sweep, rotation) {
4608
+ return this.navigationService.action_go_to_sweep(this.sdk, sweep, rotation);
3848
4609
  }
3849
4610
  getCurrentSweep() {
3850
- if (this.poseCamera) {
3851
- return this.poseCamera.sweep;
3852
- }
3853
- return null;
4611
+ return this.navigationService.getCurrentSweep(this.poseCamera);
3854
4612
  }
3855
4613
  getCurrentCameraPosition() {
3856
4614
  if (this.poseCamera) {
@@ -3869,341 +4627,54 @@ class MatterportService {
3869
4627
  */
3870
4628
  async clearAll() {
3871
4629
  console.log('removing viewer');
3872
- this.action_delete_all_mattertags();
4630
+ await this.action_delete_all_mattertags();
3873
4631
  this.floors = null;
3874
4632
  this.sweeps = null;
3875
4633
  this.sdk = null;
3876
- clearInterval(this.timerPointer);
4634
+ this.pointerService.clear({
4635
+ onLeftClick: this.pointerLeftClickHandler.bind(this),
4636
+ onRightClick: this.pointerRightClickHandler.bind(this),
4637
+ onMiddleClick: this.pointerMiddleClickHandler.bind(this),
4638
+ });
3877
4639
  this.forbiddenSweeps = [];
3878
- this.tagMessengerOn = false;
3879
- // cancel subscriptions
3880
- this.pointerButton.removeEventListener('click', this.pointerLeftClickHandler);
3881
- this.pointerButton.removeEventListener('contextmenu', this.pointerRightClickHandler);
3882
- // TODO: only For Admins
3883
- if (this.getCursorPositionButton) {
3884
- this.getCursorPositionButton.removeEventListener('auxclick', this.pointerMiddleClickHandler);
3885
- }
3886
- // TODO: only for dev!
3887
- if (!!this.getCursorPositionButton &&
3888
- (document.location.href.indexOf('dev') !== -1 || document.location.href.indexOf('localhost') !== -1)) {
3889
- clearInterval(this.intervalCursorPointerPosition);
3890
- }
3891
4640
  }
3892
4641
  async removeForbiddenSweeps(forbiddenSweeps) {
3893
- this.forbiddenSweeps = [...forbiddenSweeps];
3894
- let removed = 0;
3895
- await Promise.all(forbiddenSweeps.map(async (sweep) => {
3896
- try {
3897
- await this.sdk.Sweep.disable(sweep);
3898
- removed += 1;
3899
- }
3900
- catch (error) {
3901
- console.log(error);
3902
- }
3903
- }));
3904
- console.log('removed sweeps:', removed);
4642
+ return this.navigationService.removeForbiddenSweeps(this.sdk, forbiddenSweeps);
3905
4643
  }
3906
4644
  //
3907
4645
  // ---------- 3D objects (SDK Bundle only) ----------
3908
4646
  //
3909
4647
  async init3DObjectViewer() {
3910
- return new Promise(async (resolve) => {
3911
- var [sceneObject] = await this.sdk.Scene.createObjects(1);
3912
- var node = sceneObject.addNode();
3913
- // TODO change this 🤮
3914
- node.addComponent('mp.lights');
3915
- //node.addComponent('mp.lights');
3916
- /*node.addComponent('mp.lights');
3917
- node.addComponent('mp.lights');*/
3918
- node.start();
3919
- const nodeControl = sceneObject.addNode();
3920
- this.objectControl = nodeControl.addComponent('mp.transformControls');
3921
- nodeControl.start();
3922
- //this.add3DObject({},null);
3923
- resolve();
3924
- });
4648
+ return this.object3DService.init3DObjectViewer(this.sdk);
3925
4649
  }
3926
4650
  async add3DObject(obj, mode) {
3927
- return new Promise(async (resolve) => {
3928
- const [sceneObject] = await this.sdk.Scene.createObjects(1);
3929
- // TODO: improvment, regroup all of these in Dynamical Objects Lib
3930
- // SecurityCamera
3931
- // NestThermostat
3932
- // Video
3933
- let isAnimatedSecurityCamera = false;
3934
- let isNestThermostat = false;
3935
- let isSmarterplanPromotionalVideo = false;
3936
- let isAzimuthalCrown = false;
3937
- /**
3938
- * TODO: refacto with an enum or switch/case
3939
- */
3940
- if (obj.object === "security_camera") {
3941
- isAnimatedSecurityCamera = true;
3942
- }
3943
- if (obj.object === "nest_thermostat") {
3944
- isNestThermostat = true;
3945
- }
3946
- if (obj.object === 'video') {
3947
- isSmarterplanPromotionalVideo = true;
3948
- }
3949
- if (obj.object === 'azimuth') {
3950
- isAzimuthalCrown = true;
3951
- }
3952
- const modelNode = sceneObject.addNode();
3953
- let component = null;
3954
- const initial = {
3955
- url: `/assets/3Dobjects/objects/${obj.object}${obj.format.indexOf('.') === -1 ? '.' + obj.format : obj.format}`,
3956
- // TODO/ store localPosition && localRotation in BDD too (in order to have pertfect initial placement)
3957
- localRotation: { "x": 0, "y": 0, "z": 0 },
3958
- // TODO/ store localPosition && localRotation in BDD too (in order to have pertfect initial placement)
3959
- localPosition: { "x": 0, "y": 0, "z": 0 },
3960
- visible: true,
3961
- colliderEnabled: true
3962
- };
3963
- switch (obj.format) {
3964
- case '.obj':
3965
- case 'obj':
3966
- component = modelNode.addComponent('mp.objLoader', initial);
3967
- break;
3968
- case '.fbx':
3969
- case 'fbx':
3970
- component = modelNode.addComponent('mp.fbxLoader', initial);
3971
- break;
3972
- case '.gltf':
3973
- case 'gltf':
3974
- component = modelNode.addComponent('mp.gltfLoader', initial);
3975
- break;
3976
- case '.glb':
3977
- case 'glb':
3978
- component = modelNode.addComponent('mp.gltfLoader', initial);
3979
- break;
3980
- case '.dae':
3981
- case 'dae':
3982
- component = modelNode.addComponent('mp.daeLoader', initial);
3983
- break;
3984
- default:
3985
- console.log('Format not supported');
3986
- break;
3987
- }
3988
- //cache system (i'll try to make it work later ...)
3989
- /*let objContentsJSON = JSON.stringify(modelNode);
3990
- console.log(modelNode);
3991
- console.log(objContentsJSON);
3992
- console.log(JSON.stringify(modelNode));
3993
- localStorage.setItem(`testObj_${obj.object}`, objContentsJSON);
3994
- console.log("stored ?!");
3995
- console.log(typeof modelNode);*/
3996
- //let dataS = serialijse.serialize(modelNode);
3997
- //console.log(dataS);
3998
- // Use 3 ?! Ambient lightings
3999
- if (this.noLightForObjects) {
4000
- const lightsNode = sceneObject.addNode();
4001
- lightsNode.addComponent('mp.ambientLight', {
4002
- intensity: 1,
4003
- color: { r: 1.0, g: 1.0, b: 1.0 },
4004
- });
4005
- this.noLightForObjects = false;
4006
- }
4007
- modelNode.obj3D.position.set(obj.position.x, obj.position.y, obj.position.z);
4008
- modelNode.obj3D.rotation.set(obj.rotation.x, obj.rotation.y, obj.rotation.z);
4009
- modelNode.obj3D.scale.set(obj.scale.x, obj.scale.y, obj.scale.z);
4010
- if (isAzimuthalCrown) {
4011
- /*if(modelNode.obj3D.scale.x >= 1.0){
4012
- modelNode.obj3D.scale.set(obj.scale.x/5000, obj.scale.y/5000, obj.scale.z/5000);
4013
- }*/
4014
- this.azimuthalCrown = modelNode;
4015
- this.displayAzimutalCrown();
4016
- }
4017
- // By defaut, during creation, object has translation gizmo
4018
- // => User has to click on lateral panel, and save its position after playing with it!
4019
- if (mode && !isNestThermostat) {
4020
- this.attachGizmoControlTo3DObject(modelNode, sceneObject, mode, true, true).catch((e) => console.log(e));
4021
- }
4022
- if (this.lastObject3D && typeof this.lastObject3D.id === 'string') {
4023
- // prompt ThreeJS UUID to match our last generated object if new (not from Db)
4024
- modelNode.obj3D.uuid = this.lastObject3D.id;
4025
- }
4026
- else if (obj.id) {
4027
- modelNode.obj3D.uuid = obj.id;
4028
- }
4029
- this.lastObject3D = modelNode.obj3D;
4030
- // Store this in memory Map dictionnary
4031
- //console.log("Adding object: ");
4032
- //console.log(modelNode);
4033
- this.dictionnaryObjects3D.set(modelNode.obj3D.uuid, modelNode);
4034
- this.dictionnarySceneObjects3D.set(modelNode.obj3D.uuid, sceneObject);
4035
- /*this.sdk.Camera.pose.subscribe(
4036
- function (pose: any) {
4037
- //console.log("in callback")
4038
- //console.log(this.lastCameraPosition)
4039
- //console.log(pose.position)
4040
- if((pose.position.x == this.lastCameraPosition.x && pose.position.y == this.lastCameraPosition.y && pose.position.z == this.lastCameraPosition.z) || this.inTransitionMode || this.inTransitionSweep){
4041
- //console.log("returning")
4042
- return;
4043
- }
4044
- console.log("camera pos:",pose.position);
4045
- this.lastCameraPosition = {...pose.position};
4046
- if(this.lastObject3D){
4047
- this.lastObject3D.position.set(pose.position.x+.5,pose.position.y+.5,pose.position.z+.5);
4048
- }
4049
- }.bind(this));*/
4050
- if (isAnimatedSecurityCamera) {
4051
- const animator = modelNode.addComponent('mp.securityCamera', {
4052
- "nearPlane": 0.1,
4053
- "farPlane": 10,
4054
- "horizontalFOV": 52,
4055
- "aspect": 1.7777777777777777,
4056
- "localPosition": {
4057
- "x": 0.3,
4058
- "y": 0.18,
4059
- "z": 0
4060
- },
4061
- "localRotation": {
4062
- "x": -15,
4063
- "y": -90,
4064
- "z": 0
4065
- },
4066
- "color": 65280,
4067
- "panPeriod": 5,
4068
- "panAngle": -45
4069
- });
4070
- const modelOutput = sceneObject.addPath({
4071
- id: 'animated-model',
4072
- type: this.sdk.Scene.PathType.OUTPUT,
4073
- node: modelNode,
4074
- component: animator,
4075
- property: 'objectRoot'
4076
- });
4077
- this.securityCameraAnimator = animator;
4078
- if (!obj.viewFrustum) {
4079
- setTimeout(() => {
4080
- animator.toggleViewFrustum();
4081
- }, 1000);
4082
- }
4083
- }
4084
- if (isNestThermostat) {
4085
- // TODO: use bindPath instead using MP sdk classes (see Security Camera example)
4086
- // for TV uses CanvasImage below!
4087
- // const ci = new CanvasImage();
4088
- // ci.onInit();
4089
- const cv = new CanvasRenderer();
4090
- cv.onInit();
4091
- const plane = new PlaneRenderer();
4092
- plane.outputs = {
4093
- objectRoot: new Object3D(),
4094
- collider: new Object3D()
4095
- };
4096
- const inputTexture = cv.outputs.texture;
4097
- plane.setRootScene(this.threeJSScene);
4098
- plane.onInit(modelNode, inputTexture);
4099
- const sc = new NestThermostat();
4100
- sc.setComponent(component, plane, cv);
4101
- sc.setRootScene(this.threeJSScene);
4102
- sc.onInit(modelNode, plane, inputTexture);
4103
- cv.setCanvasNestThermostatPainter(sc);
4104
- setTimeout(() => {
4105
- sc.inputs.loadingState = "Loaded";
4106
- sc.onInputsUpdated();
4107
- }, 5000);
4108
- }
4109
- if (isSmarterplanPromotionalVideo) {
4110
- // TODO: use bindPath instead using MP sdk classes (see Security Camera example)
4111
- const sc = new TvPlayer();
4112
- sc.setComponent(component);
4113
- sc.onInit(modelNode);
4114
- setTimeout(() => {
4115
- sc.inputs.loadingState = "Loaded";
4116
- sc.onInputsUpdated();
4117
- }, 5000);
4118
- }
4119
- sceneObject.start();
4120
- resolve(this.lastObject3D);
4121
- });
4651
+ return this.object3DService.add3DObject(this.sdk, obj, mode);
4122
4652
  }
4123
4653
  toggleObjectVisibility(objectId) {
4124
- let obj = this.dictionnaryObjects3D.get(objectId);
4125
- obj.obj3D.visible = !obj.obj3D.visible;
4654
+ this.object3DService.toggleObjectVisibility(objectId);
4126
4655
  }
4127
4656
  isObjectVisible(objectId) {
4128
- let obj = this.dictionnaryObjects3D.get(objectId);
4129
- return obj.obj3D.visible;
4657
+ return this.object3DService.isObjectVisible(objectId);
4130
4658
  }
4131
4659
  async pointCameraTo3DObject(objectId) {
4132
- let obj = this.dictionnaryObjects3D.get(objectId);
4133
- //We create a temporary Tag
4134
- const poiObject = {
4135
- coordinate: JSON.stringify(obj.obj3D.position),
4136
- type: PoiType.OBJECT3D,
4137
- elementID: objectId, //todo: be careful with this
4138
- };
4139
- const objectDb = { id: objectId };
4140
- try {
4141
- await this.createMattertagFromPOI(PoiType.OBJECT3D, objectDb, poiObject);
4142
- }
4143
- catch (err) { }
4144
- //Not really necessary anymore since the tag will disappear quick
4145
- //this.sdk.Tag.editOpacity(mattertagID, 0.0);//opacity);
4146
- //this.sdk.Tag.allowAction(mattertagID, {}); //disables every action
4147
- let result = this.getTagFromElementId(objectId);
4148
- await this.goToTag(result.tag);
4149
- this.deleteLastMattertag();
4660
+ return this.object3DService.pointCameraTo3DObject(this.sdk, objectId, this.createMattertagFromPOI.bind(this), this.goToTag.bind(this), this.deleteLastMattertag.bind(this), this.getTagFromElementId.bind(this));
4150
4661
  }
4151
4662
  getSceneNodeFromObject3DId(uuid) {
4152
- return this.dictionnarySceneObjects3D.get(uuid);
4663
+ return this.object3DService.getSceneNodeFromObject3DId(uuid);
4153
4664
  }
4154
4665
  async displayAzimutalCrown() {
4155
- if (this.azimuthalCrown) {
4156
- this.azimuthalCrown.obj3D.position.set(this.poseCamera.position.x, this.poseCamera.position.y, this.poseCamera.position.z);
4157
- }
4666
+ return this.object3DService.displayAzimutalCrown(this.poseCamera);
4158
4667
  }
4159
4668
  async attachGizmoControlTo3DObject(modelNode, sceneObject, mode, visible, isNewObject) {
4160
- // Create a scene node with a transform control component.
4161
- let node = null;
4162
- node = sceneObject.addNode();
4163
- if (!node) {
4164
- const [sceneObject] = await this.sdk.Scene.createObjects(1);
4165
- node = sceneObject.addNode();
4166
- }
4167
- const myControl = node.addComponent('mp.transformControls');
4168
- node.start();
4169
- //
4170
- // // Make the transform control visible so that the user can manipulate the control selection.
4171
- myControl.transformControls.visible = visible;
4172
- //
4173
- // // Attach the model to the transform control
4174
- myControl.inputs.selection = modelNode;
4175
- //
4176
- // // set 'translate' mode to position the selection.
4177
- myControl.inputs.mode = mode;
4178
- modelNode.obj3D.controls = myControl; // store gizmoCtrl inside object
4179
- if (isNewObject) { //i keep the current solution for new objects
4180
- if (!this.lastObject3D || !this.lastObject3D.controls) {
4181
- try {
4182
- modelNode.obj3D.uuid = this.lastObject3D.uuid || this.lastObject3D.id;
4183
- }
4184
- catch (e) {
4185
- console.log(`id obj in Scene was not assigned to id from DB since`);
4186
- }
4187
- this.lastObject3D = modelNode.obj3D;
4188
- }
4189
- }
4190
- else { //objects already in place have to become the "lastObject" (i think?)
4191
- console.log("in my solution !");
4192
- console.log(modelNode);
4193
- console.log(modelNode.obj3D.uuid);
4194
- this.lastObject3D = modelNode.obj3D;
4195
- }
4196
- return modelNode;
4669
+ return this.object3DService.attachGizmoControlTo3DObject(this.sdk, modelNode, sceneObject, mode, visible, isNewObject);
4197
4670
  }
4198
4671
  removeGizmoFromLastObject() {
4199
- this.lastObject3D.controls.transformControls.visible = false;
4200
- this.lastObject3D.controls.transformControls.dispose();
4201
- this.lastObject3D.controls = null;
4672
+ this.object3DService.removeGizmoFromLastObject();
4202
4673
  }
4203
4674
  toggleViewFrustum() {
4204
- this.securityCameraAnimator.toggleViewFrustum();
4675
+ this.object3DService.toggleViewFrustum();
4205
4676
  }
4206
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MatterportService, deps: [{ token: 'config' }, { token: i1$1.Router }, { token: i1$1.ActivatedRoute }, { token: BaseVisibilityService }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable });
4677
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MatterportService, deps: [{ token: 'config' }, { token: i1$1.Router }, { token: i1$1.ActivatedRoute }, { token: BaseVisibilityService }, { token: i0.NgZone }, { token: MatterportMeasurementService }, { token: MatterportNavigationService }, { token: MatterportPointerService }, { token: MatterportObject3DService }, { token: MatterportTagService }], target: i0.ɵɵFactoryTarget.Injectable });
4207
4678
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MatterportService, providedIn: 'root' });
4208
4679
  }
4209
4680
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MatterportService, decorators: [{
@@ -4214,7 +4685,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
4214
4685
  }], ctorParameters: () => [{ type: Config, decorators: [{
4215
4686
  type: Inject,
4216
4687
  args: ['config']
4217
- }] }, { type: i1$1.Router }, { type: i1$1.ActivatedRoute }, { type: BaseVisibilityService }, { type: i0.NgZone }] });
4688
+ }] }, { type: i1$1.Router }, { type: i1$1.ActivatedRoute }, { type: BaseVisibilityService }, { type: i0.NgZone }, { type: MatterportMeasurementService }, { type: MatterportNavigationService }, { type: MatterportPointerService }, { type: MatterportObject3DService }, { type: MatterportTagService }] });
4218
4689
 
4219
4690
  /* eslint-disable no-await-in-loop */
4220
4691
  /* eslint-disable camelcase */
@@ -4275,21 +4746,50 @@ class ViewerService {
4275
4746
  setMode(indexMode) {
4276
4747
  this.viewerMode = indexMode;
4277
4748
  }
4278
- setTourUrl(model3d, showIconPlan = true, showIconDollhouse = true, showIconFloors = true, enableQuickStart = true) {
4279
- // this.tourUrl = `https://my.matterport.com/show/?m=${model3d}&play=1&hr=0&gt=0&title=0&log=0&brand=0&search=0&applicationKey=qn9wsasuy5h2fzrbrn1nzr0id&mds=0&newtags=1`;
4280
- this.tourUrl = `/assets/bundle/showcase.html?m=${model3d}&play=1&hr=0&gt=0&title=0&log=0&brand=0&search=0&applicationKey=qn9wsasuy5h2fzrbrn1nzr0id&mds=0&newtags=1`;
4749
+ setTourUrl(model3d, showIconPlan = true, showIconDollhouse = true, showIconFloors = true, showQuickStart = true, showFullscreen = true, showGuidedTour = true, showHighlightReel = true) {
4750
+ const baseUrl = `/assets/bundle/showcase.html`;
4751
+ const params = [];
4752
+ // Modèle
4753
+ params.push(`m=${model3d}`);
4754
+ // Comportement général
4755
+ params.push(`play=1`);
4756
+ params.push(`ui=1`);
4757
+ params.push(`title=0`);
4758
+ params.push(`log=0`);
4759
+ params.push(`brand=0`);
4760
+ params.push(`search=0`);
4761
+ params.push(`applicationKey=qn9wsasuy5h2fzrbrn1nzr0id`);
4762
+ params.push(`mds=0`);
4763
+ // Quick start
4764
+ if (showQuickStart) {
4765
+ params.push(`qs=1`);
4766
+ }
4767
+ // Floorplan (plan)
4281
4768
  if (!showIconPlan) {
4282
- this.tourUrl += '&fp=0';
4769
+ params.push(`fp=0`);
4283
4770
  }
4771
+ // Dollhouse (maison/poupée)
4284
4772
  if (!showIconDollhouse) {
4285
- this.tourUrl += '&dh=0';
4773
+ params.push(`dh=0`);
4286
4774
  }
4775
+ // Sélecteur d’étages
4287
4776
  if (!showIconFloors) {
4288
- this.tourUrl += '&f=0';
4777
+ params.push(`sf=0`);
4778
+ }
4779
+ // Guided tour
4780
+ if (!showGuidedTour) {
4781
+ params.push(`gt=0`);
4289
4782
  }
4290
- if (enableQuickStart) {
4291
- this.tourUrl += '&qs=1';
4783
+ // Highlight reel (bande du bas)
4784
+ if (!showHighlightReel) {
4785
+ // Selon ton bundle : hr=1 ou hr=0 masque
4786
+ params.push(`hr=1`);
4292
4787
  }
4788
+ // Plein écran
4789
+ if (!showFullscreen) {
4790
+ params.push(`fs=0`);
4791
+ }
4792
+ this.tourUrl = `${baseUrl}?${params.join("&")}`;
4293
4793
  }
4294
4794
  async clearAll() {
4295
4795
  // this.sweepToMove = null;
@@ -4552,19 +5052,15 @@ class ViewerService {
4552
5052
  .then(async (result) => {
4553
5053
  if (result) {
4554
5054
  const { sdk } = this.matterportService;
4555
- // TODO: add condition whether we want to enable it or not
4556
- // Additionnal extensions for Matterport
4557
- // 1. Security Camera with Frustum (FOV)
4558
- // 2. TODO register all new Matterport extensions here (nestSensor, lods, slots, ...)
4559
5055
  await Promise.all([
4560
5056
  sdk.Scene.register(securityCameraType, makeSecurityCamera.bind(sdk.Scene)),
4561
5057
  sdk.Scene.register(ViewFrustumMeshType, makeViewFrustumMesh.bind(sdk.Scene))
4562
5058
  ]);
5059
+ this.showingViewer = true;
4563
5060
  sdk.App.state.subscribe(function (appState) {
4564
- // app state has changed
4565
- // console.log('The current phase: ', appState.phase);
5061
+ console.debug('The current phase: ', appState.phase);
4566
5062
  if (appState.phase === sdk.App.Phase.PLAYING) {
4567
- // console.log('The app is playing');
5063
+ console.log('The app is playing');
4568
5064
  this.showingViewer = true;
4569
5065
  this.viewerIsOn.next({
4570
5066
  spaceID,
@@ -4578,6 +5074,16 @@ class ViewerService {
4578
5074
  this.rotationToMove = null;
4579
5075
  }
4580
5076
  }.bind(this));
5077
+ this.viewerIsOn.next({
5078
+ spaceID,
5079
+ model3D,
5080
+ sweep: this.sweepToMove,
5081
+ rotation: this.rotationToMove,
5082
+ });
5083
+ this.dataIsLoaded.next(true);
5084
+ this.isLoaded = true;
5085
+ this.sweepToMove = null;
5086
+ this.rotationToMove = null;
4581
5087
  return Promise.resolve();
4582
5088
  }
4583
5089
  return Promise.reject();
@@ -5012,6 +5518,23 @@ function openModalForVisitSwitch(modalService, model3D, spaceID, dataToMove = nu
5012
5518
  modalReference.componentInstance.dataToMove = dataToMove;
5013
5519
  }
5014
5520
  }
5521
+ function getCurrentLang(translate) {
5522
+ let lang = translate.getCurrentLang() || translate.getFallbackLang() || "fr";
5523
+ // Clean up leading mess
5524
+ lang = lang.replace(/^[_-]+/, '');
5525
+ // Handle fr_FR or fr-FR (take primary language)
5526
+ if (lang.includes("_")) {
5527
+ lang = lang.split("_")[0];
5528
+ }
5529
+ if (lang.includes("-")) {
5530
+ lang = lang.split("-")[0];
5531
+ }
5532
+ // Ensure 2-letter code (ISO 639-1)
5533
+ if (lang.length > 2) {
5534
+ lang = lang.substring(0, 2);
5535
+ }
5536
+ return lang;
5537
+ }
5015
5538
  /**
5016
5539
  * Return a string of a given date time in local format.
5017
5540
  * The format is determined by the current language.
@@ -5019,14 +5542,7 @@ function openModalForVisitSwitch(modalService, model3D, spaceID, dataToMove = nu
5019
5542
  function dateTimeToLocalString(date, translate, mode) {
5020
5543
  let lang = "en-EN";
5021
5544
  if (translate) {
5022
- /** Get current language */
5023
- lang = translate.currentLang
5024
- ? translate.currentLang
5025
- : translate.defaultLang;
5026
- /** Transforme '_fr' to 'fr' */
5027
- if (lang) {
5028
- lang = lang.slice(1);
5029
- }
5545
+ lang = getCurrentLang(translate);
5030
5546
  }
5031
5547
  let options;
5032
5548
  switch (mode) {
@@ -5155,11 +5671,7 @@ function translateDatePeriod(translateService, dateStartPeriod, dateEndPeriod) {
5155
5671
  * Return local in the canonical form: "en-GB", "fr-FR"
5156
5672
  */
5157
5673
  function getLocaleLong(translate) {
5158
- let lang = translate.currentLang
5159
- ? translate.currentLang
5160
- : translate.defaultLang;
5161
- /** Transform '_fr' to 'fr' */
5162
- lang = lang.slice(1);
5674
+ let lang = getCurrentLang(translate);
5163
5675
  if (lang === "en") {
5164
5676
  return "en-GB";
5165
5677
  }
@@ -5169,12 +5681,7 @@ function getLocaleLong(translate) {
5169
5681
  * Return local in the short form: "en", "fr"
5170
5682
  */
5171
5683
  function getLocaleShort(translate) {
5172
- let lang = translate.currentLang
5173
- ? translate.currentLang
5174
- : translate.defaultLang;
5175
- /** Transform '_fr' to 'fr' */
5176
- lang = lang.slice(1);
5177
- return lang;
5684
+ return getCurrentLang(translate);
5178
5685
  }
5179
5686
  /**
5180
5687
  * Format string to lowercase, no spaces
@@ -5243,6 +5750,12 @@ function durationToString(duration, translate) {
5243
5750
  }
5244
5751
  return `${seconds} ${secondsString}`;
5245
5752
  }
5753
+ function pointToString(point) {
5754
+ const x = point.x.toFixed(3);
5755
+ const y = point.y.toFixed(3);
5756
+ const z = point.z.toFixed(3);
5757
+ return `{ x: ${x}, y: ${y}, z: ${z} }`;
5758
+ }
5246
5759
 
5247
5760
  /* eslint-disable class-methods-use-this */
5248
5761
  class ZoneService {
@@ -6635,6 +7148,11 @@ class NavigatorService {
6635
7148
  // if first load and zones are not set
6636
7149
  this.zonesForUserForSpace = await this.zoneService.getZonesBySpaceForUser(this.currentSpaceID);
6637
7150
  this.audioZonesForUserForSpace = this.zonesForUserForSpace.filter((zone) => zone.audioID);
7151
+ this.zonesForUserForSpace = await this.zoneService.getZonesBySpaceForUser(this.currentSpaceID);
7152
+ this.audioZonesForUserForSpace = this.zonesForUserForSpace.filter((zone) => zone.audioID);
7153
+ if (!this.currentSweep) {
7154
+ this.currentSweep = this.matterportService.getCurrentSweep();
7155
+ }
6638
7156
  this.setZonesForCurrentSweep();
6639
7157
  }
6640
7158
  checkRotationForSweep(sweep) {
@@ -6648,7 +7166,10 @@ class NavigatorService {
6648
7166
  if (!this.currentSweep) {
6649
7167
  return;
6650
7168
  }
6651
- let zonesForSweep = this.zonesForUserForSpace?.filter((zone) => zone.sweepIDs && zone.sweepIDs.includes(this.currentSweep));
7169
+ const uuid = this.matterportService.getSweepUUIDForSid(this.currentSweep);
7170
+ console.log('uuid', uuid);
7171
+ const sweepToMatch = uuid ?? this.currentSweep;
7172
+ let zonesForSweep = this.zonesForUserForSpace?.filter((zone) => zone.sweepIDs && zone.sweepIDs.includes(sweepToMatch));
6652
7173
  const audioZones = this.audioZonesForUserForSpace?.filter((zone) => zone.sweepIDs && zone.sweepIDs.includes(this.currentSweep));
6653
7174
  this.audioZonesChange.next(audioZones);
6654
7175
  this.currentAudioZones = audioZones;
@@ -6939,7 +7460,7 @@ class TicketsService extends BaseObjectService {
6939
7460
  zone: [],
6940
7461
  space: [],
6941
7462
  };
6942
- ticketTags = new Subject();
7463
+ ticketTags = new BehaviorSubject(null);
6943
7464
  ticketsFiltered = [];
6944
7465
  ticketsUpdated = new Subject();
6945
7466
  currentSpaceID;
@@ -7391,7 +7912,6 @@ class TicketsService extends BaseObjectService {
7391
7912
  }
7392
7913
  }
7393
7914
  else {
7394
- /** Si on force le switch de la visit */
7395
7915
  window.open(`${url}?model3D=${result.model3D}&sweep=true`, '_blank');
7396
7916
  }
7397
7917
  }
@@ -7465,7 +7985,7 @@ class EquipmentService extends BaseObjectService {
7465
7985
  space: [],
7466
7986
  zonesMap: new Map(),
7467
7987
  };
7468
- equipmentsTags = new Subject();
7988
+ equipmentsTags = new BehaviorSubject(null);
7469
7989
  zoneIDFilter;
7470
7990
  currentZone;
7471
7991
  currentSpaceID;
@@ -7942,7 +8462,6 @@ class EquipmentService extends BaseObjectService {
7942
8462
  }
7943
8463
  }
7944
8464
  else {
7945
- /** Si on force le switch de la visit */
7946
8465
  window.open(`${url}?model3D=${result.model3D}&sweep=true`, '_blank');
7947
8466
  }
7948
8467
  }
@@ -8461,13 +8980,8 @@ class TimeDateToLocalStringPipe {
8461
8980
  let lang = "en-EN";
8462
8981
  if (this.translate) {
8463
8982
  /** Get current language */
8464
- lang = this.translate.getCurrentLang()
8465
- ? this.translate.getCurrentLang()
8466
- : this.translate.getFallbackLang();
8467
- /** Transforme '_fr' to 'fr' */
8468
- if (lang) {
8469
- lang = lang.slice(1);
8470
- }
8983
+ const currentLang = this.translate.getCurrentLang() || this.translate.getFallbackLang() || "fr";
8984
+ lang = currentLang.replace(/^[_-]+/, '').substring(0, 2);
8471
8985
  }
8472
8986
  let options;
8473
8987
  switch (mode) {
@@ -8802,10 +9316,8 @@ class LocaleService {
8802
9316
  this.translate.use(`_${lang}`); // because files are "_en.json"
8803
9317
  }
8804
9318
  getLocale() {
8805
- const lang = this.translate.getCurrentLang()
8806
- ? this.translate.getCurrentLang()
8807
- : this.translate.getFallbackLang();
8808
- return lang.slice(1);
9319
+ const currentLang = this.translate.getCurrentLang() || this.translate.getFallbackLang() || "fr";
9320
+ return currentLang.replace(/^[_-]+/, '').substring(0, 2);
8809
9321
  }
8810
9322
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: LocaleService, deps: [{ token: i1.TranslateService }], target: i0.ɵɵFactoryTarget.Injectable });
8811
9323
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: LocaleService, providedIn: "root" });
@@ -10278,7 +10790,7 @@ class FeatureService extends BaseObjectService {
10278
10790
  space: [],
10279
10791
  zone: [],
10280
10792
  };
10281
- featureTags = new Subject();
10793
+ featureTags = new BehaviorSubject(null);
10282
10794
  featureFiltered = [];
10283
10795
  updateDone = new Subject();
10284
10796
  zoneIDFilter;
@@ -10649,7 +11161,7 @@ class MeasurementService {
10649
11161
  navigationService;
10650
11162
  path = 'measurements/';
10651
11163
  currentMeasurements = { space: [], zone: [] };
10652
- measurementsTags = new Subject();
11164
+ measurementsTags = new BehaviorSubject(null);
10653
11165
  measurementsUpdated = new Subject();
10654
11166
  currentSpaceID;
10655
11167
  zoneIDFilter;
@@ -11804,10 +12316,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
11804
12316
  /* eslint-disable class-methods-use-this */
11805
12317
  class BaseTagService {
11806
12318
  constructor() { }
12319
+ /** Base implementation for generating tag HTML. Override in derived classes. */
11807
12320
  async getHtmlToInject(tagType, object) {
11808
- throw new Error(`getHtmlToInject is not implemented in the base class.
11809
- Please override this method in your extended class.`);
12321
+ return "";
12322
+ }
12323
+ /** Base implementation for injecting HTML into a tag container. */
12324
+ async injectHtml(container, tagType, object) {
12325
+ if (!container) {
12326
+ console.warn('injectHtml: container is null');
12327
+ return;
12328
+ }
12329
+ try {
12330
+ const html = await this.getHtmlToInject(tagType, object);
12331
+ if (!html || html.trim().length === 0) {
12332
+ console.warn(`Empty HTML returned for ${tagType}`);
12333
+ container.innerHTML = `<div class="tag-div" style="color: white; padding: 10px;">Base implementation: No content for ${tagType}</div>`;
12334
+ }
12335
+ else {
12336
+ container.innerHTML = html;
12337
+ }
12338
+ }
12339
+ catch (error) {
12340
+ console.error('Error in injectHtml:', error);
12341
+ container.innerHTML = `<div class="tag-div" style="color: white; padding: 10px;">Error: ${error.message}</div>`;
12342
+ }
11810
12343
  }
12344
+ /** Returns the navigation URL for viewing details of a specific object type. */
11811
12345
  getUrlForSeeDetails(object, tagType) {
11812
12346
  switch (tagType) {
11813
12347
  case PoiType.TICKET: {
@@ -11829,28 +12363,31 @@ class BaseTagService {
11829
12363
  return '';
11830
12364
  }
11831
12365
  }
12366
+ /*async updatePoiMetadata(poiId: string, metadata: string): Promise<void> {
12367
+ throw new Error('updatePoiMetadata is not implemented in the base class.');
12368
+ }*/
11832
12369
  async prepareEquipmentHtml(equip) {
11833
- throw new Error(`prepareEquipmentHtml is not implemented in the base class.
12370
+ throw new Error(`prepareEquipmentHtml is not implemented in the base class.
11834
12371
  Please override this method in your extended class.`);
11835
12372
  }
11836
12373
  async prepareTicketHtml(ticket) {
11837
- throw new Error(`prepareTicketHtml is not implemented in the base class.
12374
+ throw new Error(`prepareTicketHtml is not implemented in the base class.
11838
12375
  Please override this method in your extended class.`);
11839
12376
  }
11840
12377
  async prepareFeatureHtml(feature) {
11841
- throw new Error(`prepareFeatureHtml is not implemented in the base class.
12378
+ throw new Error(`prepareFeatureHtml is not implemented in the base class.
11842
12379
  Please override this method in your extended class.`);
11843
12380
  }
11844
12381
  async prepareMeasurementHtml(measure) {
11845
- throw new Error(`prepareMeasurementHtml is not implemented in the base class.
12382
+ throw new Error(`prepareMeasurementHtml is not implemented in the base class.
11846
12383
  Please override this method in your extended class.`);
11847
12384
  }
11848
12385
  async prepareDeskHtml(feature) {
11849
- throw new Error(`prepareDeskHtml is not implemented in the base class.
12386
+ throw new Error(`prepareDeskHtml is not implemented in the base class.
11850
12387
  Please override this method in your extended class.`);
11851
12388
  }
11852
12389
  async prepareIndicatorHtml(feature) {
11853
- throw new Error(`prepareDeskHtml is not implemented in the base class.
12390
+ throw new Error(`prepareDeskHtml is not implemented in the base class.
11854
12391
  Please override this method in your extended class.`);
11855
12392
  }
11856
12393
  async getSignedTagIconSource(tagIconSrc) {
@@ -11859,31 +12396,32 @@ class BaseTagService {
11859
12396
  getIconTagImageForFeature(feature, poi) {
11860
12397
  // const tagIcon = JSON.parse(poi.tagIcon);
11861
12398
  // return tagIcon.src;
11862
- throw new Error(`getIconTagImageForFeature is not implemented in the base class.
12399
+ throw new Error(`getIconTagImageForFeature is not implemented in the base class.
11863
12400
  Please override this method in your extended class.`);
11864
12401
  }
11865
12402
  getScriptForTag(object, tagType) {
11866
- throw new Error(`getScriptForTag is not implemented in the base class.
12403
+ throw new Error(`getScriptForTag is not implemented in the base class.
11867
12404
  Please override this method in your extended class.`);
11868
12405
  }
11869
12406
  getAnnexeForCommentTypeInFeature(feature, commentType) {
11870
- throw new Error(`getAnnexeForCommentTypeInFeature is not implemented in the base class.
12407
+ throw new Error(`getAnnexeForCommentTypeInFeature is not implemented in the base class.
11871
12408
  Please override this method in your extended class.`);
11872
12409
  }
11873
12410
  getBillboardMediaToEmbed(object) {
11874
- throw new Error(`getBillboardMediaToEmbed is not implemented in the base class.
11875
- Please override this method in your extended class.`);
12411
+ // Default implementation to prevent crash if base class is instantiated directly.
12412
+ // Extended classes should override this method.
12413
+ return { comment: null, tagDescription: '' };
11876
12414
  }
11877
12415
  onActionDetailClick(url) {
11878
- throw new Error(`onActionDetailClick is not implemented in the base class.
12416
+ throw new Error(`onActionDetailClick is not implemented in the base class.
11879
12417
  Please override this method in your extended class.`);
11880
12418
  }
11881
12419
  onActionAudioClick(audioCommentID) {
11882
- throw new Error(`onActionDetailClick is not implemented in the base class.
12420
+ throw new Error(`onActionDetailClick is not implemented in the base class.
11883
12421
  Please override this method in your extended class.`);
11884
12422
  }
11885
12423
  onActionVideoClick(url) {
11886
- throw new Error(`onActionVideoClick is not implemented in the base class.
12424
+ throw new Error(`onActionVideoClick is not implemented in the base class.
11887
12425
  Please override this method in your extended class.`);
11888
12426
  }
11889
12427
  onActionImageClick(imageCommentID) {
@@ -11891,11 +12429,11 @@ class BaseTagService {
11891
12429
  Please override this method in your extended class.`);
11892
12430
  }
11893
12431
  onActionDocClick(url) {
11894
- throw new Error(`onActionDocClick is not implemented in the base class.
12432
+ throw new Error(`onActionDocClick is not implemented in the base class.
11895
12433
  Please override this method in your extended class.`);
11896
12434
  }
11897
12435
  onActionYoutubeClick(url) {
11898
- throw new Error(`onActionYoutubeClick is not implemented in the base class.
12436
+ throw new Error(`onActionYoutubeClick is not implemented in the base class.
11899
12437
  Please override this method in your extended class.`);
11900
12438
  }
11901
12439
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: BaseTagService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
@@ -12022,7 +12560,7 @@ class MatterportImportService {
12022
12560
  return new Promise((res, rej) => {
12023
12561
  iframe.addEventListener('load', async () => {
12024
12562
  try {
12025
- this.sdk = await showcaseWindow.MP_SDK.connect(iframe, 'qn9wsasuy5h2fzrbrn1nzr0id', '3.11');
12563
+ this.sdk = await showcaseWindow.MP_SDK.connect(iframe, 'qn9wsasuy5h2fzrbrn1nzr0id', '');
12026
12564
  // Subscribe to Floor data
12027
12565
  this.sdk.Floor.data.subscribe({
12028
12566
  onCollectionUpdated: function upd(collection) {
@@ -12152,7 +12690,7 @@ class MatterportImportService {
12152
12690
  const start = overrideExisting ? 0 : await this.getUploadedImageCount(this.modelID);
12153
12691
  for (let index = start; index < nmbScans; index += 1) {
12154
12692
  if (!this.stop) {
12155
- await this.sdk.Sweep.moveTo(scans[index].uuid, { rotation: { x: 0, y: 0 }, transition: this.sdk.Sweep.Transition.INSTANT, transitionTime: 0 });
12693
+ await this.sdk.Sweep.moveTo(scans[index].uuid, { rotation: { x: 0, y: 0 }, transition: this.sdk.Camera.TransitionType.INSTANT, transitionTime: 0 });
12156
12694
  const img = await this.sdk.Renderer.takeEquirectangular({ width: 2048, height: 1024 }, { mattertags: false, sweeps: true });
12157
12695
  /**Upload on S3 are asynchronous, in order to no slow down the process*/
12158
12696
  uploadBase64ImageWithRetry(img, scans[index].uuid, `visits/${this.modelID}/sweeps/`, 'sweep', 5).then((r) => {
@@ -12875,5 +13413,5 @@ function floatValidator() {
12875
13413
  * Generated bundle index. Do not edit.
12876
13414
  */
12877
13415
 
12878
- export { AffectationService, AvatarComponent, BaseLateralTabService, BaseTagService, BaseUserService, BaseVisibilityService, CameraMode, CaptureService, CaptureViewer, CommentService, CommentType, Config, ContentService, CsvExportComponent, DomainService, DomainType, DurationToStringPipe, EmailStatus, EquipmentService, EventService, EventStatus, EventType, FeatureService, FeatureType, FilterService, HashtagFromIdPipe, HashtagService, InterventionService, InventoryStatus, LayerService, LevelStatus, LoaderComponent, Locale, LocaleService, MatterportImportService, MatterportService, MattertagActionMode, MattertagData, MeasurementService, MenuBarComponent, MissionService, NavigationService, NavigatorService, NgxSmarterplanCoreModule, NgxSmarterplanCoreService, NodeService, Object3DService, OperationService, OrganisationService, PaymentStatus, PlanService, PoiService, PoiType, ProfileEntity, ProfileService, ProfileStatus, PropertyService, PropertyType, RoleStatus, SafeUrlPipe, SearchBarComponent, SearchObjectType, SearchService, SpModule, SpaceService, SpaceStatus, StatusEquipment, SupportModalComponent, SupportService, TagAction, TemplateService, TicketPriority, TicketStatus, TicketType, TicketsService, TimeDateToLocalStringPipe, TypeNote, UsernameFromIdPipe, ValidatorsService, ViewerInteractions, ViewerService, VisitService, ZoneChangeService, ZoneService, arraysContainSameElements, checkElementById, convertTimestampToLocalZone, dateHasExpired, dateTimeToLocalString, deleteFromS3, downloadBlob, downloadEquipmentDocument, downloadFile, downloadFileAsObject, durationToString, emailValidator, enumToArray, filterUniqueArrayByID, floatValidator, getBufferForFileFromS3, getCoefficientsForImage, getDistanceBetweenTwoPoints, getHighestLevelForMissions, getHighestRoleForMissions, getLevelsBelow, getLocaleLong, getLocaleShort, getMetaForImage, getRolesBelowForManager, getSignedFile, getSignedImageUrlForEquipment, getSignedImageUrlForProfile, getSignedImageUrlForSpace, getSpaceIDFromUrl, getVisitUrl, isEmptyObject, listFilesInFolder, mean, noEmptyValidator, numberToDateString, openDocument, openModalForVisitSwitch, poiTypeToString, removeAllFilesFromFolderS3, removeNullKeysFromObject, showScanPointsOnPlanInDiv, shuffleArray, sortAlphabeticallyOnName, stringLocaleToEnum, stringToLowercaseNoSpaces, styleButton, textValidator, translateDatePeriod, uploadBase64Image, uploadBase64ImageWithRetry, uploadFileToS3, uploadJsonToS3, validEmail, wait, waitUntil };
13416
+ export { AffectationService, AvatarComponent, BaseLateralTabService, BaseTagService, BaseUserService, BaseVisibilityService, CameraMode, CaptureService, CaptureViewer, CommentService, CommentType, Config, ContentService, CsvExportComponent, DomainService, DomainType, DurationToStringPipe, EmailStatus, EquipmentService, EventService, EventStatus, EventType, FeatureService, FeatureType, FilterService, HashtagFromIdPipe, HashtagService, InterventionService, InventoryStatus, LayerService, LevelStatus, LoaderComponent, Locale, LocaleService, MatterportImportService, MatterportService, MattertagActionMode, MattertagData, MeasurementService, MenuBarComponent, MissionService, NavigationService, NavigatorService, NgxSmarterplanCoreModule, NgxSmarterplanCoreService, NodeService, Object3DService, OperationService, OrganisationService, PaymentStatus, PlanService, PoiService, PoiType, ProfileEntity, ProfileService, ProfileStatus, PropertyService, PropertyType, RoleStatus, SafeUrlPipe, SearchBarComponent, SearchObjectType, SearchService, SpModule, SpaceService, SpaceStatus, StatusEquipment, SupportModalComponent, SupportService, TagAction, TemplateService, TicketPriority, TicketStatus, TicketType, TicketsService, TimeDateToLocalStringPipe, TypeNote, UsernameFromIdPipe, ValidatorsService, ViewerInteractions, ViewerService, VisitService, ZoneChangeService, ZoneService, arraysContainSameElements, checkElementById, convertTimestampToLocalZone, dateHasExpired, dateTimeToLocalString, deleteFromS3, downloadBlob, downloadEquipmentDocument, downloadFile, downloadFileAsObject, durationToString, emailValidator, enumToArray, filterUniqueArrayByID, floatValidator, getBufferForFileFromS3, getCoefficientsForImage, getCurrentLang, getDistanceBetweenTwoPoints, getHighestLevelForMissions, getHighestRoleForMissions, getLevelsBelow, getLocaleLong, getLocaleShort, getMetaForImage, getRolesBelowForManager, getSignedFile, getSignedImageUrlForEquipment, getSignedImageUrlForProfile, getSignedImageUrlForSpace, getSpaceIDFromUrl, getVisitUrl, isEmptyObject, listFilesInFolder, mean, noEmptyValidator, numberToDateString, openDocument, openModalForVisitSwitch, poiTypeToString, pointToString, removeAllFilesFromFolderS3, removeNullKeysFromObject, showScanPointsOnPlanInDiv, shuffleArray, sortAlphabeticallyOnName, stringLocaleToEnum, stringToLowercaseNoSpaces, styleButton, textValidator, translateDatePeriod, uploadBase64Image, uploadBase64ImageWithRetry, uploadFileToS3, uploadJsonToS3, validEmail, wait, waitUntil };
12879
13417
  //# sourceMappingURL=smarterplan-ngx-smarterplan-core.mjs.map