@smarterplan/ngx-smarterplan-core 1.4.20 → 1.4.21

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 (99) hide show
  1. package/README.md +24 -24
  2. package/esm2022/lib/components/csv-export/csv-export.component.mjs +3 -3
  3. package/esm2022/lib/components/loader/loader.component.mjs +3 -3
  4. package/esm2022/lib/components/menu-bar/avatar/avatar.component.mjs +1 -1
  5. package/esm2022/lib/components/menu-bar/menu-bar.component.mjs +3 -3
  6. package/esm2022/lib/components/menu-bar/navigation-bar/navigation-bar.component.mjs +3 -3
  7. package/esm2022/lib/components/modal-switch-visit/modal-switch-visit.component.mjs +3 -3
  8. package/esm2022/lib/components/search-bar/search-bar.component.mjs +3 -3
  9. package/esm2022/lib/components/support-modal/support-modal.component.mjs +3 -3
  10. package/esm2022/lib/config.mjs +1 -1
  11. package/esm2022/lib/helpers.service.mjs +1 -1
  12. package/esm2022/lib/matterport-extensions/nest-thermostat/CanvasImage.mjs +1 -1
  13. package/esm2022/lib/matterport-extensions/nest-thermostat/CanvasRenderer.mjs +1 -1
  14. package/esm2022/lib/matterport-extensions/nest-thermostat/NestThermostat.mjs +1 -1
  15. package/esm2022/lib/matterport-extensions/nest-thermostat/PlaneRenderer.mjs +1 -1
  16. package/esm2022/lib/matterport-extensions/scene-component/SceneComponent.mjs +1 -1
  17. package/esm2022/lib/matterport-extensions/security-camera/SecurityCamera.mjs +49 -28
  18. package/esm2022/lib/matterport-extensions/tv-player/TvPlayer.mjs +1 -1
  19. package/esm2022/lib/matterport-extensions/view-frustum-mesh/ViewFrustumMesh.mjs +22 -22
  20. package/esm2022/lib/mattertagData.mjs +1 -1
  21. package/esm2022/lib/ngx-smarterplan-core.module.mjs +1 -1
  22. package/esm2022/lib/ngx-smarterplan-core.service.mjs +1 -1
  23. package/esm2022/lib/pipes/duration-to-string.pipe.mjs +1 -1
  24. package/esm2022/lib/pipes/format-date-number-to-digits.pipe.mjs +1 -1
  25. package/esm2022/lib/pipes/hashtag-from-id.pipe.mjs +1 -1
  26. package/esm2022/lib/pipes/safe-url.pipe.mjs +1 -1
  27. package/esm2022/lib/pipes/time-date-to-local-string.pipe.mjs +1 -1
  28. package/esm2022/lib/pipes/username-from-id.pipe.mjs +1 -1
  29. package/esm2022/lib/services/amplify-cache.service.mjs +1 -1
  30. package/esm2022/lib/services/base-tab.service.mjs +1 -1
  31. package/esm2022/lib/services/baseVisibility.service.mjs +1 -1
  32. package/esm2022/lib/services/content.service.mjs +1 -1
  33. package/esm2022/lib/services/filter.service.mjs +1 -1
  34. package/esm2022/lib/services/intervention.service.mjs +1 -1
  35. package/esm2022/lib/services/locale.service.mjs +1 -1
  36. package/esm2022/lib/services/matterport-import.service.mjs +1 -1
  37. package/esm2022/lib/services/matterport-measurement.service.mjs +1 -1
  38. package/esm2022/lib/services/matterport-navigation.service.mjs +1 -1
  39. package/esm2022/lib/services/matterport-object3d.service.mjs +657 -133
  40. package/esm2022/lib/services/matterport-pointer.service.mjs +6 -6
  41. package/esm2022/lib/services/matterport-tag-copy.service.mjs +1 -1
  42. package/esm2022/lib/services/matterport-tag.service.mjs +1 -1
  43. package/esm2022/lib/services/matterport.service.mjs +346 -25
  44. package/esm2022/lib/services/models/affectation.service.mjs +1 -1
  45. package/esm2022/lib/services/models/base-object.service.mjs +1 -1
  46. package/esm2022/lib/services/models/capture.service.mjs +1 -1
  47. package/esm2022/lib/services/models/comment.service.mjs +1 -1
  48. package/esm2022/lib/services/models/domain.service.mjs +1 -1
  49. package/esm2022/lib/services/models/equipment.service.mjs +1 -1
  50. package/esm2022/lib/services/models/event.service.mjs +1 -1
  51. package/esm2022/lib/services/models/feature.service.mjs +1 -1
  52. package/esm2022/lib/services/models/hashtag.service.mjs +1 -1
  53. package/esm2022/lib/services/models/layer.service.mjs +1 -1
  54. package/esm2022/lib/services/models/measurement.service.mjs +1 -1
  55. package/esm2022/lib/services/models/mission.service.mjs +1 -1
  56. package/esm2022/lib/services/models/navigation.service.mjs +1 -1
  57. package/esm2022/lib/services/models/node.service.mjs +1 -1
  58. package/esm2022/lib/services/models/object3D.service.mjs +1 -1
  59. package/esm2022/lib/services/models/operation.service.mjs +1 -1
  60. package/esm2022/lib/services/models/organisation.service.mjs +1 -1
  61. package/esm2022/lib/services/models/plan.service.mjs +1 -1
  62. package/esm2022/lib/services/models/poi.service.mjs +27 -27
  63. package/esm2022/lib/services/models/profile.service.mjs +1 -1
  64. package/esm2022/lib/services/models/property.service.mjs +1 -1
  65. package/esm2022/lib/services/models/space.service.mjs +1 -1
  66. package/esm2022/lib/services/models/template.service.mjs +1 -1
  67. package/esm2022/lib/services/models/ticket.service.mjs +1 -1
  68. package/esm2022/lib/services/models/visit.service.mjs +1 -1
  69. package/esm2022/lib/services/models/zone.service.mjs +1 -1
  70. package/esm2022/lib/services/navigator.service.mjs +1 -1
  71. package/esm2022/lib/services/s3.service.mjs +1 -1
  72. package/esm2022/lib/services/search.service.mjs +1 -1
  73. package/esm2022/lib/services/support.service.mjs +1 -1
  74. package/esm2022/lib/services/sweep-locator.service.mjs +1 -1
  75. package/esm2022/lib/services/tag.service.mjs +16 -16
  76. package/esm2022/lib/services/threejs-import.service.mjs +1 -1
  77. package/esm2022/lib/services/threejs.service.mjs +1 -1
  78. package/esm2022/lib/services/user.service.mjs +1 -1
  79. package/esm2022/lib/services/utils/matterport-framing.util.mjs +63 -0
  80. package/esm2022/lib/services/utils/matterport-transform.util.mjs +1 -1
  81. package/esm2022/lib/services/validators.service.mjs +1 -1
  82. package/esm2022/lib/services/viewer.service.mjs +1 -1
  83. package/esm2022/lib/services/zone-drawer.service.mjs +1 -1
  84. package/esm2022/lib/services/zoneChange.service.mjs +1 -1
  85. package/esm2022/lib/tokens/matterport-sdk-key.token.mjs +1 -1
  86. package/esm2022/lib/types.service.mjs +3 -1
  87. package/esm2022/lib/validators/email.directive.mjs +1 -1
  88. package/esm2022/lib/validators/no-empty.directive.mjs +1 -1
  89. package/esm2022/lib/validators/number.directive.mjs +1 -1
  90. package/esm2022/lib/validators/text.directive.mjs +1 -1
  91. package/esm2022/public-api.mjs +1 -1
  92. package/fesm2022/smarterplan-ngx-smarterplan-core.mjs +1192 -263
  93. package/fesm2022/smarterplan-ngx-smarterplan-core.mjs.map +1 -1
  94. package/lib/services/matterport-object3d.service.d.ts +125 -5
  95. package/lib/services/matterport.service.d.ts +106 -7
  96. package/lib/services/utils/matterport-framing.util.d.ts +40 -0
  97. package/lib/types.service.d.ts +42 -1
  98. package/package.json +1 -1
  99. package/src/assets/vs-app.json +67 -67
@@ -8,7 +8,7 @@ import { NgbDate, NgbModule } from '@ng-bootstrap/ng-bootstrap';
8
8
  import * as i1$1 from '@angular/router';
9
9
  import { RouterModule } from '@angular/router';
10
10
  import * as THREE from 'three';
11
- import { Matrix4, Vector3, Color, Object3D, Euler, PerspectiveCamera, CylinderBufferGeometry, BoxGeometry, MeshBasicMaterial, NormalBlending, BackSide, Mesh, EdgesGeometry, AdditiveBlending, ShaderMaterial, AnimationMixer, VectorKeyframeTrack, InterpolateSmooth, AnimationClip, LoopOnce, Group, PlaneBufferGeometry, CanvasTexture, Texture, LinearFilter } from 'three';
11
+ import { Matrix4, Vector3, Color, Object3D, Euler, PerspectiveCamera, CylinderBufferGeometry, BoxGeometry, MeshBasicMaterial, NormalBlending, BackSide, Mesh, EdgesGeometry, AdditiveBlending, ShaderMaterial, AnimationMixer, VectorKeyframeTrack, InterpolateSmooth, AnimationClip, LoopOnce, Group, PlaneBufferGeometry, CanvasTexture, Texture, LinearFilter, Box3, MathUtils } from 'three';
12
12
  import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js';
13
13
  import { Storage, Auth, API, graphqlOperation, Cache } from 'aws-amplify';
14
14
  import { DateTime } from 'luxon';
@@ -90,11 +90,11 @@ class CsvExportComponent {
90
90
  link.remove();
91
91
  }
92
92
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CsvExportComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
93
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: CsvExportComponent, selector: "lib-csv-export", inputs: { headers: "headers", rowsInput: "rowsInput", getRows: "getRows", title: "title", buttonLabel: "buttonLabel" }, ngImport: i0, template: "<span [style.cursor]=\"'pointer'\" (click)=\"onClickDownload()\" class=\"download-csv d-flex\">\n <span class=\"iconify\" data-icon=\"mdi:file-download-outline\"></span>\n {{ buttonLabel | translate}}\n</span>\n", styles: [".download-csv{color:var(--smarterplan-primary)}\n"], dependencies: [{ kind: "pipe", type: i1.TranslatePipe, name: "translate" }] });
93
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: CsvExportComponent, selector: "lib-csv-export", inputs: { headers: "headers", rowsInput: "rowsInput", getRows: "getRows", title: "title", buttonLabel: "buttonLabel" }, ngImport: i0, template: "<span [style.cursor]=\"'pointer'\" (click)=\"onClickDownload()\" class=\"download-csv d-flex\">\r\n <span class=\"iconify\" data-icon=\"mdi:file-download-outline\"></span>\r\n {{ buttonLabel | translate}}\r\n</span>\r\n", styles: [".download-csv{color:var(--smarterplan-primary)}\n"], dependencies: [{ kind: "pipe", type: i1.TranslatePipe, name: "translate" }] });
94
94
  }
95
95
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CsvExportComponent, decorators: [{
96
96
  type: Component,
97
- args: [{ selector: 'lib-csv-export', template: "<span [style.cursor]=\"'pointer'\" (click)=\"onClickDownload()\" class=\"download-csv d-flex\">\n <span class=\"iconify\" data-icon=\"mdi:file-download-outline\"></span>\n {{ buttonLabel | translate}}\n</span>\n", styles: [".download-csv{color:var(--smarterplan-primary)}\n"] }]
97
+ args: [{ selector: 'lib-csv-export', template: "<span [style.cursor]=\"'pointer'\" (click)=\"onClickDownload()\" class=\"download-csv d-flex\">\r\n <span class=\"iconify\" data-icon=\"mdi:file-download-outline\"></span>\r\n {{ buttonLabel | translate}}\r\n</span>\r\n", styles: [".download-csv{color:var(--smarterplan-primary)}\n"] }]
98
98
  }], propDecorators: { headers: [{
99
99
  type: Input
100
100
  }], rowsInput: [{
@@ -386,6 +386,8 @@ var ViewerInteractions;
386
386
  ViewerInteractions[ViewerInteractions["DEFAULT"] = 0] = "DEFAULT";
387
387
  ViewerInteractions[ViewerInteractions["ADDING"] = 1] = "ADDING";
388
388
  ViewerInteractions[ViewerInteractions["POSITIONING"] = 2] = "POSITIONING";
389
+ /** A loaded 3D object follows the cursor until the user clicks (validate) or cancels */
390
+ ViewerInteractions[ViewerInteractions["PLACING_OBJECT3D"] = 3] = "PLACING_OBJECT3D";
389
391
  })(ViewerInteractions || (ViewerInteractions = {}));
390
392
  class ProfileEntity {
391
393
  id;
@@ -633,7 +635,9 @@ class SecurityCamera extends SceneComponent {
633
635
  this.makeHighlight();
634
636
  // attach highighting to the mesh it represents
635
637
  // TODO (scene query): this is very brittle but is the only way to attach something to a room of the model
636
- this.outputs.objectRoot.parent.parent.add(this.highlight);
638
+ if (this.highlight) {
639
+ this.outputs.objectRoot.parent.parent.add(this.highlight);
640
+ }
637
641
  // create the animation mixer to rotate the camera
638
642
  this.makeAnimation();
639
643
  }
@@ -672,6 +676,12 @@ class SecurityCamera extends SceneComponent {
672
676
  this.edges = null;
673
677
  }
674
678
  function edgesToCylinders(edgesGeometry, thickness) {
679
+ // API to be updated in bundle - JSSDK-1222
680
+ const merge = THREE.mergeBufferGeometries ?? THREE.BufferGeometryUtils?.mergeBufferGeometries;
681
+ // not exported by the three.js of recent SDK bundles: the caller falls back to plain lines
682
+ if (!merge)
683
+ return null;
684
+ const Cylinder = THREE.CylinderGeometry ?? THREE.CylinderBufferGeometry;
675
685
  const { position } = edgesGeometry.attributes;
676
686
  const { array, count } = position;
677
687
  const r = thickness / 2;
@@ -681,15 +691,14 @@ class SecurityCamera extends SceneComponent {
681
691
  const b = new THREE.Vector3(array[i + 3], array[i + 4], array[i + 5]);
682
692
  const vec = new THREE.Vector3().subVectors(b, a);
683
693
  const len = vec.length();
684
- const geom = new THREE.CylinderBufferGeometry(r, r, len, 8);
694
+ const geom = new Cylinder(r, r, len, 8);
685
695
  geom.translate(0, len / 2, 0);
686
696
  geom.rotateX(Math.PI / 2);
687
697
  geom.lookAt(vec);
688
698
  geom.translate(a.x, a.y, a.z);
689
699
  geoms.push(geom);
690
700
  }
691
- // API to be updated in bundle - JSSDK-1222
692
- return THREE.mergeBufferGeometries(geoms);
701
+ return merge(geoms);
693
702
  }
694
703
  const frustumLength = this.inputs.farPlane - this.inputs.nearPlane;
695
704
  const boxGeometry = new THREE.BoxGeometry(2, 2, frustumLength);
@@ -725,6 +734,16 @@ class SecurityCamera extends SceneComponent {
725
734
  });
726
735
  this.box = new THREE.Mesh(boxGeometry, boxMaterial);
727
736
  const edgesGeometry = edgesToCylinders(new THREE.EdgesGeometry(boxGeometry), 0.015);
737
+ if (!edgesGeometry) {
738
+ // thick edges need mergeBufferGeometries: draw the frustum with lines
739
+ this.edges = new THREE.LineSegments(new THREE.EdgesGeometry(boxGeometry), new THREE.LineBasicMaterial({
740
+ color: this.inputs.color,
741
+ opacity: 0.8,
742
+ transparent: true,
743
+ depthWrite: false,
744
+ }));
745
+ return;
746
+ }
728
747
  this.edges = new THREE.Mesh(edgesGeometry, new THREE.MeshBasicMaterial({
729
748
  color: 0xffffff,
730
749
  opacity: 0.25,
@@ -745,8 +764,12 @@ class SecurityCamera extends SceneComponent {
745
764
  makeHighlight() {
746
765
  const THREE = this.context.three;
747
766
  // TODO (scene query): this is very brittle and hardcoded but is the only way to get room geometries until we have a better way to query the scene graph
748
- const floorMesh = this.outputs.objectRoot.parent.parent.getObjectByName('FloorMesh:0');
749
- const roomMesh = floorMesh.children[0];
767
+ const floorMesh = this.outputs.objectRoot.parent?.parent?.getObjectByName('FloorMesh:0');
768
+ const roomMesh = floorMesh?.children[0];
769
+ if (!roomMesh?.geometry) {
770
+ // no room mesh found in the scene graph: the frustum is shown without the highlight
771
+ return;
772
+ }
750
773
  updateHighlightUniforms$1(this.projector, this.highlightUniforms);
751
774
  this.highlightUniforms.color.value.setHex(this.inputs.color);
752
775
  const shader = new THREE.ShaderMaterial({
@@ -798,30 +821,30 @@ class SecurityCamera extends SceneComponent {
798
821
  }
799
822
  }
800
823
  function vertexShader$1() {
801
- return `
802
- uniform mat4 lightMatrix;
803
-
804
- varying vec4 vLightCoords;
805
-
806
- void main() {
807
- // create a UV region [0,1] x [0,1] to represent what the lightMatrix "sees"
808
- vLightCoords = lightMatrix * modelMatrix * vec4(position, 1.0);
809
- gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
810
- }
824
+ return `
825
+ uniform mat4 lightMatrix;
826
+
827
+ varying vec4 vLightCoords;
828
+
829
+ void main() {
830
+ // create a UV region [0,1] x [0,1] to represent what the lightMatrix "sees"
831
+ vLightCoords = lightMatrix * modelMatrix * vec4(position, 1.0);
832
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
833
+ }
811
834
  `;
812
835
  }
813
836
  function fragmentShader$1() {
814
- return `
815
- uniform vec3 color;
816
-
817
- varying vec4 vLightCoords;
818
-
819
- void main() {
820
- vec2 lightUV = vLightCoords.xy / vLightCoords.w;
821
- float inView = float(max(lightUV.x, lightUV.y) <= 1.0 && min(lightUV.x, lightUV.y) >= 0.0 && vLightCoords.z > 0.0);
822
-
823
- gl_FragColor = vec4(color, min(inView, 0.2));
824
- }
837
+ return `
838
+ uniform vec3 color;
839
+
840
+ varying vec4 vLightCoords;
841
+
842
+ void main() {
843
+ vec2 lightUV = vLightCoords.xy / vLightCoords.w;
844
+ float inView = float(max(lightUV.x, lightUV.y) <= 1.0 && min(lightUV.x, lightUV.y) >= 0.0 && vLightCoords.z > 0.0);
845
+
846
+ gl_FragColor = vec4(color, min(inView, 0.2));
847
+ }
825
848
  `;
826
849
  }
827
850
  function updateHighlightUniforms$1(projector, uniforms) {
@@ -1014,30 +1037,30 @@ class ViewFrustumMesh extends SceneComponent {
1014
1037
  }
1015
1038
  }
1016
1039
  function vertexShader() {
1017
- return `
1018
- uniform mat4 lightMatrix;
1019
-
1020
- varying vec4 vLightCoords;
1021
-
1022
- void main() {
1023
- // create a UV region [0,1] x [0,1] to represent what the lightMatrix "sees"
1024
- vLightCoords = lightMatrix * modelMatrix * vec4(position, 1.0);
1025
- gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
1026
- }
1040
+ return `
1041
+ uniform mat4 lightMatrix;
1042
+
1043
+ varying vec4 vLightCoords;
1044
+
1045
+ void main() {
1046
+ // create a UV region [0,1] x [0,1] to represent what the lightMatrix "sees"
1047
+ vLightCoords = lightMatrix * modelMatrix * vec4(position, 1.0);
1048
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
1049
+ }
1027
1050
  `;
1028
1051
  }
1029
1052
  function fragmentShader() {
1030
- return `
1031
- uniform vec3 color;
1032
-
1033
- varying vec4 vLightCoords;
1034
-
1035
- void main() {
1036
- vec2 lightUV = vLightCoords.xy / vLightCoords.w;
1037
- float inView = float(max(lightUV.x, lightUV.y) <= 1.0 && min(lightUV.x, lightUV.y) >= 0.0 && vLightCoords.z > 0.0);
1038
-
1039
- gl_FragColor = vec4(color, min(inView, 0.2));
1040
- }
1053
+ return `
1054
+ uniform vec3 color;
1055
+
1056
+ varying vec4 vLightCoords;
1057
+
1058
+ void main() {
1059
+ vec2 lightUV = vLightCoords.xy / vLightCoords.w;
1060
+ float inView = float(max(lightUV.x, lightUV.y) <= 1.0 && min(lightUV.x, lightUV.y) >= 0.0 && vLightCoords.z > 0.0);
1061
+
1062
+ gl_FragColor = vec4(color, min(inView, 0.2));
1063
+ }
1041
1064
  `;
1042
1065
  }
1043
1066
  function updateHighlightUniforms(projector, uniforms) {
@@ -2200,32 +2223,32 @@ class PoiService {
2200
2223
  if (!sweeps || sweeps.length === 0) {
2201
2224
  return [];
2202
2225
  }
2203
- const queryStatement = `
2204
- query ListPOISLight($filter: ModelPOIFilterInput, $limit: Int, $nextToken: String) {
2205
- listPOIS(filter: $filter, limit: $limit, nextToken: $nextToken) {
2206
- items {
2207
- id
2208
- coordinate
2209
- specific
2210
- private
2211
- global
2212
- type
2213
- elementID
2214
- level
2215
- role
2216
- category
2217
- domainID
2218
- metadata
2219
- tagIcon
2220
- tagDescription
2221
- tagImageID
2222
- matterportSweepID
2223
- createdAt
2224
- updatedAt
2225
- }
2226
- nextToken
2227
- }
2228
- }
2226
+ const queryStatement = `
2227
+ query ListPOISLight($filter: ModelPOIFilterInput, $limit: Int, $nextToken: String) {
2228
+ listPOIS(filter: $filter, limit: $limit, nextToken: $nextToken) {
2229
+ items {
2230
+ id
2231
+ coordinate
2232
+ specific
2233
+ private
2234
+ global
2235
+ type
2236
+ elementID
2237
+ level
2238
+ role
2239
+ category
2240
+ domainID
2241
+ metadata
2242
+ tagIcon
2243
+ tagDescription
2244
+ tagImageID
2245
+ matterportSweepID
2246
+ createdAt
2247
+ updatedAt
2248
+ }
2249
+ nextToken
2250
+ }
2251
+ }
2229
2252
  `;
2230
2253
  const CHUNK_SIZE = 50; // safe margin for an "or" filter expression's practical size
2231
2254
  const chunks = [];
@@ -2293,6 +2316,69 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
2293
2316
  args: ['currentAwsKinesisAnalyticsService']
2294
2317
  }] }, { type: BaseUserService }, { type: ZoneChangeService }] });
2295
2318
 
2319
+ // matterport-framing.util.ts
2320
+ /**
2321
+ * Camera rotation that shows `target` from `camera`, at a given horizontal place of the viewer.
2322
+ * Matterport yaw is 0 when looking toward -Z and grows when turning left; pitch is positive upward.
2323
+ * @param horizontalTanHalfFov tan(horizontal FOV / 2), i.e. 1 / projection[0] of the camera pose
2324
+ * @param screenX where the target must appear, from -1 (left edge) to 1 (right edge); 0 = center.
2325
+ * Used when part of the viewer is covered (side panel), to frame the visible area.
2326
+ */
2327
+ function computeFramingRotation(camera, target, horizontalTanHalfFov = 1, screenX = 0) {
2328
+ const dx = target.x - camera.x;
2329
+ const dy = target.y - camera.y;
2330
+ const dz = target.z - camera.z;
2331
+ const yawToTarget = Math.atan2(-dx, -dz);
2332
+ // the target is seen at screenX when the camera looks that much to its side
2333
+ const offset = Math.atan(screenX * horizontalTanHalfFov);
2334
+ return {
2335
+ x: toDegrees(Math.atan2(dy, Math.hypot(dx, dz))),
2336
+ y: toDegrees(yawToTarget + offset),
2337
+ };
2338
+ }
2339
+ /**
2340
+ * Horizontal place (-1..1) of the center of the visible part of the viewer.
2341
+ * @param viewer client rect of the viewer iframe
2342
+ * @param visibleLeft / visibleRight client X bounds of the part not covered by other panels
2343
+ */
2344
+ function visibleCenterScreenX(viewer, visibleLeft, visibleRight) {
2345
+ if (!viewer.width)
2346
+ return 0;
2347
+ const left = Math.max(viewer.left, visibleLeft);
2348
+ const right = Math.min(viewer.left + viewer.width, visibleRight);
2349
+ if (right <= left)
2350
+ return 0;
2351
+ const center = (left + right) / 2;
2352
+ return ((center - viewer.left) / viewer.width) * 2 - 1;
2353
+ }
2354
+ function toDegrees(rad) {
2355
+ return (rad * 180) / Math.PI;
2356
+ }
2357
+ /** Height of the floor under a sweep: its puck, or 1.5 m under the camera when the SDK gives none */
2358
+ function sweepFloorY(sweep) {
2359
+ return sweep.puckPosition?.y ?? sweep.floorPosition?.y ?? sweep.position.y - 1.5;
2360
+ }
2361
+ /**
2362
+ * Sweep closest to an object placed at `position`, among the sweeps of the same storey: the camera
2363
+ * of the storey below is often nearer in 3D than a sweep of the same room. A sweep is on the same
2364
+ * storey when the object stands between 0.5 m under its floor and 3 m above it; without any such
2365
+ * sweep, all of them are considered. Distances are measured horizontally.
2366
+ */
2367
+ function pickNearestSweepOnSameStorey(sweeps, position) {
2368
+ const sameStorey = sweeps.filter((s) => position.y >= s.floorY - 0.5 && position.y < s.floorY + 3);
2369
+ const candidates = sameStorey.length > 0 ? sameStorey : sweeps;
2370
+ let best = null;
2371
+ let bestDist = Infinity;
2372
+ for (const sweep of candidates) {
2373
+ const dist = Math.hypot(sweep.position.x - position.x, sweep.position.z - position.z);
2374
+ if (dist < bestDist) {
2375
+ bestDist = dist;
2376
+ best = sweep.sid;
2377
+ }
2378
+ }
2379
+ return best;
2380
+ }
2381
+
2296
2382
  /* eslint-disable unicorn/filename-case */
2297
2383
  class MattertagData {
2298
2384
  object;
@@ -3030,11 +3116,11 @@ class MatterportPointerService {
3030
3116
  }
3031
3117
  updateCursorDisplay(poseMatterport) {
3032
3118
  if (this.textDisplayCursorPositionPanel && poseMatterport) {
3033
- this.textDisplayCursorPositionPanel.innerHTML = `position:
3034
- ${pointToString(poseMatterport.position)}\n
3035
- normal:
3036
- ${pointToString(poseMatterport.normal)}\n
3037
- floorId:
3119
+ this.textDisplayCursorPositionPanel.innerHTML = `position:
3120
+ ${pointToString(poseMatterport.position)}\n
3121
+ normal:
3122
+ ${pointToString(poseMatterport.normal)}\n
3123
+ floorId:
3038
3124
  ${poseMatterport.floorId}`;
3039
3125
  if (this.getCursorPositionButton) {
3040
3126
  this.getCursorPositionButton.style.display = 'none';
@@ -3463,6 +3549,28 @@ class MatterportObject3DService {
3463
3549
  azimuthalCrown;
3464
3550
  threeJSScene;
3465
3551
  noLightForObjects = true;
3552
+ /** Max time (ms) to wait for a model to be loaded by the SDK */
3553
+ loadTimeoutMs = 15000;
3554
+ /** Max distance (m) an object can travel from where a translate drag started */
3555
+ maxDragDistance = 2;
3556
+ /** Max vertical travel (m, up and down) during a translate drag, so an object cannot jump to another floor */
3557
+ maxDragHeight = 1.5;
3558
+ /** Emits on every gizmo change and on setObject3DTransform */
3559
+ object3DTransformChanged$ = new Subject();
3560
+ /** Security camera animators by object uuid (`securityCameraAnimator` stays the last loaded one) */
3561
+ securityCameraAnimators = new Map();
3562
+ viewFrustumVisible = new Map();
3563
+ lastSecurityCameraUuid = null;
3564
+ /** Loader component of each model (outputs.objectRoot is set once loaded) */
3565
+ loaderComponents = new Map();
3566
+ gizmos = new Map();
3567
+ activeGizmoUuid = null;
3568
+ gizmoSnap = null;
3569
+ /** Sweeps disabled by setNavigationLocked(true), re-enabled on unlock */
3570
+ sweepsDisabledByLock = [];
3571
+ /** Sweeps re-enabled by the last unlock (the SDK may still report them disabled for a moment) */
3572
+ sweepsEnabledByUnlock = [];
3573
+ tmpBox = new Box3();
3466
3574
  constructor() { }
3467
3575
  async init3DObjectViewer(sdk) {
3468
3576
  const [sceneObject] = await sdk.Scene.createObjects(1);
@@ -3473,112 +3581,184 @@ class MatterportObject3DService {
3473
3581
  this.objectControl = nodeControl.addComponent('mp.transformControls');
3474
3582
  nodeControl.start();
3475
3583
  }
3476
- async add3DObject(sdk, obj, mode) {
3477
- return new Promise(async (resolve) => {
3478
- const [sceneObject] = await sdk.Scene.createObjects(1);
3479
- let isAnimatedSecurityCamera = (obj.object === "security_camera");
3480
- let isNestThermostat = (obj.object === "nest_thermostat");
3481
- let isSmarterplanPromotionalVideo = (obj.object === 'video');
3482
- let isAzimuthalCrown = (obj.object === 'azimuth');
3483
- const modelNode = sceneObject.addNode();
3484
- let component = null;
3485
- const initial = {
3486
- url: `/assets/3Dobjects/objects/${obj.object}${obj.format.indexOf('.') === -1 ? '.' + obj.format : obj.format}`,
3487
- localRotation: { "x": 0, "y": 0, "z": 0 },
3488
- localPosition: { "x": 0, "y": 0, "z": 0 },
3489
- visible: true,
3490
- colliderEnabled: true
3491
- };
3492
- switch (obj.format) {
3493
- case '.obj':
3494
- case 'obj':
3495
- component = modelNode.addComponent('mp.objLoader', initial);
3496
- break;
3497
- case '.fbx':
3498
- case 'fbx':
3499
- component = modelNode.addComponent('mp.fbxLoader', initial);
3500
- break;
3501
- case '.gltf':
3502
- case 'gltf':
3503
- case '.glb':
3504
- case 'glb':
3505
- component = modelNode.addComponent('mp.gltfLoader', initial);
3506
- break;
3507
- case '.dae':
3508
- case 'dae':
3509
- component = modelNode.addComponent('mp.daeLoader', initial);
3510
- break;
3511
- default:
3512
- console.log('Format not supported');
3513
- break;
3584
+ /** SDK loader component name for a model format, null when the format is not supported */
3585
+ getLoaderComponentName(format) {
3586
+ switch ((format || '').toLowerCase()) {
3587
+ case '.obj':
3588
+ case 'obj': return 'mp.objLoader';
3589
+ case '.fbx':
3590
+ case 'fbx': return 'mp.fbxLoader';
3591
+ case '.gltf':
3592
+ case 'gltf':
3593
+ case '.glb':
3594
+ case 'glb': return 'mp.gltfLoader';
3595
+ case '.dae':
3596
+ case 'dae': return 'mp.daeLoader';
3597
+ default: return null;
3598
+ }
3599
+ }
3600
+ /** Resolves once the loader component has produced its objectRoot, rejects after `timeoutMs` */
3601
+ waitForModelLoaded(component, timeoutMs) {
3602
+ return new Promise((resolve, reject) => {
3603
+ const isLoaded = () => !!component?.outputs?.objectRoot;
3604
+ if (isLoaded()) {
3605
+ resolve();
3606
+ return;
3514
3607
  }
3515
- if (this.noLightForObjects) {
3516
- const lightsNode = sceneObject.addNode();
3517
- lightsNode.addComponent('mp.ambientLight', {
3518
- intensity: 1,
3519
- color: { r: 1.0, g: 1.0, b: 1.0 },
3520
- });
3521
- this.noLightForObjects = false;
3522
- }
3523
- modelNode.obj3D.position.set(obj.position.x, obj.position.y, obj.position.z);
3524
- modelNode.obj3D.rotation.set(obj.rotation.x, obj.rotation.y, obj.rotation.z);
3525
- modelNode.obj3D.scale.set(obj.scale.x, obj.scale.y, obj.scale.z);
3526
- if (isAzimuthalCrown) {
3527
- this.azimuthalCrown = modelNode;
3528
- }
3529
- if (mode && !isNestThermostat) {
3530
- this.attachGizmoControlTo3DObject(sdk, modelNode, sceneObject, mode, true, true).catch((e) => console.log(e));
3531
- }
3532
- if (this.lastObject3D && typeof this.lastObject3D.id === 'string') {
3533
- modelNode.obj3D.uuid = this.lastObject3D.id;
3534
- }
3535
- else if (obj.id) {
3536
- modelNode.obj3D.uuid = obj.id;
3537
- }
3538
- this.lastObject3D = modelNode.obj3D;
3539
- this.dictionnaryObjects3D.set(modelNode.obj3D.uuid, modelNode);
3540
- this.dictionnarySceneObjects3D.set(modelNode.obj3D.uuid, sceneObject);
3541
- if (isAnimatedSecurityCamera) {
3542
- const animator = modelNode.addComponent('mp.securityCamera', {
3543
- "nearPlane": 0.1, "farPlane": 10, "horizontalFOV": 52, "aspect": 1.7777777777777777,
3544
- "localPosition": { "x": 0.3, "y": 0.18, "z": 0 },
3545
- "localRotation": { "x": -15, "y": -90, "z": 0 },
3546
- "color": 65280, "panPeriod": 5, "panAngle": -45
3547
- });
3548
- this.securityCameraAnimator = animator;
3549
- if (!obj.viewFrustum) {
3550
- setTimeout(() => { animator.toggleViewFrustum(); }, 1000);
3608
+ const poll = setInterval(() => {
3609
+ if (isLoaded()) {
3610
+ clearInterval(poll);
3611
+ clearTimeout(timer);
3612
+ resolve();
3551
3613
  }
3552
- }
3553
- if (isNestThermostat) {
3554
- const cv = new CanvasRenderer();
3555
- cv.onInit();
3556
- const plane = new PlaneRenderer();
3557
- plane.outputs = { objectRoot: new Object3D(), collider: new Object3D() };
3558
- const inputTexture = cv.outputs.texture;
3559
- plane.setRootScene(this.threeJSScene);
3560
- plane.onInit(modelNode, inputTexture);
3561
- const sc = new NestThermostat();
3562
- sc.setComponent(component, plane, cv);
3563
- sc.setRootScene(this.threeJSScene);
3564
- sc.onInit(modelNode, plane, inputTexture);
3565
- cv.setCanvasNestThermostatPainter(sc);
3566
- setTimeout(() => { sc.inputs.loadingState = "Loaded"; sc.onInputsUpdated(); }, 5000);
3567
- }
3568
- if (isSmarterplanPromotionalVideo) {
3569
- const sc = new TvPlayer();
3570
- sc.setComponent(component);
3571
- sc.onInit(modelNode);
3572
- setTimeout(() => { sc.inputs.loadingState = "Loaded"; sc.onInputsUpdated(); }, 5000);
3573
- }
3574
- sceneObject.start();
3575
- resolve(this.lastObject3D);
3614
+ }, 50);
3615
+ const timer = setTimeout(() => {
3616
+ clearInterval(poll);
3617
+ reject(new Error(`3D model not loaded after ${timeoutMs} ms`));
3618
+ }, timeoutMs);
3576
3619
  });
3577
3620
  }
3621
+ /**
3622
+ * Adds a 3D model to the scene.
3623
+ * The promise resolves with the three.js object once the model is actually loaded, and rejects
3624
+ * (after cleaning the scene) when the format is unknown or the load times out.
3625
+ * The position / rotation / scale of `obj` are applied to the model origin, as stored in DB.
3626
+ */
3627
+ async add3DObject(sdk, obj, mode) {
3628
+ const loaderName = this.getLoaderComponentName(obj.format);
3629
+ if (!loaderName) {
3630
+ throw new Error(`3D object format not supported: ${obj.format}`);
3631
+ }
3632
+ const [sceneObject] = await sdk.Scene.createObjects(1);
3633
+ const isAnimatedSecurityCamera = (!obj.url && obj.object === 'security_camera');
3634
+ const isNestThermostat = (!obj.url && obj.object === 'nest_thermostat');
3635
+ const isSmarterplanPromotionalVideo = (!obj.url && obj.object === 'video');
3636
+ const isAzimuthalCrown = (!obj.url && obj.object === 'azimuth');
3637
+ const modelNode = sceneObject.addNode();
3638
+ const initial = {
3639
+ url: obj.url || `/assets/3Dobjects/objects/${obj.object}${obj.format.indexOf('.') === -1 ? '.' + obj.format : obj.format}`,
3640
+ localRotation: { 'x': 0, 'y': 0, 'z': 0 },
3641
+ localPosition: { 'x': 0, 'y': 0, 'z': 0 },
3642
+ visible: true,
3643
+ colliderEnabled: true
3644
+ };
3645
+ const component = modelNode.addComponent(loaderName, initial);
3646
+ if (this.noLightForObjects) {
3647
+ const lightsNode = sceneObject.addNode();
3648
+ lightsNode.addComponent('mp.ambientLight', {
3649
+ intensity: 1,
3650
+ color: { r: 1.0, g: 1.0, b: 1.0 },
3651
+ });
3652
+ this.noLightForObjects = false;
3653
+ }
3654
+ modelNode.obj3D.position.set(obj.position.x, obj.position.y, obj.position.z);
3655
+ modelNode.obj3D.rotation.set(obj.rotation.x, obj.rotation.y, obj.rotation.z);
3656
+ modelNode.obj3D.scale.set(obj.scale.x, obj.scale.y, obj.scale.z);
3657
+ // The DB id replaces the three.js uuid. The id pre-set through setLastObject3D is only a fallback
3658
+ // for callers without id: taking it first gave the same id to every object loaded meanwhile
3659
+ // (several cameras or cubes loading while an add is starting), which then overwrote each other.
3660
+ if (obj.id) {
3661
+ modelNode.obj3D.uuid = obj.id;
3662
+ }
3663
+ else if (this.lastObject3D && typeof this.lastObject3D.id === 'string') {
3664
+ modelNode.obj3D.uuid = this.lastObject3D.id;
3665
+ }
3666
+ const uuid = modelNode.obj3D.uuid;
3667
+ this.lastObject3D = modelNode.obj3D;
3668
+ this.dictionnaryObjects3D.set(uuid, modelNode);
3669
+ this.dictionnarySceneObjects3D.set(uuid, sceneObject);
3670
+ this.loaderComponents.set(uuid, component);
3671
+ if (isAzimuthalCrown) {
3672
+ this.azimuthalCrown = modelNode;
3673
+ }
3674
+ if (mode && !isNestThermostat) {
3675
+ this.attachGizmo(uuid, mode).catch(() => { });
3676
+ }
3677
+ let animator = null;
3678
+ if (isAnimatedSecurityCamera) {
3679
+ animator = modelNode.addComponent('mp.securityCamera', {
3680
+ 'nearPlane': 0.1, 'farPlane': 10, 'horizontalFOV': 52, 'aspect': 1.7777777777777777,
3681
+ 'localPosition': { 'x': 0.3, 'y': 0.18, 'z': 0 },
3682
+ 'localRotation': { 'x': -15, 'y': -90, 'z': 0 },
3683
+ 'color': 65280, 'panPeriod': 5, 'panAngle': -45
3684
+ });
3685
+ this.securityCameraAnimator = animator;
3686
+ this.securityCameraAnimators.set(uuid, animator);
3687
+ this.viewFrustumVisible.set(uuid, true); // the component shows its frustum by default
3688
+ this.lastSecurityCameraUuid = uuid;
3689
+ }
3690
+ if (isNestThermostat) {
3691
+ const cv = new CanvasRenderer();
3692
+ cv.onInit();
3693
+ const plane = new PlaneRenderer();
3694
+ plane.outputs = { objectRoot: new Object3D(), collider: new Object3D() };
3695
+ const inputTexture = cv.outputs.texture;
3696
+ plane.setRootScene(this.threeJSScene);
3697
+ plane.onInit(modelNode, inputTexture);
3698
+ const sc = new NestThermostat();
3699
+ sc.setComponent(component, plane, cv);
3700
+ sc.setRootScene(this.threeJSScene);
3701
+ sc.onInit(modelNode, plane, inputTexture);
3702
+ cv.setCanvasNestThermostatPainter(sc);
3703
+ setTimeout(() => { sc.inputs.loadingState = 'Loaded'; sc.onInputsUpdated(); }, 5000);
3704
+ }
3705
+ if (isSmarterplanPromotionalVideo) {
3706
+ const sc = new TvPlayer();
3707
+ sc.setComponent(component);
3708
+ sc.onInit(modelNode);
3709
+ setTimeout(() => { sc.inputs.loadingState = 'Loaded'; sc.onInputsUpdated(); }, 5000);
3710
+ }
3711
+ sceneObject.start();
3712
+ try {
3713
+ await this.waitForModelLoaded(component, this.loadTimeoutMs);
3714
+ }
3715
+ catch (e) {
3716
+ await this.deleteObject3D(uuid);
3717
+ throw e;
3718
+ }
3719
+ if (animator && !obj.viewFrustum) {
3720
+ // The camera component starts with the model node, so it is ready now that the model is loaded
3721
+ this.applyViewFrustum(uuid, false);
3722
+ }
3723
+ return modelNode.obj3D;
3724
+ }
3725
+ /**
3726
+ * Removes a 3D object for good: detaches its gizmo, stops its scene object and forgets it.
3727
+ */
3578
3728
  async deleteObject3D(uuid) {
3579
- const obj = this.dictionnaryObjects3D.get(uuid);
3580
- if (obj)
3581
- obj.obj3D.visible = false;
3729
+ this.detachGizmo(uuid);
3730
+ const node = this.dictionnaryObjects3D.get(uuid);
3731
+ const sceneObject = this.dictionnarySceneObjects3D.get(uuid);
3732
+ if (node) {
3733
+ try {
3734
+ node.obj3D.visible = false;
3735
+ }
3736
+ catch { /* already destroyed */ }
3737
+ }
3738
+ if (sceneObject && typeof sceneObject.stop === 'function') {
3739
+ try {
3740
+ sceneObject.stop();
3741
+ }
3742
+ catch { /* already stopped */ }
3743
+ }
3744
+ this.dictionnaryObjects3D.delete(uuid);
3745
+ this.dictionnarySceneObjects3D.delete(uuid);
3746
+ this.loaderComponents.delete(uuid);
3747
+ if (this.lastObject3D && (this.lastObject3D.uuid === uuid || this.lastObject3D.id === uuid)) {
3748
+ this.lastObject3D = null;
3749
+ }
3750
+ if (node && this.azimuthalCrown === node) {
3751
+ this.azimuthalCrown = null;
3752
+ }
3753
+ const animator = this.securityCameraAnimators.get(uuid);
3754
+ if (animator && this.securityCameraAnimator === animator) {
3755
+ this.securityCameraAnimator = null;
3756
+ }
3757
+ this.securityCameraAnimators.delete(uuid);
3758
+ this.viewFrustumVisible.delete(uuid);
3759
+ if (this.lastSecurityCameraUuid === uuid) {
3760
+ this.lastSecurityCameraUuid = null;
3761
+ }
3582
3762
  }
3583
3763
  getObject3DModelNodeFromDictionnary(uuid) {
3584
3764
  let obj = this.dictionnaryObjects3D.get(uuid);
@@ -3598,10 +3778,32 @@ class MatterportObject3DService {
3598
3778
  let obj = this.dictionnaryObjects3D.get(objectId);
3599
3779
  return obj ? obj.obj3D.visible : false;
3600
3780
  }
3781
+ /** Shows / hides a model (used by the placement preview) */
3782
+ setObject3DVisible(uuid, visible) {
3783
+ const node = this.dictionnaryObjects3D.get(uuid);
3784
+ if (node)
3785
+ node.obj3D.visible = visible;
3786
+ }
3787
+ /** Enables / disables the pointer collider of a model (disabled while it follows the cursor) */
3788
+ setObject3DColliderEnabled(uuid, enabled) {
3789
+ const component = this.loaderComponents.get(uuid);
3790
+ if (component?.inputs) {
3791
+ component.inputs.colliderEnabled = enabled;
3792
+ }
3793
+ }
3794
+ /**
3795
+ * Moves the camera next to a 3D object by creating a temporary tag on the object centre.
3796
+ * @returns false when the object is unknown or no tag could be reached
3797
+ */
3601
3798
  async pointCameraTo3DObject(sdk, objectId, createTagCallback, goToTagCallback, deleteLastTagCallback, getTagFromElementId) {
3602
- let obj = this.dictionnaryObjects3D.get(objectId);
3799
+ const obj = this.dictionnaryObjects3D.get(objectId);
3800
+ if (!obj)
3801
+ return false;
3802
+ // Tag on the bounding box centre (the model origin is a corner for most models)
3803
+ const box = this.getObject3DBoundingBox(objectId);
3804
+ const anchor = box ? box.center : this.toPoint(obj.obj3D.position);
3603
3805
  const poiObject = {
3604
- coordinate: JSON.stringify(obj.obj3D.position),
3806
+ coordinate: JSON.stringify(anchor),
3605
3807
  type: PoiType.OBJECT3D,
3606
3808
  elementID: objectId,
3607
3809
  };
@@ -3610,9 +3812,16 @@ class MatterportObject3DService {
3610
3812
  await createTagCallback(PoiType.OBJECT3D, objectDb, poiObject);
3611
3813
  }
3612
3814
  catch (err) { }
3613
- let result = getTagFromElementId(objectId);
3614
- await goToTagCallback(result.tag);
3615
- deleteLastTagCallback();
3815
+ try {
3816
+ const result = getTagFromElementId(objectId);
3817
+ if (!result?.tag)
3818
+ return false;
3819
+ await goToTagCallback(result.tag);
3820
+ return true;
3821
+ }
3822
+ finally {
3823
+ deleteLastTagCallback();
3824
+ }
3616
3825
  }
3617
3826
  getSceneNodeFromObject3DId(uuid) {
3618
3827
  return this.dictionnarySceneObjects3D.get(uuid);
@@ -3622,49 +3831,449 @@ class MatterportObject3DService {
3622
3831
  this.azimuthalCrown.obj3D.position.set(poseCamera.position.x, poseCamera.position.y, poseCamera.position.z);
3623
3832
  }
3624
3833
  }
3834
+ //
3835
+ // ---------- Geometry helpers ----------
3836
+ //
3837
+ toPoint(v) {
3838
+ return { x: v.x, y: v.y, z: v.z };
3839
+ }
3840
+ /**
3841
+ * Geometry of the model itself (the loader output): the node also holds the helpers added by other
3842
+ * components, like the view frustum of a security camera, which must not count in its size.
3843
+ */
3844
+ modelRoot(uuid, node) {
3845
+ return this.loaderComponents.get(uuid)?.outputs?.objectRoot ?? node.obj3D;
3846
+ }
3847
+ /** World-space bounding box of the model, null when unknown or when nothing is loaded yet */
3848
+ getObject3DBoundingBox(uuid) {
3849
+ const node = this.dictionnaryObjects3D.get(uuid);
3850
+ if (!node)
3851
+ return null;
3852
+ node.obj3D.updateWorldMatrix(true, true);
3853
+ const box = this.tmpBox.setFromObject(this.modelRoot(uuid, node));
3854
+ if (box.isEmpty())
3855
+ return null;
3856
+ const size = box.getSize(new Vector3());
3857
+ const center = box.getCenter(new Vector3());
3858
+ return {
3859
+ min: this.toPoint(box.min),
3860
+ max: this.toPoint(box.max),
3861
+ size: this.toPoint(size),
3862
+ center: this.toPoint(center),
3863
+ };
3864
+ }
3865
+ /**
3866
+ * Real dimensions of the model in meters (width x, height y, depth z), scale included but not
3867
+ * its rotation: the world bounding box of a rotated object is larger than the object itself.
3868
+ */
3869
+ getObject3DDimensions(uuid) {
3870
+ const node = this.dictionnaryObjects3D.get(uuid);
3871
+ if (!node)
3872
+ return null;
3873
+ const rotation = node.obj3D.rotation.clone();
3874
+ node.obj3D.rotation.set(0, 0, 0);
3875
+ try {
3876
+ node.obj3D.updateWorldMatrix(true, true);
3877
+ const box = this.tmpBox.setFromObject(this.modelRoot(uuid, node));
3878
+ return box.isEmpty() ? null : this.toPoint(box.getSize(new Vector3()));
3879
+ }
3880
+ finally {
3881
+ node.obj3D.rotation.copy(rotation);
3882
+ node.obj3D.updateWorldMatrix(true, true);
3883
+ }
3884
+ }
3885
+ /**
3886
+ * Uniform scale so that the largest bounding box dimension equals `targetSizeMeters`.
3887
+ * The node position is untouched, so call placeObject3DOnSurface afterwards.
3888
+ * @returns the new scale, null when the object is unknown / not loaded / `targetSizeMeters` invalid
3889
+ */
3890
+ normalizeObject3DSize(uuid, targetSizeMeters) {
3891
+ const node = this.dictionnaryObjects3D.get(uuid);
3892
+ const box = this.getObject3DBoundingBox(uuid);
3893
+ if (!node || !box || !(targetSizeMeters > 0))
3894
+ return null;
3895
+ const largest = Math.max(box.size.x, box.size.y, box.size.z);
3896
+ if (!(largest > 0))
3897
+ return null;
3898
+ const factor = targetSizeMeters / largest;
3899
+ node.obj3D.scale.multiplyScalar(factor);
3900
+ this.emitTransformChanged(uuid);
3901
+ return this.toPoint(node.obj3D.scale);
3902
+ }
3903
+ /**
3904
+ * Places the model on a surface point without touching its pivot: the node position is
3905
+ * adjusted so that the bounding box bottom-center lies on `point`.
3906
+ * - floor (normal pointing up, or no normal): bottom-center on the point
3907
+ * - wall (mostly horizontal normal): model turned around Y so its +Z faces away from the wall,
3908
+ * and pushed along the normal by half its depth so it does not cut the wall
3909
+ * - ceiling (normal pointing down): the model hangs, top-center on the point
3910
+ * @returns the new transform, null when the object is unknown or not loaded
3911
+ */
3912
+ /**
3913
+ * @param frontYawOffset rotation (radians, around Y) that turns the front of the model toward its +Z
3914
+ * axis, for models whose front is another axis (e.g. -PI/2 when it is +X)
3915
+ */
3916
+ placeObject3DOnSurface(uuid, point, normal, frontYawOffset = 0) {
3917
+ const node = this.dictionnaryObjects3D.get(uuid);
3918
+ if (!node || !this.getObject3DBoundingBox(uuid))
3919
+ return null;
3920
+ const obj = node.obj3D;
3921
+ const n = normal ? new Vector3(normal.x, normal.y, normal.z) : new Vector3(0, 1, 0);
3922
+ if (n.lengthSq() === 0)
3923
+ n.set(0, 1, 0);
3924
+ n.normalize();
3925
+ const isWall = Math.abs(n.y) < 0.5;
3926
+ const isCeiling = !isWall && n.y < 0;
3927
+ const target = new Vector3(point.x, point.y, point.z);
3928
+ if (isWall) {
3929
+ // Face away from the wall; the box has to be measured after the rotation
3930
+ obj.rotation.y = Math.atan2(n.x, n.z) + frontYawOffset;
3931
+ }
3932
+ const box = this.getObject3DBoundingBox(uuid);
3933
+ if (isWall) {
3934
+ const horizontal = new Vector3(n.x, 0, n.z).normalize();
3935
+ const halfDepth = 0.5 * (Math.abs(horizontal.x) * box.size.x + Math.abs(horizontal.z) * box.size.z);
3936
+ target.addScaledVector(horizontal, halfDepth);
3937
+ }
3938
+ const anchorY = isCeiling ? box.max.y : box.min.y;
3939
+ obj.position.x += target.x - box.center.x;
3940
+ obj.position.y += target.y - anchorY;
3941
+ obj.position.z += target.z - box.center.z;
3942
+ this.emitTransformChanged(uuid);
3943
+ return this.getObject3DTransform(uuid);
3944
+ }
3945
+ getObject3DTransform(uuid) {
3946
+ const node = this.dictionnaryObjects3D.get(uuid);
3947
+ if (!node)
3948
+ return null;
3949
+ const o = node.obj3D;
3950
+ return {
3951
+ position: this.toPoint(o.position),
3952
+ rotation: { x: o.rotation.x, y: o.rotation.y, z: o.rotation.z },
3953
+ scale: this.toPoint(o.scale),
3954
+ };
3955
+ }
3956
+ /** Applies a (partial) transform. Rotation is in radians. */
3957
+ setObject3DTransform(uuid, transform) {
3958
+ const node = this.dictionnaryObjects3D.get(uuid);
3959
+ if (!node)
3960
+ return false;
3961
+ const o = node.obj3D;
3962
+ if (transform.position)
3963
+ o.position.set(transform.position.x, transform.position.y, transform.position.z);
3964
+ if (transform.rotation)
3965
+ o.rotation.set(transform.rotation.x, transform.rotation.y, transform.rotation.z);
3966
+ if (transform.scale)
3967
+ o.scale.set(transform.scale.x, transform.scale.y, transform.scale.z);
3968
+ this.emitTransformChanged(uuid);
3969
+ return true;
3970
+ }
3971
+ emitTransformChanged(uuid) {
3972
+ const transform = this.getObject3DTransform(uuid);
3973
+ if (transform) {
3974
+ this.object3DTransformChanged$.next({ uuid, ...transform });
3975
+ }
3976
+ }
3977
+ //
3978
+ // ---------- Gizmo ----------
3979
+ //
3980
+ /** Creates the mp.transformControls node of a model and wires it (events, snap) */
3981
+ createGizmo(uuid, modelNode, sceneObject, mode, visible, node) {
3982
+ const gizmoNode = node ?? sceneObject.addNode();
3983
+ const component = gizmoNode.addComponent('mp.transformControls');
3984
+ gizmoNode.start();
3985
+ const tc = component.transformControls;
3986
+ tc.visible = visible;
3987
+ component.inputs.selection = modelNode;
3988
+ component.inputs.mode = mode;
3989
+ modelNode.obj3D.controls = component;
3990
+ const entry = {
3991
+ uuid,
3992
+ node: gizmoNode,
3993
+ component,
3994
+ mode,
3995
+ dragStart: null,
3996
+ lastPosition: modelNode.obj3D.position.clone(),
3997
+ onDraggingChanged: (event) => {
3998
+ entry.dragStart = event?.value ? modelNode.obj3D.position.clone() : null;
3999
+ entry.lastPosition = modelNode.obj3D.position.clone();
4000
+ },
4001
+ onObjectChange: () => this.onGizmoObjectChange(entry, modelNode),
4002
+ };
4003
+ if (typeof tc.addEventListener === 'function') {
4004
+ tc.addEventListener('dragging-changed', entry.onDraggingChanged);
4005
+ tc.addEventListener('objectChange', entry.onObjectChange);
4006
+ }
4007
+ this.applySnap(tc);
4008
+ this.gizmos.set(uuid, entry);
4009
+ this.activeGizmoUuid = uuid;
4010
+ return entry;
4011
+ }
4012
+ /** Keeps a translate drag within maxDragDistance / maxDragHeight of where it started, then notifies */
4013
+ onGizmoObjectChange(entry, modelNode) {
4014
+ const pos = modelNode.obj3D.position;
4015
+ if (entry.mode === 'translate') {
4016
+ const start = entry.dragStart ?? entry.lastPosition;
4017
+ const delta = pos.clone().sub(start);
4018
+ delta.y = MathUtils.clamp(delta.y, -this.maxDragHeight, this.maxDragHeight);
4019
+ const length = delta.length();
4020
+ if (length > this.maxDragDistance) {
4021
+ delta.multiplyScalar(this.maxDragDistance / length);
4022
+ }
4023
+ pos.copy(start).add(delta);
4024
+ }
4025
+ entry.lastPosition = pos.clone();
4026
+ this.emitTransformChanged(entry.uuid);
4027
+ }
4028
+ applySnap(tc) {
4029
+ if (!tc)
4030
+ return;
4031
+ const snap = this.gizmoSnap;
4032
+ const translation = snap?.translation ?? null;
4033
+ const rotation = snap?.rotationDeg != null ? MathUtils.degToRad(snap.rotationDeg) : null;
4034
+ const scale = snap?.scale ?? null;
4035
+ if (typeof tc.setTranslationSnap === 'function')
4036
+ tc.setTranslationSnap(translation);
4037
+ else
4038
+ tc.translationSnap = translation;
4039
+ if (typeof tc.setRotationSnap === 'function')
4040
+ tc.setRotationSnap(rotation);
4041
+ else
4042
+ tc.rotationSnap = rotation;
4043
+ if (typeof tc.setScaleSnap === 'function')
4044
+ tc.setScaleSnap(scale);
4045
+ else
4046
+ tc.scaleSnap = scale;
4047
+ }
4048
+ /**
4049
+ * Shows the transform gizmo on an object (any previous gizmo is detached first).
4050
+ * Re-attaching to the object that already has the gizmo only changes its mode.
4051
+ */
4052
+ async attachGizmo(uuid, mode, sdk) {
4053
+ const modelNode = this.dictionnaryObjects3D.get(uuid);
4054
+ const sceneObject = this.dictionnarySceneObjects3D.get(uuid);
4055
+ if (!modelNode || !sceneObject)
4056
+ return false;
4057
+ const existing = this.gizmos.get(uuid);
4058
+ if (existing && this.activeGizmoUuid === uuid) {
4059
+ this.setGizmoMode(mode);
4060
+ return true;
4061
+ }
4062
+ // A started scene object refuses new nodes (addNode returns null): use a dedicated one
4063
+ let node = sceneObject.addNode();
4064
+ let gizmoSceneObject = null;
4065
+ if (!node) {
4066
+ if (!sdk?.Scene)
4067
+ return false;
4068
+ [gizmoSceneObject] = await sdk.Scene.createObjects(1);
4069
+ node = gizmoSceneObject.addNode();
4070
+ }
4071
+ this.detachGizmo();
4072
+ const entry = this.createGizmo(uuid, modelNode, sceneObject, mode, true, node);
4073
+ entry.sceneObject = gizmoSceneObject;
4074
+ this.lastObject3D = modelNode.obj3D;
4075
+ return true;
4076
+ }
4077
+ /** Switches the active gizmo between translate / rotate / scale without re-attaching it */
4078
+ setGizmoMode(mode) {
4079
+ const entry = this.activeGizmoUuid ? this.gizmos.get(this.activeGizmoUuid) : null;
4080
+ if (!entry)
4081
+ return;
4082
+ entry.mode = mode;
4083
+ entry.dragStart = null;
4084
+ entry.component.inputs.mode = mode;
4085
+ const tc = entry.component.transformControls;
4086
+ if (tc && typeof tc.setMode === 'function')
4087
+ tc.setMode(mode);
4088
+ }
4089
+ /** Really removes the gizmo from the scene (default: the active one) */
4090
+ detachGizmo(uuid) {
4091
+ const id = uuid ?? this.activeGizmoUuid;
4092
+ if (!id)
4093
+ return;
4094
+ const entry = this.gizmos.get(id);
4095
+ if (!entry)
4096
+ return;
4097
+ const tc = entry.component?.transformControls;
4098
+ if (tc) {
4099
+ if (typeof tc.removeEventListener === 'function') {
4100
+ tc.removeEventListener('dragging-changed', entry.onDraggingChanged);
4101
+ tc.removeEventListener('objectChange', entry.onObjectChange);
4102
+ }
4103
+ tc.visible = false;
4104
+ if (typeof tc.detach === 'function')
4105
+ tc.detach();
4106
+ if (typeof tc.dispose === 'function')
4107
+ tc.dispose();
4108
+ if (tc.parent && typeof tc.parent.remove === 'function')
4109
+ tc.parent.remove(tc);
4110
+ }
4111
+ try {
4112
+ entry.node.stop();
4113
+ }
4114
+ catch { /* already stopped */ }
4115
+ try {
4116
+ entry.sceneObject?.stop();
4117
+ }
4118
+ catch { /* already stopped */ }
4119
+ const modelNode = this.dictionnaryObjects3D.get(id);
4120
+ if (modelNode?.obj3D)
4121
+ modelNode.obj3D.controls = null;
4122
+ this.gizmos.delete(id);
4123
+ if (this.activeGizmoUuid === id)
4124
+ this.activeGizmoUuid = null;
4125
+ }
4126
+ isGizmoAttached(uuid) {
4127
+ return uuid ? this.gizmos.has(uuid) : this.activeGizmoUuid !== null;
4128
+ }
4129
+ /** Snap steps for all gizmos (null / missing value = no snap) */
4130
+ setGizmoSnap(snap) {
4131
+ this.gizmoSnap = snap ? { ...snap } : null;
4132
+ this.gizmos.forEach((entry) => this.applySnap(entry.component?.transformControls));
4133
+ }
4134
+ setGizmoMaxDragDistance(maxDistance, maxHeight) {
4135
+ if (maxDistance > 0)
4136
+ this.maxDragDistance = maxDistance;
4137
+ if (maxHeight !== undefined && maxHeight > 0)
4138
+ this.maxDragHeight = maxHeight;
4139
+ }
4140
+ /**
4141
+ * Prevents camera navigation to other sweeps while a gizmo is edited.
4142
+ * The SDK has no "freeze camera" switch, so every enabled sweep except the current one is
4143
+ * disabled (Sweep.disable); exactly those sweeps are re-enabled on unlock. Sweeps already
4144
+ * disabled (forbidden sweeps, edit mode) are never touched, and `keepDisabled` lists sweeps
4145
+ * that must stay disabled on unlock.
4146
+ * @returns false when the lock could not be applied (SDK without Sweep.disable, unknown current sweep)
4147
+ */
4148
+ async setNavigationLocked(sdk, locked, sweepCollection, currentSweepId, keepDisabled = []) {
4149
+ if (!sdk?.Sweep || typeof sdk.Sweep.disable !== 'function' || typeof sdk.Sweep.enable !== 'function') {
4150
+ return false;
4151
+ }
4152
+ if (locked) {
4153
+ if (this.sweepsDisabledByLock.length > 0)
4154
+ return true;
4155
+ if (!sweepCollection || !currentSweepId)
4156
+ return false;
4157
+ // a sweep this lock re-enabled just before may still be reported disabled by the SDK
4158
+ const ids = Object.keys(sweepCollection).filter((sid) => sid !== currentSweepId &&
4159
+ (sweepCollection[sid]?.enabled !== false || this.sweepsEnabledByUnlock.includes(sid)));
4160
+ if (ids.length === 0)
4161
+ return true;
4162
+ try {
4163
+ await sdk.Sweep.disable(...ids);
4164
+ this.sweepsDisabledByLock = ids;
4165
+ }
4166
+ catch {
4167
+ // fall back to one call per sweep to know exactly which ones were disabled
4168
+ const results = await Promise.allSettled(ids.map((sid) => sdk.Sweep.disable(sid)));
4169
+ this.sweepsDisabledByLock = ids.filter((_, i) => results[i].status === 'fulfilled');
4170
+ }
4171
+ return true;
4172
+ }
4173
+ const toEnable = this.sweepsDisabledByLock.filter((sid) => !keepDisabled.includes(sid));
4174
+ this.sweepsDisabledByLock = [];
4175
+ this.sweepsEnabledByUnlock = toEnable;
4176
+ if (toEnable.length > 0) {
4177
+ try {
4178
+ await sdk.Sweep.enable(...toEnable);
4179
+ }
4180
+ catch {
4181
+ await Promise.allSettled(toEnable.map((sid) => sdk.Sweep.enable(sid)));
4182
+ }
4183
+ }
4184
+ return true;
4185
+ }
4186
+ isNavigationLocked() {
4187
+ return this.sweepsDisabledByLock.length > 0;
4188
+ }
4189
+ /** Sweeps currently disabled by the navigation lock */
4190
+ getSweepsDisabledByLock() {
4191
+ return [...this.sweepsDisabledByLock];
4192
+ }
4193
+ /**
4194
+ * Legacy API: attaches a gizmo to a model node. Prefer attachGizmo(uuid, mode).
4195
+ */
3625
4196
  async attachGizmoControlTo3DObject(sdk, modelNode, sceneObject, mode, visible, isNewObject) {
3626
4197
  let node = sceneObject.addNode();
3627
4198
  if (!node) {
3628
4199
  const [newSceneObject] = await sdk.Scene.createObjects(1);
3629
4200
  node = newSceneObject.addNode();
3630
4201
  }
3631
- const myControl = node.addComponent('mp.transformControls');
3632
- node.start();
3633
- myControl.transformControls.visible = visible;
3634
- myControl.inputs.selection = modelNode;
3635
- myControl.inputs.mode = mode;
3636
- modelNode.obj3D.controls = myControl;
3637
- if (isNewObject) {
3638
- if (!this.lastObject3D || !this.lastObject3D.controls) {
3639
- try {
3640
- modelNode.obj3D.uuid = this.lastObject3D.uuid || this.lastObject3D.id;
3641
- }
3642
- catch (e) {
3643
- console.log(`id obj in Scene was not assigned to id from DB`);
3644
- }
3645
- this.lastObject3D = modelNode.obj3D;
4202
+ if (isNewObject && (!this.lastObject3D || !this.lastObject3D.controls)) {
4203
+ try {
4204
+ modelNode.obj3D.uuid = this.lastObject3D.uuid || this.lastObject3D.id;
4205
+ }
4206
+ catch (e) {
4207
+ // lastObject3D is not set: the model keeps its own uuid
3646
4208
  }
3647
4209
  }
3648
- else {
3649
- this.lastObject3D = modelNode.obj3D;
3650
- }
4210
+ this.detachGizmo();
4211
+ this.createGizmo(modelNode.obj3D.uuid, modelNode, sceneObject, mode, visible, node);
4212
+ this.lastObject3D = modelNode.obj3D;
3651
4213
  return modelNode;
3652
4214
  }
4215
+ /** Removes the gizmo of the last added / edited object */
3653
4216
  removeGizmoFromLastObject() {
3654
- if (this.lastObject3D && this.lastObject3D.controls) {
3655
- this.lastObject3D.controls.transformControls.visible = false;
3656
- this.lastObject3D.controls.transformControls.dispose();
3657
- this.lastObject3D.controls = null;
4217
+ const last = this.lastObject3D;
4218
+ if (!last)
4219
+ return;
4220
+ const uuid = last.uuid ?? last.id;
4221
+ if (uuid && this.gizmos.has(uuid)) {
4222
+ this.detachGizmo(uuid);
4223
+ }
4224
+ else if (last.controls) {
4225
+ // gizmo not created by this service: best effort
4226
+ last.controls.transformControls.visible = false;
4227
+ last.controls.transformControls.dispose();
4228
+ last.controls = null;
3658
4229
  }
3659
4230
  }
3660
- toggleViewFrustum() {
3661
- if (this.securityCameraAnimator) {
4231
+ //
4232
+ // ---------- Security camera ----------
4233
+ //
4234
+ applyViewFrustum(uuid, visible) {
4235
+ const animator = this.securityCameraAnimators.get(uuid);
4236
+ if (!animator)
4237
+ return false;
4238
+ const current = this.viewFrustumVisible.get(uuid) ?? true;
4239
+ if (current === visible)
4240
+ return true;
4241
+ try {
4242
+ animator.toggleViewFrustum();
4243
+ }
4244
+ catch {
4245
+ return false;
4246
+ }
4247
+ this.viewFrustumVisible.set(uuid, visible);
4248
+ return true;
4249
+ }
4250
+ /** Shows / hides the view frustum of the security camera `uuid` */
4251
+ setViewFrustum(uuid, visible) {
4252
+ return this.applyViewFrustum(uuid, visible);
4253
+ }
4254
+ isViewFrustumVisible(uuid) {
4255
+ return this.viewFrustumVisible.get(uuid) ?? false;
4256
+ }
4257
+ /** Toggles the frustum of `uuid`, or of the last loaded security camera when omitted */
4258
+ toggleViewFrustum(uuid) {
4259
+ const id = uuid ?? this.lastSecurityCameraUuid;
4260
+ if (id && this.securityCameraAnimators.has(id)) {
4261
+ this.applyViewFrustum(id, !(this.viewFrustumVisible.get(id) ?? true));
4262
+ }
4263
+ else if (!uuid && this.securityCameraAnimator) {
4264
+ // animator set by hand (legacy)
3662
4265
  this.securityCameraAnimator.toggleViewFrustum();
3663
4266
  }
3664
4267
  }
3665
4268
  clear() {
4269
+ Array.from(this.gizmos.keys()).forEach((id) => this.detachGizmo(id));
3666
4270
  this.dictionnaryObjects3D.clear();
3667
4271
  this.dictionnarySceneObjects3D.clear();
4272
+ this.loaderComponents.clear();
4273
+ this.securityCameraAnimators.clear();
4274
+ this.viewFrustumVisible.clear();
4275
+ this.lastSecurityCameraUuid = null;
4276
+ this.sweepsDisabledByLock = [];
3668
4277
  this.lastObject3D = null;
3669
4278
  this.azimuthalCrown = null;
3670
4279
  this.securityCameraAnimator = null;
@@ -4361,6 +4970,8 @@ class Config {
4361
4970
  constructor() { }
4362
4971
  }
4363
4972
 
4973
+ /** Pointer intersections a 3D object can be placed on (Pointer.Colliders MODEL and SWEEP) */
4974
+ const PLACEMENT_SURFACES = ['intersectedobject.model', 'intersectedobject.sweep'];
4364
4975
  class MatterportService {
4365
4976
  sdkKey;
4366
4977
  router;
@@ -4381,6 +4992,18 @@ class MatterportService {
4381
4992
  isMeasureModeOn = false;
4382
4993
  interactionMode = ViewerInteractions.DEFAULT;
4383
4994
  cameraMode = 'INSIDE';
4995
+ /** Emitted when a placement (tag following the cursor, or 3D object preview) is cancelled by right click or Escape */
4996
+ placementCancelled$ = new Subject();
4997
+ /** Pending interactive placement of a 3D object (see startObject3DPlacement) */
4998
+ object3DPlacement = null;
4999
+ /** Sweeps snapshot taken when navigation is locked (disabled sweeps vanish from Sweep.data) */
5000
+ lockedSweepsSnapshot = {};
5001
+ /**
5002
+ * Every sweep seen in Sweep.data. After an unlock the SDK re-emits the re-enabled sweeps little by
5003
+ * little: a lock asked meanwhile must still know all of them (it found nothing to disable).
5004
+ */
5005
+ knownSweeps = {};
5006
+ keydownTargets = [];
4384
5007
  get mattertagIDs() {
4385
5008
  return this.matterportTagService.mattertagIDs;
4386
5009
  }
@@ -4522,6 +5145,10 @@ class MatterportService {
4522
5145
  * Actions on left click when positioning mattertag in visit
4523
5146
  */
4524
5147
  pointerLeftClickHandler = (evt) => {
5148
+ if (this.object3DPlacement) {
5149
+ this.validateObject3DPlacement();
5150
+ return;
5151
+ }
4525
5152
  if (this.mattertagToFollow) {
4526
5153
  const mattertagData = this.dictionnaryTags.get(this.mattertagToFollow);
4527
5154
  if (mattertagData) {
@@ -4535,8 +5162,7 @@ class MatterportService {
4535
5162
  };
4536
5163
  pointerRightClickHandler = (e) => {
4537
5164
  e.preventDefault();
4538
- this.cancelFollowingCursor();
4539
- alert('action cancelled');
5165
+ this.cancelCurrentPlacement();
4540
5166
  };
4541
5167
  pointerMiddleClickHandler = (e) => {
4542
5168
  this.pointerService.updateCursorDisplay(this.poseMatterport);
@@ -4583,6 +5209,7 @@ class MatterportService {
4583
5209
  onRightClick: this.pointerRightClickHandler.bind(this),
4584
5210
  onMiddleClick: this.pointerMiddleClickHandler.bind(this),
4585
5211
  });
5212
+ this.registerEscapeListener(iframe);
4586
5213
  // Load SDK
4587
5214
  const showcaseWindow = iframe.contentWindow;
4588
5215
  iframe.addEventListener('load', async () => {
@@ -4659,6 +5286,9 @@ class MatterportService {
4659
5286
  // (we are about to validate, but it exists in sdk already)
4660
5287
  this.enable_following_tag(this.mattertagToFollow);
4661
5288
  }
5289
+ if (this.object3DPlacement) {
5290
+ this.updateObject3DPlacementPreview(intersection);
5291
+ }
4662
5292
  if (this.pointerService) {
4663
5293
  this.pointerService.setCursorPositionButtonDisplayed(false);
4664
5294
  }
@@ -4684,6 +5314,11 @@ class MatterportService {
4684
5314
  // subscribe to sweeps
4685
5315
  this.sdk.Sweep.data.subscribe({
4686
5316
  onCollectionUpdated: function subscr(collection) {
5317
+ this.knownSweeps = { ...this.knownSweeps, ...collection };
5318
+ if (this.object3DService.isNavigationLocked()) {
5319
+ // sweeps disabled by the lock are no longer in the collection: keep them known
5320
+ collection = { ...this.lockedSweepsSnapshot, ...collection };
5321
+ }
4687
5322
  this.sweeps = Object.keys(collection);
4688
5323
  this.sweepCollection = collection;
4689
5324
  // Rebuild sid ↔ uuid maps whenever the collection updates
@@ -4795,7 +5430,7 @@ class MatterportService {
4795
5430
  // Pointer trick
4796
5431
  // Create a div that will appear when the cursor is still
4797
5432
  // It will intercept the click of the mouse before it trigerring the Matterport's tour navigation
4798
- this.pointerService.startPointerTrick(this.sdk, this.container, () => this.poseMatterport, () => this.poseCamera, () => this.interactionMode, () => this.mattertagToFollow);
5433
+ this.pointerService.startPointerTrick(this.sdk, this.container, () => this.poseMatterport, () => this.poseCamera, () => this.interactionMode, () => this.getFollowTarget());
4799
5434
  /**
4800
5435
  * Transitions
4801
5436
  */
@@ -4867,7 +5502,7 @@ class MatterportService {
4867
5502
  * Styling of pointer
4868
5503
  */
4869
5504
  updatePointerTrick() {
4870
- this.pointerService.updatePointerTrick(this.sdk, this.container, this.poseMatterport, this.poseCamera, this.interactionMode, this.mattertagToFollow);
5505
+ this.pointerService.updatePointerTrick(this.sdk, this.container, this.poseMatterport, this.poseCamera, this.interactionMode, this.getFollowTarget());
4871
5506
  }
4872
5507
  /**
4873
5508
  * Triggers a tag visibility refresh based on current camera mode, floor and sweep.
@@ -5184,22 +5819,22 @@ class MatterportService {
5184
5819
  );
5185
5820
  }*/
5186
5821
  addCustomCss() {
5187
- const css = `
5188
- .footer-ui,
5189
- .showcase-footer,
5190
- .mp-nova-btn-group,
5191
- .footer-divider,
5192
- .logo-link,
5193
- .footer-logo,
5194
- .bottom-ui,
5195
- img.logo-image,
5196
- img[class^="logo-image"],
5197
- img[class*=" logo-image"],
5198
- [data-testid="showcase-footer"] {
5199
- opacity: 0 !important;
5200
- visibility: hidden !important;
5201
- pointer-events: none !important;
5202
- }
5822
+ const css = `
5823
+ .footer-ui,
5824
+ .showcase-footer,
5825
+ .mp-nova-btn-group,
5826
+ .footer-divider,
5827
+ .logo-link,
5828
+ .footer-logo,
5829
+ .bottom-ui,
5830
+ img.logo-image,
5831
+ img[class^="logo-image"],
5832
+ img[class*=" logo-image"],
5833
+ [data-testid="showcase-footer"] {
5834
+ opacity: 0 !important;
5835
+ visibility: hidden !important;
5836
+ pointer-events: none !important;
5837
+ }
5203
5838
  `;
5204
5839
  // 1st method: Via Scene SDK
5205
5840
  if (this.sdk?.Scene?.addStyle) {
@@ -5296,6 +5931,9 @@ class MatterportService {
5296
5931
  * @param uuid
5297
5932
  */
5298
5933
  async deleteObject3D(uuid) {
5934
+ if (this.object3DPlacement?.uuid === uuid) {
5935
+ this.cancelObject3DPlacement();
5936
+ }
5299
5937
  return this.object3DService.deleteObject3D(uuid);
5300
5938
  }
5301
5939
  getObject3DModelNodeFromDictionnary(uuid) {
@@ -5340,6 +5978,11 @@ class MatterportService {
5340
5978
  * Cancels following of cursor (meaning deleting last Mattertag)
5341
5979
  */
5342
5980
  cancelFollowingCursor() {
5981
+ if (this.object3DPlacement) {
5982
+ // generic cancel (toolbox buttons...) while a 3D object follows the cursor
5983
+ this.cancelObject3DPlacement();
5984
+ return;
5985
+ }
5343
5986
  this.deleteLastMattertag();
5344
5987
  this.setInteractionMode(ViewerInteractions.DEFAULT);
5345
5988
  }
@@ -5708,7 +6351,10 @@ class MatterportService {
5708
6351
  * Clear all variables, deletes all tags (when viewer is remover or model changed=reload needed)
5709
6352
  */
5710
6353
  async clearAll() {
5711
- console.log('removing viewer');
6354
+ this.cancelObject3DPlacement();
6355
+ this.removeEscapeListener();
6356
+ this.lockedSweepsSnapshot = {};
6357
+ this.knownSweeps = {};
5712
6358
  this.clearViewSubscriptions();
5713
6359
  this.viewsByType.clear();
5714
6360
  this.currentView = null;
@@ -5732,6 +6378,9 @@ class MatterportService {
5732
6378
  async init3DObjectViewer() {
5733
6379
  return this.object3DService.init3DObjectViewer(this.sdk);
5734
6380
  }
6381
+ /**
6382
+ * Adds a 3D model; resolves once it is loaded, rejects on unknown format or load timeout (~15 s).
6383
+ */
5735
6384
  async add3DObject(obj, mode) {
5736
6385
  return this.object3DService.add3DObject(this.sdk, obj, mode);
5737
6386
  }
@@ -5741,7 +6390,43 @@ class MatterportService {
5741
6390
  isObjectVisible(objectId) {
5742
6391
  return this.object3DService.isObjectVisible(objectId);
5743
6392
  }
5744
- async pointCameraTo3DObject(objectId) {
6393
+ /** Moves the camera to the object (centered on its bounding box). Resolves false when the object is unknown. */
6394
+ /**
6395
+ * Moves the camera to the sweep closest to a 3D object and turns it toward the object.
6396
+ * @param visibleArea client X bounds of the part of the viewer not covered by other panels
6397
+ * (the lateral menu covers the right of the viewer): the object is centered there.
6398
+ * Falls back to the temporary tag navigation when no sweep can be used.
6399
+ */
6400
+ async pointCameraTo3DObject(objectId, visibleArea) {
6401
+ const box = this.object3DService.getObject3DBoundingBox(objectId);
6402
+ const sweep = box ? this.findNearestSweep(box.center) : null;
6403
+ const sweepData = sweep ? { ...this.lockedSweepsSnapshot, ...(this.sweepCollection ?? {}) }[sweep.sid] : null;
6404
+ if (box && sweepData?.position && this.sdk?.Sweep) {
6405
+ const projection = this.poseCamera?.projection;
6406
+ const tanHalfFov = projection?.[0] ? 1 / projection[0] : 1;
6407
+ let screenX = 0;
6408
+ const viewer = document.querySelector('#viewer-module');
6409
+ if (visibleArea && viewer) {
6410
+ const rect = viewer.getBoundingClientRect();
6411
+ screenX = visibleCenterScreenX(rect, visibleArea.left, visibleArea.right);
6412
+ }
6413
+ const rotation = computeFramingRotation(sweepData.position, box.center, tanHalfFov, screenX);
6414
+ try {
6415
+ if (this.poseCamera?.sweep === sweep.sid) {
6416
+ await this.sdk.Camera.setRotation(rotation, { speed: 80 });
6417
+ }
6418
+ else {
6419
+ await this.sdk.Sweep.moveTo(sweep.sid, {
6420
+ rotation,
6421
+ transition: this.sdk.Sweep.Transition?.FLY,
6422
+ });
6423
+ }
6424
+ return true;
6425
+ }
6426
+ catch (error) {
6427
+ // disabled or unreachable sweep: use the tag navigation below
6428
+ }
6429
+ }
5745
6430
  return this.object3DService.pointCameraTo3DObject(this.sdk, objectId, this.createMattertagFromPOI.bind(this), this.goToTag.bind(this), this.deleteLastMattertag.bind(this), this.getTagFromElementId.bind(this));
5746
6431
  }
5747
6432
  getSceneNodeFromObject3DId(uuid) {
@@ -5756,8 +6441,252 @@ class MatterportService {
5756
6441
  removeGizmoFromLastObject() {
5757
6442
  this.object3DService.removeGizmoFromLastObject();
5758
6443
  }
5759
- toggleViewFrustum() {
5760
- this.object3DService.toggleViewFrustum();
6444
+ /** Toggles the frustum of the security camera `uuid` (default: the last loaded one) */
6445
+ toggleViewFrustum(uuid) {
6446
+ this.object3DService.toggleViewFrustum(uuid);
6447
+ }
6448
+ setViewFrustum(uuid, visible) {
6449
+ return this.object3DService.setViewFrustum(uuid, visible);
6450
+ }
6451
+ isViewFrustumVisible(uuid) {
6452
+ return this.object3DService.isViewFrustumVisible(uuid);
6453
+ }
6454
+ //
6455
+ // ---------- 3D objects: geometry, transform, gizmo ----------
6456
+ //
6457
+ /** Emits on gizmo changes and on setObject3DTransform / placeObject3DOnSurface / normalizeObject3DSize */
6458
+ get object3DTransformChanged$() {
6459
+ return this.object3DService.object3DTransformChanged$;
6460
+ }
6461
+ /** World-space bounding box, null when the object is unknown or not loaded */
6462
+ getObject3DBoundingBox(uuid) {
6463
+ return this.object3DService.getObject3DBoundingBox(uuid);
6464
+ }
6465
+ /** Dimensions of the model in meters, without its rotation */
6466
+ getObject3DDimensions(uuid) {
6467
+ return this.object3DService.getObject3DDimensions(uuid);
6468
+ }
6469
+ /** Uniform scale so the largest dimension equals `targetSizeMeters`; returns the new scale or null */
6470
+ normalizeObject3DSize(uuid, targetSizeMeters) {
6471
+ return this.object3DService.normalizeObject3DSize(uuid, targetSizeMeters);
6472
+ }
6473
+ /** Moves the object so its bounding box bottom-center (top-center on a ceiling) lies on `point` */
6474
+ placeObject3DOnSurface(uuid, point, normal, frontYawOffset = 0) {
6475
+ return this.object3DService.placeObject3DOnSurface(uuid, point, normal, frontYawOffset);
6476
+ }
6477
+ getObject3DTransform(uuid) {
6478
+ return this.object3DService.getObject3DTransform(uuid);
6479
+ }
6480
+ /** Rotation in radians */
6481
+ setObject3DTransform(uuid, transform) {
6482
+ return this.object3DService.setObject3DTransform(uuid, transform);
6483
+ }
6484
+ attachGizmo(uuid, mode) {
6485
+ return this.object3DService.attachGizmo(uuid, mode, this.sdk);
6486
+ }
6487
+ setGizmoMode(mode) {
6488
+ this.object3DService.setGizmoMode(mode);
6489
+ }
6490
+ detachGizmo(uuid) {
6491
+ this.object3DService.detachGizmo(uuid);
6492
+ }
6493
+ isGizmoAttached(uuid) {
6494
+ return this.object3DService.isGizmoAttached(uuid);
6495
+ }
6496
+ /** null disables snapping (default) */
6497
+ setGizmoSnap(snap) {
6498
+ this.object3DService.setGizmoSnap(snap);
6499
+ }
6500
+ /** Limits of a translate drag from its start (default 2 m, +/- 1.5 m vertically) */
6501
+ setGizmoMaxDragDistance(maxDistance, maxHeight) {
6502
+ this.object3DService.setGizmoMaxDragDistance(maxDistance, maxHeight);
6503
+ }
6504
+ /**
6505
+ * Locks / unlocks camera navigation to other sweeps while a gizmo is edited: every enabled sweep
6506
+ * except the current one is disabled, and exactly those are re-enabled on unlock.
6507
+ * @returns false when the lock could not be applied (SDK not ready, current sweep unknown)
6508
+ */
6509
+ async setNavigationLocked(locked) {
6510
+ if (!this.sdk)
6511
+ return false;
6512
+ const currentSweep = this.poseCamera?.sweep ?? null;
6513
+ const sweeps = { ...this.knownSweeps, ...(this.sweepCollection ?? {}) };
6514
+ if (locked && !this.object3DService.isNavigationLocked()) {
6515
+ this.lockedSweepsSnapshot = sweeps;
6516
+ }
6517
+ const keepDisabled = (this.forbiddenSweeps ?? []).map((s) => this.navigationService.resolveSweepSid(s));
6518
+ const done = await this.object3DService.setNavigationLocked(this.sdk, locked, sweeps, currentSweep, keepDisabled);
6519
+ if (!locked || !done) {
6520
+ this.lockedSweepsSnapshot = {};
6521
+ }
6522
+ return done;
6523
+ }
6524
+ isNavigationLocked() {
6525
+ return this.object3DService.isNavigationLocked();
6526
+ }
6527
+ //
6528
+ // ---------- 3D objects: interactive placement ----------
6529
+ //
6530
+ /**
6531
+ * Makes an already loaded model follow the cursor on the surfaces of the visit.
6532
+ * Left click resolves with the surface point / normal under the cursor (read the object origin
6533
+ * with getObject3DTransform); right click or Escape hides the model and resolves null
6534
+ * (deleting it is up to the caller). Resolves null right away if another interaction is running.
6535
+ */
6536
+ startObject3DPlacement(uuid, options = {}) {
6537
+ if (!this.object3DService.getObject3DModelNodeFromDictionnary(uuid)) {
6538
+ return Promise.resolve(null);
6539
+ }
6540
+ if (this.object3DPlacement) {
6541
+ this.cancelObject3DPlacement();
6542
+ }
6543
+ if (this.interactionMode !== ViewerInteractions.DEFAULT) {
6544
+ return Promise.resolve(null);
6545
+ }
6546
+ return new Promise((resolve) => {
6547
+ this.object3DPlacement = { uuid, resolve, lastPoint: null, lastNormal: null, frontYawOffset: options.frontYawOffset ?? 0 };
6548
+ this.object3DService.setObject3DColliderEnabled(uuid, false);
6549
+ this.object3DService.setObject3DVisible(uuid, false);
6550
+ this.setInteractionMode(ViewerInteractions.PLACING_OBJECT3D);
6551
+ if (this.poseMatterport?.position) {
6552
+ this.updateObject3DPlacementPreview(this.poseMatterport);
6553
+ }
6554
+ });
6555
+ }
6556
+ isPlacingObject3D() {
6557
+ return !!this.object3DPlacement;
6558
+ }
6559
+ /** Cancels the running object placement (model hidden, promise resolves null) */
6560
+ cancelObject3DPlacement() {
6561
+ const placement = this.object3DPlacement;
6562
+ if (!placement)
6563
+ return;
6564
+ this.endObject3DPlacement();
6565
+ this.object3DService.setObject3DVisible(placement.uuid, false);
6566
+ placement.resolve(null);
6567
+ }
6568
+ /**
6569
+ * Drops the object where it is previewed (same as a left click). Touch screens have no hover: each
6570
+ * tap moves the preview, and the app confirms with a button.
6571
+ * @returns false when no surface was pointed yet
6572
+ */
6573
+ confirmObject3DPlacement() {
6574
+ if (!this.object3DPlacement?.lastPoint)
6575
+ return false;
6576
+ this.validateObject3DPlacement();
6577
+ return true;
6578
+ }
6579
+ /** True once the preview stands on a surface (the placement can be confirmed) */
6580
+ hasObject3DPlacementPoint() {
6581
+ return !!this.object3DPlacement?.lastPoint;
6582
+ }
6583
+ validateObject3DPlacement() {
6584
+ const placement = this.object3DPlacement;
6585
+ if (!placement || !placement.lastPoint)
6586
+ return; // no surface under the cursor yet
6587
+ this.endObject3DPlacement();
6588
+ placement.resolve({
6589
+ position: placement.lastPoint,
6590
+ normal: placement.lastNormal ?? { x: 0, y: 1, z: 0 },
6591
+ });
6592
+ }
6593
+ endObject3DPlacement() {
6594
+ const placement = this.object3DPlacement;
6595
+ if (!placement)
6596
+ return;
6597
+ this.object3DPlacement = null;
6598
+ this.object3DService.setObject3DColliderEnabled(placement.uuid, true);
6599
+ this.setInteractionMode(ViewerInteractions.DEFAULT);
6600
+ }
6601
+ updateObject3DPlacementPreview(intersection) {
6602
+ const placement = this.object3DPlacement;
6603
+ if (!placement || !intersection?.position)
6604
+ return;
6605
+ // Only the scanned surfaces: hitting the preview itself (reported as "unknown") would push it away
6606
+ // along the ray at each move, until it lands kilometers from the camera
6607
+ if (typeof intersection.object === 'string' && !PLACEMENT_SURFACES.includes(intersection.object))
6608
+ return;
6609
+ const point = { x: intersection.position.x, y: intersection.position.y, z: intersection.position.z };
6610
+ const normal = intersection.normal
6611
+ ? { x: intersection.normal.x, y: intersection.normal.y, z: intersection.normal.z }
6612
+ : { x: 0, y: 1, z: 0 };
6613
+ if (!placement.lastPoint) {
6614
+ // hidden until it is on a surface (it was loaded at the camera position)
6615
+ this.object3DService.setObject3DVisible(placement.uuid, true);
6616
+ }
6617
+ placement.lastPoint = point;
6618
+ placement.lastNormal = normal;
6619
+ this.object3DService.placeObject3DOnSurface(placement.uuid, point, normal, placement.frontYawOffset);
6620
+ }
6621
+ /** Token for the pointer trick: truthy while something follows the cursor (tag or 3D object) */
6622
+ getFollowTarget() {
6623
+ return this.mattertagToFollow ?? this.object3DPlacement?.uuid ?? null;
6624
+ }
6625
+ /**
6626
+ * Cancels whatever follows the cursor (right click / Escape): the 3D object preview, or the tag.
6627
+ * Emits placementCancelled$ when something was actually running.
6628
+ */
6629
+ cancelCurrentPlacement() {
6630
+ const wasRunning = this.interactionMode !== ViewerInteractions.DEFAULT || !!this.object3DPlacement;
6631
+ // nothing follows the cursor: a plain right click must not remove the last (existing) tag
6632
+ if (!wasRunning)
6633
+ return;
6634
+ if (this.object3DPlacement) {
6635
+ this.cancelObject3DPlacement();
6636
+ }
6637
+ else {
6638
+ this.cancelFollowingCursor();
6639
+ }
6640
+ this.placementCancelled$.next();
6641
+ }
6642
+ /** Escape cancels any placement. Listens on the page and on the (same-origin) Showcase iframe, which takes the focus on click. */
6643
+ registerEscapeListener(iframe) {
6644
+ this.removeEscapeListener();
6645
+ const handler = (e) => {
6646
+ if (e.key !== 'Escape')
6647
+ return;
6648
+ if (this.interactionMode !== ViewerInteractions.DEFAULT || this.object3DPlacement) {
6649
+ this.cancelCurrentPlacement();
6650
+ }
6651
+ };
6652
+ const targets = [document];
6653
+ try {
6654
+ if (iframe?.contentWindow)
6655
+ targets.push(iframe.contentWindow);
6656
+ }
6657
+ catch { /* cross-origin iframe: page listener only */ }
6658
+ targets.forEach((target) => {
6659
+ target.addEventListener('keydown', handler);
6660
+ this.keydownTargets.push({ target, handler });
6661
+ });
6662
+ }
6663
+ removeEscapeListener() {
6664
+ this.keydownTargets.forEach(({ target, handler }) => target.removeEventListener('keydown', handler));
6665
+ this.keydownTargets = [];
6666
+ }
6667
+ ngOnDestroy() {
6668
+ this.cancelObject3DPlacement();
6669
+ this.removeEscapeListener();
6670
+ }
6671
+ //
6672
+ // ---------- Sweeps ----------
6673
+ //
6674
+ /**
6675
+ * Closest enabled sweep to a world position (for instance to re-assign the zone of a moved object).
6676
+ * `sweepId` is the DB-format id (like currentSweep), `sid` the SDK id. null when no sweep is known.
6677
+ */
6678
+ findNearestSweep(position) {
6679
+ const collection = { ...this.lockedSweepsSnapshot, ...(this.sweepCollection ?? {}) };
6680
+ const sids = Object.keys(collection).filter((sid) => collection[sid]?.position && collection[sid].enabled !== false);
6681
+ const best = pickNearestSweepOnSameStorey(sids.map((sid) => ({ sid, position: collection[sid].position, floorY: sweepFloorY(collection[sid]) })), position);
6682
+ const data = best ? collection[best] : null;
6683
+ if (!best)
6684
+ return null;
6685
+ return {
6686
+ sweepId: this.navigationService.resolveSweepUuid(best),
6687
+ sid: best,
6688
+ floorSequence: data.floorInfo?.sequence ?? (typeof data.floor === 'number' ? data.floor : null),
6689
+ };
5761
6690
  }
5762
6691
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MatterportService, deps: [{ token: 'config' }, { token: MATTERPORT_SDK_KEY }, { 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 });
5763
6692
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MatterportService, providedIn: 'root' });
@@ -9663,11 +10592,11 @@ class ModalSwitchVisitComponent {
9663
10592
  });
9664
10593
  }
9665
10594
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ModalSwitchVisitComponent, deps: [{ token: i1$2.NgbActiveModal }, { token: i1$1.Router }, { token: ViewerService }], target: i0.ɵɵFactoryTarget.Component });
9666
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: ModalSwitchVisitComponent, selector: "lib-modal-switch-visit", inputs: { content: "content", model3D: "model3D", spaceID: "spaceID", dataToMove: "dataToMove" }, ngImport: i0, template: "<div class=\"modal-container\">\n <p>{{content}}</p>\n <button class=\"btn btn-outline-primary rounded-pill\" (click)=\"switchVisit()\">{{'Yes, switch visit' | translate }}</button>\n <button class=\"btn btn-outline-primary rounded-pill\"(click)=\"activeModal.close()\">{{'No, stay here' | translate }}</button>\n</div>\n", styles: [".modal-container{margin:10px}button{margin-right:8px}\n"], dependencies: [{ kind: "pipe", type: i1.TranslatePipe, name: "translate" }] });
10595
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: ModalSwitchVisitComponent, selector: "lib-modal-switch-visit", inputs: { content: "content", model3D: "model3D", spaceID: "spaceID", dataToMove: "dataToMove" }, ngImport: i0, template: "<div class=\"modal-container\">\r\n <p>{{content}}</p>\r\n <button class=\"btn btn-outline-primary rounded-pill\" (click)=\"switchVisit()\">{{'Yes, switch visit' | translate }}</button>\r\n <button class=\"btn btn-outline-primary rounded-pill\"(click)=\"activeModal.close()\">{{'No, stay here' | translate }}</button>\r\n</div>\r\n", styles: [".modal-container{margin:10px}button{margin-right:8px}\n"], dependencies: [{ kind: "pipe", type: i1.TranslatePipe, name: "translate" }] });
9667
10596
  }
9668
10597
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ModalSwitchVisitComponent, decorators: [{
9669
10598
  type: Component,
9670
- args: [{ selector: 'lib-modal-switch-visit', template: "<div class=\"modal-container\">\n <p>{{content}}</p>\n <button class=\"btn btn-outline-primary rounded-pill\" (click)=\"switchVisit()\">{{'Yes, switch visit' | translate }}</button>\n <button class=\"btn btn-outline-primary rounded-pill\"(click)=\"activeModal.close()\">{{'No, stay here' | translate }}</button>\n</div>\n", styles: [".modal-container{margin:10px}button{margin-right:8px}\n"] }]
10599
+ args: [{ selector: 'lib-modal-switch-visit', template: "<div class=\"modal-container\">\r\n <p>{{content}}</p>\r\n <button class=\"btn btn-outline-primary rounded-pill\" (click)=\"switchVisit()\">{{'Yes, switch visit' | translate }}</button>\r\n <button class=\"btn btn-outline-primary rounded-pill\"(click)=\"activeModal.close()\">{{'No, stay here' | translate }}</button>\r\n</div>\r\n", styles: [".modal-container{margin:10px}button{margin-right:8px}\n"] }]
9671
10600
  }], ctorParameters: () => [{ type: i1$2.NgbActiveModal }, { type: i1$1.Router }, { type: ViewerService }], propDecorators: { content: [{
9672
10601
  type: Input
9673
10602
  }], model3D: [{
@@ -14386,11 +15315,11 @@ class SearchBarComponent {
14386
15315
  this.searchEvent.emit(results);
14387
15316
  }
14388
15317
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: SearchBarComponent, deps: [{ token: SearchService }], target: i0.ɵɵFactoryTarget.Component });
14389
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: SearchBarComponent, selector: "lib-search-bar", inputs: { searchable: "searchable", objectType: "objectType" }, outputs: { searchEvent: "searchEvent" }, ngImport: i0, template: "<div class=\"col-md-8 searchbar\">\n <div class=\"row depend\">\n <div class=\"col-md-5\">\n <input id=\"searchbar\" type=\"text\" class=\"form-control\" placeholder=\"keyword\" />\n </div>\n <div class=\"col-md-5\">\n <button class=\"btn btn-outline-primary rounded-pill\" (click)=\"onSubmit()\">{{ 'Search' |\n translate}}</button>\n </div>\n </div>\n</div>", styles: [".searchbar{margin-bottom:10px}\n"], dependencies: [{ kind: "pipe", type: i1.TranslatePipe, name: "translate" }] });
15318
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: SearchBarComponent, selector: "lib-search-bar", inputs: { searchable: "searchable", objectType: "objectType" }, outputs: { searchEvent: "searchEvent" }, ngImport: i0, template: "<div class=\"col-md-8 searchbar\">\r\n <div class=\"row depend\">\r\n <div class=\"col-md-5\">\r\n <input id=\"searchbar\" type=\"text\" class=\"form-control\" placeholder=\"keyword\" />\r\n </div>\r\n <div class=\"col-md-5\">\r\n <button class=\"btn btn-outline-primary rounded-pill\" (click)=\"onSubmit()\">{{ 'Search' |\r\n translate}}</button>\r\n </div>\r\n </div>\r\n</div>", styles: [".searchbar{margin-bottom:10px}\n"], dependencies: [{ kind: "pipe", type: i1.TranslatePipe, name: "translate" }] });
14390
15319
  }
14391
15320
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: SearchBarComponent, decorators: [{
14392
15321
  type: Component,
14393
- args: [{ selector: 'lib-search-bar', template: "<div class=\"col-md-8 searchbar\">\n <div class=\"row depend\">\n <div class=\"col-md-5\">\n <input id=\"searchbar\" type=\"text\" class=\"form-control\" placeholder=\"keyword\" />\n </div>\n <div class=\"col-md-5\">\n <button class=\"btn btn-outline-primary rounded-pill\" (click)=\"onSubmit()\">{{ 'Search' |\n translate}}</button>\n </div>\n </div>\n</div>", styles: [".searchbar{margin-bottom:10px}\n"] }]
15322
+ args: [{ selector: 'lib-search-bar', template: "<div class=\"col-md-8 searchbar\">\r\n <div class=\"row depend\">\r\n <div class=\"col-md-5\">\r\n <input id=\"searchbar\" type=\"text\" class=\"form-control\" placeholder=\"keyword\" />\r\n </div>\r\n <div class=\"col-md-5\">\r\n <button class=\"btn btn-outline-primary rounded-pill\" (click)=\"onSubmit()\">{{ 'Search' |\r\n translate}}</button>\r\n </div>\r\n </div>\r\n</div>", styles: [".searchbar{margin-bottom:10px}\n"] }]
14394
15323
  }], ctorParameters: () => [{ type: SearchService }], propDecorators: { searchable: [{
14395
15324
  type: Input
14396
15325
  }], objectType: [{
@@ -14838,11 +15767,11 @@ class LoaderComponent {
14838
15767
  document.documentElement.style.setProperty('--color', this.color);
14839
15768
  }
14840
15769
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: LoaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
14841
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: LoaderComponent, selector: "lib-loader", inputs: { useLogo: "useLogo", color: "color" }, ngImport: i0, template: "<div class=\"sk-fading-circle\">\n <img *ngIf=\"useLogo\" src=\"../../../../assets/icons/smarterplan.svg\">\n <div class=\"sk-circle1 sk-circle\"></div>\n <div class=\"sk-circle2 sk-circle\"></div>\n <div class=\"sk-circle3 sk-circle\"></div>\n <div class=\"sk-circle4 sk-circle\"></div>\n <div class=\"sk-circle5 sk-circle\"></div>\n <div class=\"sk-circle6 sk-circle\"></div>\n <div class=\"sk-circle7 sk-circle\"></div>\n <div class=\"sk-circle8 sk-circle\"></div>\n <div class=\"sk-circle9 sk-circle\"></div>\n <div class=\"sk-circle10 sk-circle\"></div>\n <div class=\"sk-circle11 sk-circle\"></div>\n <div class=\"sk-circle12 sk-circle\"></div>\n</div>\n", styles: [".sk-fading-circle{width:88px;height:88px;position:relative}.sk-fading-circle img{width:2rem;position:absolute;left:calc(50% - 16px);top:calc(50% - 16px)}.sk-fading-circle .sk-circle{width:100%;height:100%;position:absolute;left:0;top:0}.sk-fading-circle .sk-circle:before{content:\"\";display:block;margin:0 auto;width:15%;height:15%;background-color:var(--color);border-radius:100%;-webkit-animation:sk-circleFadeDelay 1.2s infinite ease-in-out both;animation:sk-circleFadeDelay 1.2s infinite ease-in-out both}.sk-fading-circle .sk-circle2{-webkit-transform:rotate(30deg);-ms-transform:rotate(30deg);transform:rotate(30deg)}.sk-fading-circle .sk-circle3{-webkit-transform:rotate(60deg);-ms-transform:rotate(60deg);transform:rotate(60deg)}.sk-fading-circle .sk-circle4{-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.sk-fading-circle .sk-circle5{-webkit-transform:rotate(120deg);-ms-transform:rotate(120deg);transform:rotate(120deg)}.sk-fading-circle .sk-circle6{-webkit-transform:rotate(150deg);-ms-transform:rotate(150deg);transform:rotate(150deg)}.sk-fading-circle .sk-circle7{-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.sk-fading-circle .sk-circle8{-webkit-transform:rotate(210deg);-ms-transform:rotate(210deg);transform:rotate(210deg)}.sk-fading-circle .sk-circle9{-webkit-transform:rotate(240deg);-ms-transform:rotate(240deg);transform:rotate(240deg)}.sk-fading-circle .sk-circle10{-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.sk-fading-circle .sk-circle11{-webkit-transform:rotate(300deg);-ms-transform:rotate(300deg);transform:rotate(300deg)}.sk-fading-circle .sk-circle12{-webkit-transform:rotate(330deg);-ms-transform:rotate(330deg);transform:rotate(330deg)}.sk-fading-circle .sk-circle2:before{-webkit-animation-delay:-1.1s;animation-delay:-1.1s}.sk-fading-circle .sk-circle3:before{-webkit-animation-delay:-1s;animation-delay:-1s}.sk-fading-circle .sk-circle4:before{-webkit-animation-delay:-.9s;animation-delay:-.9s}.sk-fading-circle .sk-circle5:before{-webkit-animation-delay:-.8s;animation-delay:-.8s}.sk-fading-circle .sk-circle6:before{-webkit-animation-delay:-.7s;animation-delay:-.7s}.sk-fading-circle .sk-circle7:before{-webkit-animation-delay:-.6s;animation-delay:-.6s}.sk-fading-circle .sk-circle8:before{-webkit-animation-delay:-.5s;animation-delay:-.5s}.sk-fading-circle .sk-circle9:before{-webkit-animation-delay:-.4s;animation-delay:-.4s}.sk-fading-circle .sk-circle10:before{-webkit-animation-delay:-.3s;animation-delay:-.3s}.sk-fading-circle .sk-circle11:before{-webkit-animation-delay:-.2s;animation-delay:-.2s}.sk-fading-circle .sk-circle12:before{-webkit-animation-delay:-.1s;animation-delay:-.1s}@-webkit-keyframes sk-circleFadeDelay{0%,39%,to{opacity:0}40%{opacity:1}}@keyframes sk-circleFadeDelay{0%,39%,to{opacity:0}40%{opacity:1}}\n"], dependencies: [{ kind: "directive", type: i11.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
15770
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: LoaderComponent, selector: "lib-loader", inputs: { useLogo: "useLogo", color: "color" }, ngImport: i0, template: "<div class=\"sk-fading-circle\">\r\n <img *ngIf=\"useLogo\" src=\"../../../../assets/icons/smarterplan.svg\">\r\n <div class=\"sk-circle1 sk-circle\"></div>\r\n <div class=\"sk-circle2 sk-circle\"></div>\r\n <div class=\"sk-circle3 sk-circle\"></div>\r\n <div class=\"sk-circle4 sk-circle\"></div>\r\n <div class=\"sk-circle5 sk-circle\"></div>\r\n <div class=\"sk-circle6 sk-circle\"></div>\r\n <div class=\"sk-circle7 sk-circle\"></div>\r\n <div class=\"sk-circle8 sk-circle\"></div>\r\n <div class=\"sk-circle9 sk-circle\"></div>\r\n <div class=\"sk-circle10 sk-circle\"></div>\r\n <div class=\"sk-circle11 sk-circle\"></div>\r\n <div class=\"sk-circle12 sk-circle\"></div>\r\n</div>\r\n", styles: [".sk-fading-circle{width:88px;height:88px;position:relative}.sk-fading-circle img{width:2rem;position:absolute;left:calc(50% - 16px);top:calc(50% - 16px)}.sk-fading-circle .sk-circle{width:100%;height:100%;position:absolute;left:0;top:0}.sk-fading-circle .sk-circle:before{content:\"\";display:block;margin:0 auto;width:15%;height:15%;background-color:var(--color);border-radius:100%;-webkit-animation:sk-circleFadeDelay 1.2s infinite ease-in-out both;animation:sk-circleFadeDelay 1.2s infinite ease-in-out both}.sk-fading-circle .sk-circle2{-webkit-transform:rotate(30deg);-ms-transform:rotate(30deg);transform:rotate(30deg)}.sk-fading-circle .sk-circle3{-webkit-transform:rotate(60deg);-ms-transform:rotate(60deg);transform:rotate(60deg)}.sk-fading-circle .sk-circle4{-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.sk-fading-circle .sk-circle5{-webkit-transform:rotate(120deg);-ms-transform:rotate(120deg);transform:rotate(120deg)}.sk-fading-circle .sk-circle6{-webkit-transform:rotate(150deg);-ms-transform:rotate(150deg);transform:rotate(150deg)}.sk-fading-circle .sk-circle7{-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.sk-fading-circle .sk-circle8{-webkit-transform:rotate(210deg);-ms-transform:rotate(210deg);transform:rotate(210deg)}.sk-fading-circle .sk-circle9{-webkit-transform:rotate(240deg);-ms-transform:rotate(240deg);transform:rotate(240deg)}.sk-fading-circle .sk-circle10{-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.sk-fading-circle .sk-circle11{-webkit-transform:rotate(300deg);-ms-transform:rotate(300deg);transform:rotate(300deg)}.sk-fading-circle .sk-circle12{-webkit-transform:rotate(330deg);-ms-transform:rotate(330deg);transform:rotate(330deg)}.sk-fading-circle .sk-circle2:before{-webkit-animation-delay:-1.1s;animation-delay:-1.1s}.sk-fading-circle .sk-circle3:before{-webkit-animation-delay:-1s;animation-delay:-1s}.sk-fading-circle .sk-circle4:before{-webkit-animation-delay:-.9s;animation-delay:-.9s}.sk-fading-circle .sk-circle5:before{-webkit-animation-delay:-.8s;animation-delay:-.8s}.sk-fading-circle .sk-circle6:before{-webkit-animation-delay:-.7s;animation-delay:-.7s}.sk-fading-circle .sk-circle7:before{-webkit-animation-delay:-.6s;animation-delay:-.6s}.sk-fading-circle .sk-circle8:before{-webkit-animation-delay:-.5s;animation-delay:-.5s}.sk-fading-circle .sk-circle9:before{-webkit-animation-delay:-.4s;animation-delay:-.4s}.sk-fading-circle .sk-circle10:before{-webkit-animation-delay:-.3s;animation-delay:-.3s}.sk-fading-circle .sk-circle11:before{-webkit-animation-delay:-.2s;animation-delay:-.2s}.sk-fading-circle .sk-circle12:before{-webkit-animation-delay:-.1s;animation-delay:-.1s}@-webkit-keyframes sk-circleFadeDelay{0%,39%,to{opacity:0}40%{opacity:1}}@keyframes sk-circleFadeDelay{0%,39%,to{opacity:0}40%{opacity:1}}\n"], dependencies: [{ kind: "directive", type: i11.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
14842
15771
  }
14843
15772
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: LoaderComponent, decorators: [{
14844
15773
  type: Component,
14845
- args: [{ selector: 'lib-loader', template: "<div class=\"sk-fading-circle\">\n <img *ngIf=\"useLogo\" src=\"../../../../assets/icons/smarterplan.svg\">\n <div class=\"sk-circle1 sk-circle\"></div>\n <div class=\"sk-circle2 sk-circle\"></div>\n <div class=\"sk-circle3 sk-circle\"></div>\n <div class=\"sk-circle4 sk-circle\"></div>\n <div class=\"sk-circle5 sk-circle\"></div>\n <div class=\"sk-circle6 sk-circle\"></div>\n <div class=\"sk-circle7 sk-circle\"></div>\n <div class=\"sk-circle8 sk-circle\"></div>\n <div class=\"sk-circle9 sk-circle\"></div>\n <div class=\"sk-circle10 sk-circle\"></div>\n <div class=\"sk-circle11 sk-circle\"></div>\n <div class=\"sk-circle12 sk-circle\"></div>\n</div>\n", styles: [".sk-fading-circle{width:88px;height:88px;position:relative}.sk-fading-circle img{width:2rem;position:absolute;left:calc(50% - 16px);top:calc(50% - 16px)}.sk-fading-circle .sk-circle{width:100%;height:100%;position:absolute;left:0;top:0}.sk-fading-circle .sk-circle:before{content:\"\";display:block;margin:0 auto;width:15%;height:15%;background-color:var(--color);border-radius:100%;-webkit-animation:sk-circleFadeDelay 1.2s infinite ease-in-out both;animation:sk-circleFadeDelay 1.2s infinite ease-in-out both}.sk-fading-circle .sk-circle2{-webkit-transform:rotate(30deg);-ms-transform:rotate(30deg);transform:rotate(30deg)}.sk-fading-circle .sk-circle3{-webkit-transform:rotate(60deg);-ms-transform:rotate(60deg);transform:rotate(60deg)}.sk-fading-circle .sk-circle4{-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.sk-fading-circle .sk-circle5{-webkit-transform:rotate(120deg);-ms-transform:rotate(120deg);transform:rotate(120deg)}.sk-fading-circle .sk-circle6{-webkit-transform:rotate(150deg);-ms-transform:rotate(150deg);transform:rotate(150deg)}.sk-fading-circle .sk-circle7{-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.sk-fading-circle .sk-circle8{-webkit-transform:rotate(210deg);-ms-transform:rotate(210deg);transform:rotate(210deg)}.sk-fading-circle .sk-circle9{-webkit-transform:rotate(240deg);-ms-transform:rotate(240deg);transform:rotate(240deg)}.sk-fading-circle .sk-circle10{-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.sk-fading-circle .sk-circle11{-webkit-transform:rotate(300deg);-ms-transform:rotate(300deg);transform:rotate(300deg)}.sk-fading-circle .sk-circle12{-webkit-transform:rotate(330deg);-ms-transform:rotate(330deg);transform:rotate(330deg)}.sk-fading-circle .sk-circle2:before{-webkit-animation-delay:-1.1s;animation-delay:-1.1s}.sk-fading-circle .sk-circle3:before{-webkit-animation-delay:-1s;animation-delay:-1s}.sk-fading-circle .sk-circle4:before{-webkit-animation-delay:-.9s;animation-delay:-.9s}.sk-fading-circle .sk-circle5:before{-webkit-animation-delay:-.8s;animation-delay:-.8s}.sk-fading-circle .sk-circle6:before{-webkit-animation-delay:-.7s;animation-delay:-.7s}.sk-fading-circle .sk-circle7:before{-webkit-animation-delay:-.6s;animation-delay:-.6s}.sk-fading-circle .sk-circle8:before{-webkit-animation-delay:-.5s;animation-delay:-.5s}.sk-fading-circle .sk-circle9:before{-webkit-animation-delay:-.4s;animation-delay:-.4s}.sk-fading-circle .sk-circle10:before{-webkit-animation-delay:-.3s;animation-delay:-.3s}.sk-fading-circle .sk-circle11:before{-webkit-animation-delay:-.2s;animation-delay:-.2s}.sk-fading-circle .sk-circle12:before{-webkit-animation-delay:-.1s;animation-delay:-.1s}@-webkit-keyframes sk-circleFadeDelay{0%,39%,to{opacity:0}40%{opacity:1}}@keyframes sk-circleFadeDelay{0%,39%,to{opacity:0}40%{opacity:1}}\n"] }]
15774
+ args: [{ selector: 'lib-loader', template: "<div class=\"sk-fading-circle\">\r\n <img *ngIf=\"useLogo\" src=\"../../../../assets/icons/smarterplan.svg\">\r\n <div class=\"sk-circle1 sk-circle\"></div>\r\n <div class=\"sk-circle2 sk-circle\"></div>\r\n <div class=\"sk-circle3 sk-circle\"></div>\r\n <div class=\"sk-circle4 sk-circle\"></div>\r\n <div class=\"sk-circle5 sk-circle\"></div>\r\n <div class=\"sk-circle6 sk-circle\"></div>\r\n <div class=\"sk-circle7 sk-circle\"></div>\r\n <div class=\"sk-circle8 sk-circle\"></div>\r\n <div class=\"sk-circle9 sk-circle\"></div>\r\n <div class=\"sk-circle10 sk-circle\"></div>\r\n <div class=\"sk-circle11 sk-circle\"></div>\r\n <div class=\"sk-circle12 sk-circle\"></div>\r\n</div>\r\n", styles: [".sk-fading-circle{width:88px;height:88px;position:relative}.sk-fading-circle img{width:2rem;position:absolute;left:calc(50% - 16px);top:calc(50% - 16px)}.sk-fading-circle .sk-circle{width:100%;height:100%;position:absolute;left:0;top:0}.sk-fading-circle .sk-circle:before{content:\"\";display:block;margin:0 auto;width:15%;height:15%;background-color:var(--color);border-radius:100%;-webkit-animation:sk-circleFadeDelay 1.2s infinite ease-in-out both;animation:sk-circleFadeDelay 1.2s infinite ease-in-out both}.sk-fading-circle .sk-circle2{-webkit-transform:rotate(30deg);-ms-transform:rotate(30deg);transform:rotate(30deg)}.sk-fading-circle .sk-circle3{-webkit-transform:rotate(60deg);-ms-transform:rotate(60deg);transform:rotate(60deg)}.sk-fading-circle .sk-circle4{-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.sk-fading-circle .sk-circle5{-webkit-transform:rotate(120deg);-ms-transform:rotate(120deg);transform:rotate(120deg)}.sk-fading-circle .sk-circle6{-webkit-transform:rotate(150deg);-ms-transform:rotate(150deg);transform:rotate(150deg)}.sk-fading-circle .sk-circle7{-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.sk-fading-circle .sk-circle8{-webkit-transform:rotate(210deg);-ms-transform:rotate(210deg);transform:rotate(210deg)}.sk-fading-circle .sk-circle9{-webkit-transform:rotate(240deg);-ms-transform:rotate(240deg);transform:rotate(240deg)}.sk-fading-circle .sk-circle10{-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.sk-fading-circle .sk-circle11{-webkit-transform:rotate(300deg);-ms-transform:rotate(300deg);transform:rotate(300deg)}.sk-fading-circle .sk-circle12{-webkit-transform:rotate(330deg);-ms-transform:rotate(330deg);transform:rotate(330deg)}.sk-fading-circle .sk-circle2:before{-webkit-animation-delay:-1.1s;animation-delay:-1.1s}.sk-fading-circle .sk-circle3:before{-webkit-animation-delay:-1s;animation-delay:-1s}.sk-fading-circle .sk-circle4:before{-webkit-animation-delay:-.9s;animation-delay:-.9s}.sk-fading-circle .sk-circle5:before{-webkit-animation-delay:-.8s;animation-delay:-.8s}.sk-fading-circle .sk-circle6:before{-webkit-animation-delay:-.7s;animation-delay:-.7s}.sk-fading-circle .sk-circle7:before{-webkit-animation-delay:-.6s;animation-delay:-.6s}.sk-fading-circle .sk-circle8:before{-webkit-animation-delay:-.5s;animation-delay:-.5s}.sk-fading-circle .sk-circle9:before{-webkit-animation-delay:-.4s;animation-delay:-.4s}.sk-fading-circle .sk-circle10:before{-webkit-animation-delay:-.3s;animation-delay:-.3s}.sk-fading-circle .sk-circle11:before{-webkit-animation-delay:-.2s;animation-delay:-.2s}.sk-fading-circle .sk-circle12:before{-webkit-animation-delay:-.1s;animation-delay:-.1s}@-webkit-keyframes sk-circleFadeDelay{0%,39%,to{opacity:0}40%{opacity:1}}@keyframes sk-circleFadeDelay{0%,39%,to{opacity:0}40%{opacity:1}}\n"] }]
14846
15775
  }], ctorParameters: () => [], propDecorators: { useLogo: [{
14847
15776
  type: Input
14848
15777
  }], color: [{
@@ -14900,11 +15829,11 @@ class SupportModalComponent {
14900
15829
  }
14901
15830
  }
14902
15831
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: SupportModalComponent, deps: [{ token: SupportService }, { token: i1$2.NgbActiveModal }, { token: i3.UntypedFormBuilder }, { token: BaseUserService }, { token: ValidatorsService }], target: i0.ɵɵFactoryTarget.Component });
14903
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: SupportModalComponent, selector: "app-support-modal", ngImport: i0, template: "<div class=\"modal-header\">\n <h5 class=\"modal-title\" translate=\"Help\"></h5>\n</div>\n<div class=\"modal-body\">\n <div class=\"support-loader-container\">\n <lib-loader *ngIf=\"loading\"></lib-loader>\n </div>\n <form (ngSubmit)=\"onSubmit()\" [formGroup]=\"supportForm\" style=\"margin-top:1rem;\" *ngIf=\"!loading && status==='new'\">\n <div>\n <label>{{'Title' | translate}} *</label>\n <div>\n <input type=\"text\" class=\"form-control\" formControlName=\"title\" data-testid=\"new-support-title\"\n [ngClass]=\"{'is-invalid' : validatorsService.isControlError(supportForm.controls['title'])}\"\n maxlength=\"255\">\n </div>\n <div class=\"invalid-feedback\">\n <span data-testid=\"new-support-title-invalid\" [translate]=\"validatorsService.getError(supportForm.controls['title'])\"\n *ngIf=\" validatorsService.isControlError(supportForm.controls['title'])\"></span>\n </div>\n </div>\n <div *ngIf=\"!currentUser\">\n <label>{{'Email' | translate}} *</label>\n <div>\n <input type=\"email\" class=\"form-control\" formControlName=\"email\" data-testid=\"new-support-email\"\n [ngClass]=\"{'is-invalid' : validatorsService.isControlError(supportForm.controls['email'])}\"\n maxlength=\"255\">\n </div>\n <div class=\"invalid-feedback\">\n <span data-testid=\"new-support-email-invalid\" [translate]=\"validatorsService.getError(supportForm.controls['email'])\"\n *ngIf=\" validatorsService.isControlError(supportForm.controls['email'])\"></span>\n </div>\n </div>\n <div>\n <label>{{'Description' | translate}} *</label>\n <div>\n <textarea formControlName=\"description\" class=\"form-control\"\n data-testid=\"new-support-description\"\n [ngClass]=\"{'is-invalid' : validatorsService.isControlError(supportForm.controls['description'])}\"></textarea>\n </div>\n <div class=\"invalid-feedback\" >\n <span data-testid=\"new-support-description-invalid\" [translate]=\" validatorsService.getError(supportForm.controls['description'])\"\n *ngIf=\"validatorsService.isControlError(supportForm.controls['description'])\"></span>\n </div>\n </div>\n </form>\n\n <div *ngIf=\"status ==='send'\" translate=\"Your request has been registered\" data-testid=\"new-support-send-info\">\n </div>\n <div *ngIf=\"status ==='error'\" translate=\"An error has occurred, please renew your request\"\n data-testid=\"new-support-error-info\">\n\n </div>\n</div>\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn rounded-pill btn-museum\" data-dismiss=\"modal\" translate=\"Close\" (click)=\"onClose()\"\n data-testid=\"new-support-close-btn\"></button>\n <button *ngIf=\"!loading && status==='new'\" type=\"button\" class=\"btn rounded-pill btn-museum\" translate=\"Submit\"\n (click)=\"onSubmit()\" [disabled]=\"!supportForm.valid\" data-testid=\"new-support-submit-btn\"></button>\n</div>", styles: [".support-loader-container{width:88px;margin:auto}.invalid-feedback{height:16px}\n"], dependencies: [{ kind: "directive", type: i3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i11.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i11.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: LoaderComponent, selector: "lib-loader", inputs: ["useLogo", "color"] }, { kind: "pipe", type: i1.TranslatePipe, name: "translate" }] });
15832
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: SupportModalComponent, selector: "app-support-modal", ngImport: i0, template: "<div class=\"modal-header\">\r\n <h5 class=\"modal-title\" translate=\"Help\"></h5>\r\n</div>\r\n<div class=\"modal-body\">\r\n <div class=\"support-loader-container\">\r\n <lib-loader *ngIf=\"loading\"></lib-loader>\r\n </div>\r\n <form (ngSubmit)=\"onSubmit()\" [formGroup]=\"supportForm\" style=\"margin-top:1rem;\" *ngIf=\"!loading && status==='new'\">\r\n <div>\r\n <label>{{'Title' | translate}} *</label>\r\n <div>\r\n <input type=\"text\" class=\"form-control\" formControlName=\"title\" data-testid=\"new-support-title\"\r\n [ngClass]=\"{'is-invalid' : validatorsService.isControlError(supportForm.controls['title'])}\"\r\n maxlength=\"255\">\r\n </div>\r\n <div class=\"invalid-feedback\">\r\n <span data-testid=\"new-support-title-invalid\" [translate]=\"validatorsService.getError(supportForm.controls['title'])\"\r\n *ngIf=\" validatorsService.isControlError(supportForm.controls['title'])\"></span>\r\n </div>\r\n </div>\r\n <div *ngIf=\"!currentUser\">\r\n <label>{{'Email' | translate}} *</label>\r\n <div>\r\n <input type=\"email\" class=\"form-control\" formControlName=\"email\" data-testid=\"new-support-email\"\r\n [ngClass]=\"{'is-invalid' : validatorsService.isControlError(supportForm.controls['email'])}\"\r\n maxlength=\"255\">\r\n </div>\r\n <div class=\"invalid-feedback\">\r\n <span data-testid=\"new-support-email-invalid\" [translate]=\"validatorsService.getError(supportForm.controls['email'])\"\r\n *ngIf=\" validatorsService.isControlError(supportForm.controls['email'])\"></span>\r\n </div>\r\n </div>\r\n <div>\r\n <label>{{'Description' | translate}} *</label>\r\n <div>\r\n <textarea formControlName=\"description\" class=\"form-control\"\r\n data-testid=\"new-support-description\"\r\n [ngClass]=\"{'is-invalid' : validatorsService.isControlError(supportForm.controls['description'])}\"></textarea>\r\n </div>\r\n <div class=\"invalid-feedback\" >\r\n <span data-testid=\"new-support-description-invalid\" [translate]=\" validatorsService.getError(supportForm.controls['description'])\"\r\n *ngIf=\"validatorsService.isControlError(supportForm.controls['description'])\"></span>\r\n </div>\r\n </div>\r\n </form>\r\n\r\n <div *ngIf=\"status ==='send'\" translate=\"Your request has been registered\" data-testid=\"new-support-send-info\">\r\n </div>\r\n <div *ngIf=\"status ==='error'\" translate=\"An error has occurred, please renew your request\"\r\n data-testid=\"new-support-error-info\">\r\n\r\n </div>\r\n</div>\r\n<div class=\"modal-footer\">\r\n <button type=\"button\" class=\"btn rounded-pill btn-museum\" data-dismiss=\"modal\" translate=\"Close\" (click)=\"onClose()\"\r\n data-testid=\"new-support-close-btn\"></button>\r\n <button *ngIf=\"!loading && status==='new'\" type=\"button\" class=\"btn rounded-pill btn-museum\" translate=\"Submit\"\r\n (click)=\"onSubmit()\" [disabled]=\"!supportForm.valid\" data-testid=\"new-support-submit-btn\"></button>\r\n</div>", styles: [".support-loader-container{width:88px;margin:auto}.invalid-feedback{height:16px}\n"], dependencies: [{ kind: "directive", type: i3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i11.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i11.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: LoaderComponent, selector: "lib-loader", inputs: ["useLogo", "color"] }, { kind: "pipe", type: i1.TranslatePipe, name: "translate" }] });
14904
15833
  }
14905
15834
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: SupportModalComponent, decorators: [{
14906
15835
  type: Component,
14907
- args: [{ selector: 'app-support-modal', template: "<div class=\"modal-header\">\n <h5 class=\"modal-title\" translate=\"Help\"></h5>\n</div>\n<div class=\"modal-body\">\n <div class=\"support-loader-container\">\n <lib-loader *ngIf=\"loading\"></lib-loader>\n </div>\n <form (ngSubmit)=\"onSubmit()\" [formGroup]=\"supportForm\" style=\"margin-top:1rem;\" *ngIf=\"!loading && status==='new'\">\n <div>\n <label>{{'Title' | translate}} *</label>\n <div>\n <input type=\"text\" class=\"form-control\" formControlName=\"title\" data-testid=\"new-support-title\"\n [ngClass]=\"{'is-invalid' : validatorsService.isControlError(supportForm.controls['title'])}\"\n maxlength=\"255\">\n </div>\n <div class=\"invalid-feedback\">\n <span data-testid=\"new-support-title-invalid\" [translate]=\"validatorsService.getError(supportForm.controls['title'])\"\n *ngIf=\" validatorsService.isControlError(supportForm.controls['title'])\"></span>\n </div>\n </div>\n <div *ngIf=\"!currentUser\">\n <label>{{'Email' | translate}} *</label>\n <div>\n <input type=\"email\" class=\"form-control\" formControlName=\"email\" data-testid=\"new-support-email\"\n [ngClass]=\"{'is-invalid' : validatorsService.isControlError(supportForm.controls['email'])}\"\n maxlength=\"255\">\n </div>\n <div class=\"invalid-feedback\">\n <span data-testid=\"new-support-email-invalid\" [translate]=\"validatorsService.getError(supportForm.controls['email'])\"\n *ngIf=\" validatorsService.isControlError(supportForm.controls['email'])\"></span>\n </div>\n </div>\n <div>\n <label>{{'Description' | translate}} *</label>\n <div>\n <textarea formControlName=\"description\" class=\"form-control\"\n data-testid=\"new-support-description\"\n [ngClass]=\"{'is-invalid' : validatorsService.isControlError(supportForm.controls['description'])}\"></textarea>\n </div>\n <div class=\"invalid-feedback\" >\n <span data-testid=\"new-support-description-invalid\" [translate]=\" validatorsService.getError(supportForm.controls['description'])\"\n *ngIf=\"validatorsService.isControlError(supportForm.controls['description'])\"></span>\n </div>\n </div>\n </form>\n\n <div *ngIf=\"status ==='send'\" translate=\"Your request has been registered\" data-testid=\"new-support-send-info\">\n </div>\n <div *ngIf=\"status ==='error'\" translate=\"An error has occurred, please renew your request\"\n data-testid=\"new-support-error-info\">\n\n </div>\n</div>\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn rounded-pill btn-museum\" data-dismiss=\"modal\" translate=\"Close\" (click)=\"onClose()\"\n data-testid=\"new-support-close-btn\"></button>\n <button *ngIf=\"!loading && status==='new'\" type=\"button\" class=\"btn rounded-pill btn-museum\" translate=\"Submit\"\n (click)=\"onSubmit()\" [disabled]=\"!supportForm.valid\" data-testid=\"new-support-submit-btn\"></button>\n</div>", styles: [".support-loader-container{width:88px;margin:auto}.invalid-feedback{height:16px}\n"] }]
15836
+ args: [{ selector: 'app-support-modal', template: "<div class=\"modal-header\">\r\n <h5 class=\"modal-title\" translate=\"Help\"></h5>\r\n</div>\r\n<div class=\"modal-body\">\r\n <div class=\"support-loader-container\">\r\n <lib-loader *ngIf=\"loading\"></lib-loader>\r\n </div>\r\n <form (ngSubmit)=\"onSubmit()\" [formGroup]=\"supportForm\" style=\"margin-top:1rem;\" *ngIf=\"!loading && status==='new'\">\r\n <div>\r\n <label>{{'Title' | translate}} *</label>\r\n <div>\r\n <input type=\"text\" class=\"form-control\" formControlName=\"title\" data-testid=\"new-support-title\"\r\n [ngClass]=\"{'is-invalid' : validatorsService.isControlError(supportForm.controls['title'])}\"\r\n maxlength=\"255\">\r\n </div>\r\n <div class=\"invalid-feedback\">\r\n <span data-testid=\"new-support-title-invalid\" [translate]=\"validatorsService.getError(supportForm.controls['title'])\"\r\n *ngIf=\" validatorsService.isControlError(supportForm.controls['title'])\"></span>\r\n </div>\r\n </div>\r\n <div *ngIf=\"!currentUser\">\r\n <label>{{'Email' | translate}} *</label>\r\n <div>\r\n <input type=\"email\" class=\"form-control\" formControlName=\"email\" data-testid=\"new-support-email\"\r\n [ngClass]=\"{'is-invalid' : validatorsService.isControlError(supportForm.controls['email'])}\"\r\n maxlength=\"255\">\r\n </div>\r\n <div class=\"invalid-feedback\">\r\n <span data-testid=\"new-support-email-invalid\" [translate]=\"validatorsService.getError(supportForm.controls['email'])\"\r\n *ngIf=\" validatorsService.isControlError(supportForm.controls['email'])\"></span>\r\n </div>\r\n </div>\r\n <div>\r\n <label>{{'Description' | translate}} *</label>\r\n <div>\r\n <textarea formControlName=\"description\" class=\"form-control\"\r\n data-testid=\"new-support-description\"\r\n [ngClass]=\"{'is-invalid' : validatorsService.isControlError(supportForm.controls['description'])}\"></textarea>\r\n </div>\r\n <div class=\"invalid-feedback\" >\r\n <span data-testid=\"new-support-description-invalid\" [translate]=\" validatorsService.getError(supportForm.controls['description'])\"\r\n *ngIf=\"validatorsService.isControlError(supportForm.controls['description'])\"></span>\r\n </div>\r\n </div>\r\n </form>\r\n\r\n <div *ngIf=\"status ==='send'\" translate=\"Your request has been registered\" data-testid=\"new-support-send-info\">\r\n </div>\r\n <div *ngIf=\"status ==='error'\" translate=\"An error has occurred, please renew your request\"\r\n data-testid=\"new-support-error-info\">\r\n\r\n </div>\r\n</div>\r\n<div class=\"modal-footer\">\r\n <button type=\"button\" class=\"btn rounded-pill btn-museum\" data-dismiss=\"modal\" translate=\"Close\" (click)=\"onClose()\"\r\n data-testid=\"new-support-close-btn\"></button>\r\n <button *ngIf=\"!loading && status==='new'\" type=\"button\" class=\"btn rounded-pill btn-museum\" translate=\"Submit\"\r\n (click)=\"onSubmit()\" [disabled]=\"!supportForm.valid\" data-testid=\"new-support-submit-btn\"></button>\r\n</div>", styles: [".support-loader-container{width:88px;margin:auto}.invalid-feedback{height:16px}\n"] }]
14908
15837
  }], ctorParameters: () => [{ type: SupportService }, { type: i1$2.NgbActiveModal }, { type: i3.UntypedFormBuilder }, { type: BaseUserService }, { type: ValidatorsService }] });
14909
15838
 
14910
15839
  class LocaleService {
@@ -16344,11 +17273,11 @@ class NavigationBarComponent {
16344
17273
  this.baseVisibilityService.isChangePositionVisible.next(false);
16345
17274
  }
16346
17275
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NavigationBarComponent, deps: [{ token: 'currentAwsKinesisAnalyticsService' }, { token: i1$1.Router }, { token: SpaceService }, { token: BaseUserService }, { token: ZoneService }, { token: VisitService }, { token: ViewerService }, { token: NavigatorService }, { token: ZoneChangeService }, { token: BaseVisibilityService }], target: i0.ɵɵFactoryTarget.Component });
16347
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: NavigationBarComponent, selector: "lib-navigation-bar", viewQueries: [{ propertyName: "navbarSelectMenu", first: true, predicate: ["navbarSelectMenu"], descendants: true }], ngImport: i0, template: "<nav class=\"navbar navbar-expand-md navbar-dark navbar-inverse second-nav\">\n <div class=\"d-flex w-100\">\n <div #navbarSelectMenu class=\"w-100\" id=\"nav-item-container-blue\">\n <div class=\"d-flex flex-column flex-md-row p-1 mt-2\">\n <select class=\"form-control flex-fill flex-lg-fill-unset location select-menu\"\n [(ngModel)]=\"selectedLocation\" (ngModelChange)=\"onLocationClick()\">\n <option [ngValue]=\"null\" selected>{{ 'Location' | translate }}</option>\n <option *ngFor=\"let loc of locations; index as index\" [ngValue]=\"loc\"> {{ loc.name }}</option>\n </select>\n <select class=\"form-control flex-fill flex-lg-fill-unset select-menu\" (change)=\"onVisitClick($event)\"\n *ngIf=\"visitsPerSpace && visitsPerSpace.length > 1\">\n <option value=\"default\" selected disabled hidden>{{'Visit' | translate}}</option>\n <option *ngFor=\"let visit of visitsPerSpace; index as index\" value=\"{{index}}\">{{ visit.name}}\n </option>\n </select>\n <select class=\"form-control flex-fill flex-lg-fill-unset floor select-menu\" [(ngModel)]=\"selectedFloor\"\n (ngModelChange)=\"onFloorClick()\">\n <option [ngValue]=\"null\" selected>{{'Floor' | translate}}</option>\n <option *ngFor=\"let floor of floorsPerSpace\" [ngValue]=\"floor\">{{ floor.name}}</option>\n </select>\n <select class=\"form-control flex-fill flex-lg-fill-unset select-menu zone\" [(ngModel)]=\"selectedZone\"\n (ngModelChange)=\"onZoneClick()\">\n <option [ngValue]=\"null\" selected>{{'Zone' | translate}}</option>\n <option *ngFor=\"let zone of zonesPerFloor\" [ngValue]=\"zone\"><span\n *ngIf=\"zone.code_ref\">{{zone.code_ref}}_</span>{{zone.name}}</option>\n </select>\n <select class=\"form-control flex-fill flex-lg-fill-unset select-menu\" [(ngModel)]=\"selectedChildZone\"\n *ngIf=\"childrenZones && childrenZones.length>0\" (ngModelChange)=\"onChildZoneClick()\">\n <option [ngValue]=\"null\" selected disabled>{{'Zone' | translate}}</option>\n <option *ngFor=\"let zone of childrenZones\" [ngValue]=\"zone\">{{ zone.name}}</option>\n </select>\n <button (click)=\"goTo()\"\n class=\"btn btn-outline-primary rounded-pill mx-2 w-md-fit-content h-md-fit-content align-self-md-center btn-3d d-none d-lg-block\"\n [disabled]=\"!selectedLocation || !selectedModel3d || !canGo()\">\n <i class=\"icon-go-to-3d d-inline-block\"></i> {{'3D visit' | translate}}\n </button>\n </div>\n <div class=\"row align-items-center justify-content-center ml-0 p-1\" *ngIf=\"showLocation()\">\n <p class=\"fw-bold lh-base align-middle text-center\">\n {{ showLocation() }}\n </p>\n </div>\n <div class=\"row align-items-center justify-content-center ml-0 p-1 d-block d-lg-none\">\n <button (click)=\"goTo()\"\n class=\"btn btn-outline-primary rounded-pill w-25 mx-2 w-md-fit-content h-md-fit-content align-self-md-center btn-3d\"\n [disabled]=\"!selectedLocation || !selectedModel3d || !canGo()\">\n <i class=\"icon-go-to-3d d-inline-block\"></i> {{'3D visit' | translate}}\n </button>\n </div>\n </div>\n </div>\n</nav>", styles: [":host{position:sticky;top:0;height:123px}.navbar{z-index:200;padding:.4rem 1rem}.navbar.second-nav{z-index:199;background-color:var(--smarterplan-primary)}input,select{margin-right:15px}.select{background:transparent;border:none;color:#fff;margin:6px 20px 0}@media (min-width: 992px){.flex-lg-fill-unset{flex:unset!important}}.form-control{width:100%;min-width:80px;max-width:18%}.location{width:auto;max-width:35%}.floor,.zone{width:auto;max-width:30%}button{color:#fff;padding:5px 10px;font-size:1.1em}.navbar-toggler{margin-right:1em}.select-menu{max-width:inherit}.btn-3d .icon-go-to-3d{width:30px;height:30px;background:url('data:image/svg+xml,<?xml version=\"1.0\" encoding=\"utf-8\"?>%0A<!-- Generator: Adobe Illustrator 25.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->%0A<svg version=\"1.1\" id=\"Calque_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\"%0A%09 viewBox=\"0 0 60 60\" style=\"enable-background:new 0 0 60 60;\" xml:space=\"preserve\">%0A<style type=\"text/css\">%0A%09.st0{fill:%23565656;}%0A<\\/style>%0A<g>%0A%09<path class=\"st0\" d=\"M43.9,32.1V17.7c0-0.4-0.2-0.7-0.5-0.9l-12.8-8c-0.2-0.1-0.4-0.2-0.6-0.2c-0.2,0-0.4,0.1-0.6,0.2l-12.8,8%0A%09%09c-0.3,0.2-0.5,0.5-0.5,0.9v14.4c0,0.4,0.2,0.7,0.5,0.9l12.8,8c0.3,0.2,0.8,0.2,1.1,0l12.8-8C43.7,32.8,43.9,32.5,43.9,32.1z%0A%09%09 M28.9,38.2l-10.7-6.7v-12l10.7,6.7V38.2z M30,24.4l-10.8-6.8L30,10.9l10.8,6.8L30,24.4z M41.8,31.5l-10.7,6.7v-12l10.7-6.7V31.5z\"%0A%09%09/>%0A%09<path class=\"st0\" d=\"M56.1,29.3l-10.3-6.8v2l1.4,1v0l0.8,0.5l0,0l7,4.8c0.3,0.2,0.4,0.5,0.4,0.8c0,0.3-0.2,0.6-0.4,0.8l-6.8,4.7%0A%09%09l0,0l-0.9,0.7v0l-2.9,2v0l-13.8,9.6c-0.3,0.2-0.8,0.2-1.1,0l-14.2-9.8l-2.6-1.8v0L5,32.4c-0.3-0.2-0.4-0.5-0.4-0.8%0A%09%09c0-0.3,0.2-0.6,0.4-0.8l0,0l7.3-5l0,0l0.5-0.3v0l1.4-1v-2L3.9,29.3c-0.8,0.5-1.2,1.4-1.2,2.4c0,0.9,0.5,1.8,1.2,2.4l8.8,6.1v0%0A%09%09l4.5,3.1l11.2,7.7c1,0.7,2.3,0.7,3.3,0l13.7-9.5l1.9-1.3v0l8.8-6.1c0.8-0.5,1.2-1.4,1.2-2.4S56.9,29.8,56.1,29.3z\"/>%0A</g>%0A</svg>%0A');border:none;vertical-align:middle;fill:var(--smarterplan-primary)}.btn-3d:hover:enabled .icon-go-to-3d{background:url('data:image/svg+xml,<?xml version=\"1.0\" encoding=\"utf-8\"?>%0A<!-- Generator: Adobe Illustrator 25.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->%0A<svg version=\"1.1\" id=\"Calque_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\"%0A%09 viewBox=\"0 0 60 60\" style=\"enable-background:new 0 0 60 60;\" xml:space=\"preserve\">%0A<style type=\"text/css\">%0A%09.st0{fill:%23FFFFFF;}%0A<\\/style>%0A<g>%0A%09<path class=\"st0\" d=\"M43.9,32.1V17.7c0-0.4-0.2-0.7-0.5-0.9l-12.8-8c-0.2-0.1-0.4-0.2-0.6-0.2c-0.2,0-0.4,0.1-0.6,0.2l-12.8,8%0A%09%09c-0.3,0.2-0.5,0.5-0.5,0.9v14.4c0,0.4,0.2,0.7,0.5,0.9l12.8,8c0.3,0.2,0.8,0.2,1.1,0l12.8-8C43.7,32.8,43.9,32.5,43.9,32.1z%0A%09%09 M28.9,38.2l-10.7-6.7v-12l10.7,6.7V38.2z M30,24.4l-10.8-6.8L30,10.9l10.8,6.8L30,24.4z M41.8,31.5l-10.7,6.7v-12l10.7-6.7V31.5z\"%0A%09%09/>%0A%09<path class=\"st0\" d=\"M56.1,29.3l-10.3-6.8v2l1.4,1v0l0.8,0.5l0,0l7,4.8c0.3,0.2,0.4,0.5,0.4,0.8c0,0.3-0.2,0.6-0.4,0.8l-6.8,4.7%0A%09%09l0,0l-0.9,0.7v0l-2.9,2v0l-13.8,9.6c-0.3,0.2-0.8,0.2-1.1,0l-14.2-9.8l-2.6-1.8v0L5,32.4c-0.3-0.2-0.4-0.5-0.4-0.8%0A%09%09c0-0.3,0.2-0.6,0.4-0.8l0,0l7.3-5l0,0l0.5-0.3v0l1.4-1v-2L3.9,29.3c-0.8,0.5-1.2,1.4-1.2,2.4c0,0.9,0.5,1.8,1.2,2.4l8.8,6.1v0%0A%09%09l4.5,3.1l11.2,7.7c1,0.7,2.3,0.7,3.3,0l13.7-9.5l1.9-1.3v0l8.8-6.1c0.8-0.5,1.2-1.4,1.2-2.4S56.9,29.8,56.1,29.3z\"/>%0A</g>%0A</svg>%0A')}.btn-3d[disabled]:hover .icon-go-to-3d{fill:var(--smarterplan-primary)}.outer_circle{background-color:#ebebeb;border-radius:50%;width:32px;height:32px;padding:1px}@media (max-width: 767px){.navbar{padding:.2rem .5rem}.form-control,.location,.floor,.zone{max-width:100%;margin-bottom:15px}input,select{margin-right:0;margin-bottom:15px}}\n"], dependencies: [{ kind: "directive", type: i3.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i11.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i11.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1.TranslatePipe, name: "translate" }] });
17276
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: NavigationBarComponent, selector: "lib-navigation-bar", viewQueries: [{ propertyName: "navbarSelectMenu", first: true, predicate: ["navbarSelectMenu"], descendants: true }], ngImport: i0, template: "<nav class=\"navbar navbar-expand-md navbar-dark navbar-inverse second-nav\">\r\n <div class=\"d-flex w-100\">\r\n <div #navbarSelectMenu class=\"w-100\" id=\"nav-item-container-blue\">\r\n <div class=\"d-flex flex-column flex-md-row p-1 mt-2\">\r\n <select class=\"form-control flex-fill flex-lg-fill-unset location select-menu\"\r\n [(ngModel)]=\"selectedLocation\" (ngModelChange)=\"onLocationClick()\">\r\n <option [ngValue]=\"null\" selected>{{ 'Location' | translate }}</option>\r\n <option *ngFor=\"let loc of locations; index as index\" [ngValue]=\"loc\"> {{ loc.name }}</option>\r\n </select>\r\n <select class=\"form-control flex-fill flex-lg-fill-unset select-menu\" (change)=\"onVisitClick($event)\"\r\n *ngIf=\"visitsPerSpace && visitsPerSpace.length > 1\">\r\n <option value=\"default\" selected disabled hidden>{{'Visit' | translate}}</option>\r\n <option *ngFor=\"let visit of visitsPerSpace; index as index\" value=\"{{index}}\">{{ visit.name}}\r\n </option>\r\n </select>\r\n <select class=\"form-control flex-fill flex-lg-fill-unset floor select-menu\" [(ngModel)]=\"selectedFloor\"\r\n (ngModelChange)=\"onFloorClick()\">\r\n <option [ngValue]=\"null\" selected>{{'Floor' | translate}}</option>\r\n <option *ngFor=\"let floor of floorsPerSpace\" [ngValue]=\"floor\">{{ floor.name}}</option>\r\n </select>\r\n <select class=\"form-control flex-fill flex-lg-fill-unset select-menu zone\" [(ngModel)]=\"selectedZone\"\r\n (ngModelChange)=\"onZoneClick()\">\r\n <option [ngValue]=\"null\" selected>{{'Zone' | translate}}</option>\r\n <option *ngFor=\"let zone of zonesPerFloor\" [ngValue]=\"zone\"><span\r\n *ngIf=\"zone.code_ref\">{{zone.code_ref}}_</span>{{zone.name}}</option>\r\n </select>\r\n <select class=\"form-control flex-fill flex-lg-fill-unset select-menu\" [(ngModel)]=\"selectedChildZone\"\r\n *ngIf=\"childrenZones && childrenZones.length>0\" (ngModelChange)=\"onChildZoneClick()\">\r\n <option [ngValue]=\"null\" selected disabled>{{'Zone' | translate}}</option>\r\n <option *ngFor=\"let zone of childrenZones\" [ngValue]=\"zone\">{{ zone.name}}</option>\r\n </select>\r\n <button (click)=\"goTo()\"\r\n class=\"btn btn-outline-primary rounded-pill mx-2 w-md-fit-content h-md-fit-content align-self-md-center btn-3d d-none d-lg-block\"\r\n [disabled]=\"!selectedLocation || !selectedModel3d || !canGo()\">\r\n <i class=\"icon-go-to-3d d-inline-block\"></i> {{'3D visit' | translate}}\r\n </button>\r\n </div>\r\n <div class=\"row align-items-center justify-content-center ml-0 p-1\" *ngIf=\"showLocation()\">\r\n <p class=\"fw-bold lh-base align-middle text-center\">\r\n {{ showLocation() }}\r\n </p>\r\n </div>\r\n <div class=\"row align-items-center justify-content-center ml-0 p-1 d-block d-lg-none\">\r\n <button (click)=\"goTo()\"\r\n class=\"btn btn-outline-primary rounded-pill w-25 mx-2 w-md-fit-content h-md-fit-content align-self-md-center btn-3d\"\r\n [disabled]=\"!selectedLocation || !selectedModel3d || !canGo()\">\r\n <i class=\"icon-go-to-3d d-inline-block\"></i> {{'3D visit' | translate}}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n</nav>", styles: [":host{position:sticky;top:0;height:123px}.navbar{z-index:200;padding:.4rem 1rem}.navbar.second-nav{z-index:199;background-color:var(--smarterplan-primary)}input,select{margin-right:15px}.select{background:transparent;border:none;color:#fff;margin:6px 20px 0}@media (min-width: 992px){.flex-lg-fill-unset{flex:unset!important}}.form-control{width:100%;min-width:80px;max-width:18%}.location{width:auto;max-width:35%}.floor,.zone{width:auto;max-width:30%}button{color:#fff;padding:5px 10px;font-size:1.1em}.navbar-toggler{margin-right:1em}.select-menu{max-width:inherit}.btn-3d .icon-go-to-3d{width:30px;height:30px;background:url('data:image/svg+xml,<?xml version=\"1.0\" encoding=\"utf-8\"?>%0D%0A<!-- Generator: Adobe Illustrator 25.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->%0D%0A<svg version=\"1.1\" id=\"Calque_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\"%0D%0A%09 viewBox=\"0 0 60 60\" style=\"enable-background:new 0 0 60 60;\" xml:space=\"preserve\">%0D%0A<style type=\"text/css\">%0D%0A%09.st0{fill:%23565656;}%0D%0A<\\/style>%0D%0A<g>%0D%0A%09<path class=\"st0\" d=\"M43.9,32.1V17.7c0-0.4-0.2-0.7-0.5-0.9l-12.8-8c-0.2-0.1-0.4-0.2-0.6-0.2c-0.2,0-0.4,0.1-0.6,0.2l-12.8,8%0D%0A%09%09c-0.3,0.2-0.5,0.5-0.5,0.9v14.4c0,0.4,0.2,0.7,0.5,0.9l12.8,8c0.3,0.2,0.8,0.2,1.1,0l12.8-8C43.7,32.8,43.9,32.5,43.9,32.1z%0D%0A%09%09 M28.9,38.2l-10.7-6.7v-12l10.7,6.7V38.2z M30,24.4l-10.8-6.8L30,10.9l10.8,6.8L30,24.4z M41.8,31.5l-10.7,6.7v-12l10.7-6.7V31.5z\"%0D%0A%09%09/>%0D%0A%09<path class=\"st0\" d=\"M56.1,29.3l-10.3-6.8v2l1.4,1v0l0.8,0.5l0,0l7,4.8c0.3,0.2,0.4,0.5,0.4,0.8c0,0.3-0.2,0.6-0.4,0.8l-6.8,4.7%0D%0A%09%09l0,0l-0.9,0.7v0l-2.9,2v0l-13.8,9.6c-0.3,0.2-0.8,0.2-1.1,0l-14.2-9.8l-2.6-1.8v0L5,32.4c-0.3-0.2-0.4-0.5-0.4-0.8%0D%0A%09%09c0-0.3,0.2-0.6,0.4-0.8l0,0l7.3-5l0,0l0.5-0.3v0l1.4-1v-2L3.9,29.3c-0.8,0.5-1.2,1.4-1.2,2.4c0,0.9,0.5,1.8,1.2,2.4l8.8,6.1v0%0D%0A%09%09l4.5,3.1l11.2,7.7c1,0.7,2.3,0.7,3.3,0l13.7-9.5l1.9-1.3v0l8.8-6.1c0.8-0.5,1.2-1.4,1.2-2.4S56.9,29.8,56.1,29.3z\"/>%0D%0A</g>%0D%0A</svg>%0D%0A');border:none;vertical-align:middle;fill:var(--smarterplan-primary)}.btn-3d:hover:enabled .icon-go-to-3d{background:url('data:image/svg+xml,<?xml version=\"1.0\" encoding=\"utf-8\"?>%0D%0A<!-- Generator: Adobe Illustrator 25.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->%0D%0A<svg version=\"1.1\" id=\"Calque_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\"%0D%0A%09 viewBox=\"0 0 60 60\" style=\"enable-background:new 0 0 60 60;\" xml:space=\"preserve\">%0D%0A<style type=\"text/css\">%0D%0A%09.st0{fill:%23FFFFFF;}%0D%0A<\\/style>%0D%0A<g>%0D%0A%09<path class=\"st0\" d=\"M43.9,32.1V17.7c0-0.4-0.2-0.7-0.5-0.9l-12.8-8c-0.2-0.1-0.4-0.2-0.6-0.2c-0.2,0-0.4,0.1-0.6,0.2l-12.8,8%0D%0A%09%09c-0.3,0.2-0.5,0.5-0.5,0.9v14.4c0,0.4,0.2,0.7,0.5,0.9l12.8,8c0.3,0.2,0.8,0.2,1.1,0l12.8-8C43.7,32.8,43.9,32.5,43.9,32.1z%0D%0A%09%09 M28.9,38.2l-10.7-6.7v-12l10.7,6.7V38.2z M30,24.4l-10.8-6.8L30,10.9l10.8,6.8L30,24.4z M41.8,31.5l-10.7,6.7v-12l10.7-6.7V31.5z\"%0D%0A%09%09/>%0D%0A%09<path class=\"st0\" d=\"M56.1,29.3l-10.3-6.8v2l1.4,1v0l0.8,0.5l0,0l7,4.8c0.3,0.2,0.4,0.5,0.4,0.8c0,0.3-0.2,0.6-0.4,0.8l-6.8,4.7%0D%0A%09%09l0,0l-0.9,0.7v0l-2.9,2v0l-13.8,9.6c-0.3,0.2-0.8,0.2-1.1,0l-14.2-9.8l-2.6-1.8v0L5,32.4c-0.3-0.2-0.4-0.5-0.4-0.8%0D%0A%09%09c0-0.3,0.2-0.6,0.4-0.8l0,0l7.3-5l0,0l0.5-0.3v0l1.4-1v-2L3.9,29.3c-0.8,0.5-1.2,1.4-1.2,2.4c0,0.9,0.5,1.8,1.2,2.4l8.8,6.1v0%0D%0A%09%09l4.5,3.1l11.2,7.7c1,0.7,2.3,0.7,3.3,0l13.7-9.5l1.9-1.3v0l8.8-6.1c0.8-0.5,1.2-1.4,1.2-2.4S56.9,29.8,56.1,29.3z\"/>%0D%0A</g>%0D%0A</svg>%0D%0A')}.btn-3d[disabled]:hover .icon-go-to-3d{fill:var(--smarterplan-primary)}.outer_circle{background-color:#ebebeb;border-radius:50%;width:32px;height:32px;padding:1px}@media (max-width: 767px){.navbar{padding:.2rem .5rem}.form-control,.location,.floor,.zone{max-width:100%;margin-bottom:15px}input,select{margin-right:0;margin-bottom:15px}}\n"], dependencies: [{ kind: "directive", type: i3.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i11.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i11.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1.TranslatePipe, name: "translate" }] });
16348
17277
  }
16349
17278
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NavigationBarComponent, decorators: [{
16350
17279
  type: Component,
16351
- args: [{ selector: 'lib-navigation-bar', template: "<nav class=\"navbar navbar-expand-md navbar-dark navbar-inverse second-nav\">\n <div class=\"d-flex w-100\">\n <div #navbarSelectMenu class=\"w-100\" id=\"nav-item-container-blue\">\n <div class=\"d-flex flex-column flex-md-row p-1 mt-2\">\n <select class=\"form-control flex-fill flex-lg-fill-unset location select-menu\"\n [(ngModel)]=\"selectedLocation\" (ngModelChange)=\"onLocationClick()\">\n <option [ngValue]=\"null\" selected>{{ 'Location' | translate }}</option>\n <option *ngFor=\"let loc of locations; index as index\" [ngValue]=\"loc\"> {{ loc.name }}</option>\n </select>\n <select class=\"form-control flex-fill flex-lg-fill-unset select-menu\" (change)=\"onVisitClick($event)\"\n *ngIf=\"visitsPerSpace && visitsPerSpace.length > 1\">\n <option value=\"default\" selected disabled hidden>{{'Visit' | translate}}</option>\n <option *ngFor=\"let visit of visitsPerSpace; index as index\" value=\"{{index}}\">{{ visit.name}}\n </option>\n </select>\n <select class=\"form-control flex-fill flex-lg-fill-unset floor select-menu\" [(ngModel)]=\"selectedFloor\"\n (ngModelChange)=\"onFloorClick()\">\n <option [ngValue]=\"null\" selected>{{'Floor' | translate}}</option>\n <option *ngFor=\"let floor of floorsPerSpace\" [ngValue]=\"floor\">{{ floor.name}}</option>\n </select>\n <select class=\"form-control flex-fill flex-lg-fill-unset select-menu zone\" [(ngModel)]=\"selectedZone\"\n (ngModelChange)=\"onZoneClick()\">\n <option [ngValue]=\"null\" selected>{{'Zone' | translate}}</option>\n <option *ngFor=\"let zone of zonesPerFloor\" [ngValue]=\"zone\"><span\n *ngIf=\"zone.code_ref\">{{zone.code_ref}}_</span>{{zone.name}}</option>\n </select>\n <select class=\"form-control flex-fill flex-lg-fill-unset select-menu\" [(ngModel)]=\"selectedChildZone\"\n *ngIf=\"childrenZones && childrenZones.length>0\" (ngModelChange)=\"onChildZoneClick()\">\n <option [ngValue]=\"null\" selected disabled>{{'Zone' | translate}}</option>\n <option *ngFor=\"let zone of childrenZones\" [ngValue]=\"zone\">{{ zone.name}}</option>\n </select>\n <button (click)=\"goTo()\"\n class=\"btn btn-outline-primary rounded-pill mx-2 w-md-fit-content h-md-fit-content align-self-md-center btn-3d d-none d-lg-block\"\n [disabled]=\"!selectedLocation || !selectedModel3d || !canGo()\">\n <i class=\"icon-go-to-3d d-inline-block\"></i> {{'3D visit' | translate}}\n </button>\n </div>\n <div class=\"row align-items-center justify-content-center ml-0 p-1\" *ngIf=\"showLocation()\">\n <p class=\"fw-bold lh-base align-middle text-center\">\n {{ showLocation() }}\n </p>\n </div>\n <div class=\"row align-items-center justify-content-center ml-0 p-1 d-block d-lg-none\">\n <button (click)=\"goTo()\"\n class=\"btn btn-outline-primary rounded-pill w-25 mx-2 w-md-fit-content h-md-fit-content align-self-md-center btn-3d\"\n [disabled]=\"!selectedLocation || !selectedModel3d || !canGo()\">\n <i class=\"icon-go-to-3d d-inline-block\"></i> {{'3D visit' | translate}}\n </button>\n </div>\n </div>\n </div>\n</nav>", styles: [":host{position:sticky;top:0;height:123px}.navbar{z-index:200;padding:.4rem 1rem}.navbar.second-nav{z-index:199;background-color:var(--smarterplan-primary)}input,select{margin-right:15px}.select{background:transparent;border:none;color:#fff;margin:6px 20px 0}@media (min-width: 992px){.flex-lg-fill-unset{flex:unset!important}}.form-control{width:100%;min-width:80px;max-width:18%}.location{width:auto;max-width:35%}.floor,.zone{width:auto;max-width:30%}button{color:#fff;padding:5px 10px;font-size:1.1em}.navbar-toggler{margin-right:1em}.select-menu{max-width:inherit}.btn-3d .icon-go-to-3d{width:30px;height:30px;background:url('data:image/svg+xml,<?xml version=\"1.0\" encoding=\"utf-8\"?>%0A<!-- Generator: Adobe Illustrator 25.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->%0A<svg version=\"1.1\" id=\"Calque_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\"%0A%09 viewBox=\"0 0 60 60\" style=\"enable-background:new 0 0 60 60;\" xml:space=\"preserve\">%0A<style type=\"text/css\">%0A%09.st0{fill:%23565656;}%0A<\\/style>%0A<g>%0A%09<path class=\"st0\" d=\"M43.9,32.1V17.7c0-0.4-0.2-0.7-0.5-0.9l-12.8-8c-0.2-0.1-0.4-0.2-0.6-0.2c-0.2,0-0.4,0.1-0.6,0.2l-12.8,8%0A%09%09c-0.3,0.2-0.5,0.5-0.5,0.9v14.4c0,0.4,0.2,0.7,0.5,0.9l12.8,8c0.3,0.2,0.8,0.2,1.1,0l12.8-8C43.7,32.8,43.9,32.5,43.9,32.1z%0A%09%09 M28.9,38.2l-10.7-6.7v-12l10.7,6.7V38.2z M30,24.4l-10.8-6.8L30,10.9l10.8,6.8L30,24.4z M41.8,31.5l-10.7,6.7v-12l10.7-6.7V31.5z\"%0A%09%09/>%0A%09<path class=\"st0\" d=\"M56.1,29.3l-10.3-6.8v2l1.4,1v0l0.8,0.5l0,0l7,4.8c0.3,0.2,0.4,0.5,0.4,0.8c0,0.3-0.2,0.6-0.4,0.8l-6.8,4.7%0A%09%09l0,0l-0.9,0.7v0l-2.9,2v0l-13.8,9.6c-0.3,0.2-0.8,0.2-1.1,0l-14.2-9.8l-2.6-1.8v0L5,32.4c-0.3-0.2-0.4-0.5-0.4-0.8%0A%09%09c0-0.3,0.2-0.6,0.4-0.8l0,0l7.3-5l0,0l0.5-0.3v0l1.4-1v-2L3.9,29.3c-0.8,0.5-1.2,1.4-1.2,2.4c0,0.9,0.5,1.8,1.2,2.4l8.8,6.1v0%0A%09%09l4.5,3.1l11.2,7.7c1,0.7,2.3,0.7,3.3,0l13.7-9.5l1.9-1.3v0l8.8-6.1c0.8-0.5,1.2-1.4,1.2-2.4S56.9,29.8,56.1,29.3z\"/>%0A</g>%0A</svg>%0A');border:none;vertical-align:middle;fill:var(--smarterplan-primary)}.btn-3d:hover:enabled .icon-go-to-3d{background:url('data:image/svg+xml,<?xml version=\"1.0\" encoding=\"utf-8\"?>%0A<!-- Generator: Adobe Illustrator 25.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->%0A<svg version=\"1.1\" id=\"Calque_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\"%0A%09 viewBox=\"0 0 60 60\" style=\"enable-background:new 0 0 60 60;\" xml:space=\"preserve\">%0A<style type=\"text/css\">%0A%09.st0{fill:%23FFFFFF;}%0A<\\/style>%0A<g>%0A%09<path class=\"st0\" d=\"M43.9,32.1V17.7c0-0.4-0.2-0.7-0.5-0.9l-12.8-8c-0.2-0.1-0.4-0.2-0.6-0.2c-0.2,0-0.4,0.1-0.6,0.2l-12.8,8%0A%09%09c-0.3,0.2-0.5,0.5-0.5,0.9v14.4c0,0.4,0.2,0.7,0.5,0.9l12.8,8c0.3,0.2,0.8,0.2,1.1,0l12.8-8C43.7,32.8,43.9,32.5,43.9,32.1z%0A%09%09 M28.9,38.2l-10.7-6.7v-12l10.7,6.7V38.2z M30,24.4l-10.8-6.8L30,10.9l10.8,6.8L30,24.4z M41.8,31.5l-10.7,6.7v-12l10.7-6.7V31.5z\"%0A%09%09/>%0A%09<path class=\"st0\" d=\"M56.1,29.3l-10.3-6.8v2l1.4,1v0l0.8,0.5l0,0l7,4.8c0.3,0.2,0.4,0.5,0.4,0.8c0,0.3-0.2,0.6-0.4,0.8l-6.8,4.7%0A%09%09l0,0l-0.9,0.7v0l-2.9,2v0l-13.8,9.6c-0.3,0.2-0.8,0.2-1.1,0l-14.2-9.8l-2.6-1.8v0L5,32.4c-0.3-0.2-0.4-0.5-0.4-0.8%0A%09%09c0-0.3,0.2-0.6,0.4-0.8l0,0l7.3-5l0,0l0.5-0.3v0l1.4-1v-2L3.9,29.3c-0.8,0.5-1.2,1.4-1.2,2.4c0,0.9,0.5,1.8,1.2,2.4l8.8,6.1v0%0A%09%09l4.5,3.1l11.2,7.7c1,0.7,2.3,0.7,3.3,0l13.7-9.5l1.9-1.3v0l8.8-6.1c0.8-0.5,1.2-1.4,1.2-2.4S56.9,29.8,56.1,29.3z\"/>%0A</g>%0A</svg>%0A')}.btn-3d[disabled]:hover .icon-go-to-3d{fill:var(--smarterplan-primary)}.outer_circle{background-color:#ebebeb;border-radius:50%;width:32px;height:32px;padding:1px}@media (max-width: 767px){.navbar{padding:.2rem .5rem}.form-control,.location,.floor,.zone{max-width:100%;margin-bottom:15px}input,select{margin-right:0;margin-bottom:15px}}\n"] }]
17280
+ args: [{ selector: 'lib-navigation-bar', template: "<nav class=\"navbar navbar-expand-md navbar-dark navbar-inverse second-nav\">\r\n <div class=\"d-flex w-100\">\r\n <div #navbarSelectMenu class=\"w-100\" id=\"nav-item-container-blue\">\r\n <div class=\"d-flex flex-column flex-md-row p-1 mt-2\">\r\n <select class=\"form-control flex-fill flex-lg-fill-unset location select-menu\"\r\n [(ngModel)]=\"selectedLocation\" (ngModelChange)=\"onLocationClick()\">\r\n <option [ngValue]=\"null\" selected>{{ 'Location' | translate }}</option>\r\n <option *ngFor=\"let loc of locations; index as index\" [ngValue]=\"loc\"> {{ loc.name }}</option>\r\n </select>\r\n <select class=\"form-control flex-fill flex-lg-fill-unset select-menu\" (change)=\"onVisitClick($event)\"\r\n *ngIf=\"visitsPerSpace && visitsPerSpace.length > 1\">\r\n <option value=\"default\" selected disabled hidden>{{'Visit' | translate}}</option>\r\n <option *ngFor=\"let visit of visitsPerSpace; index as index\" value=\"{{index}}\">{{ visit.name}}\r\n </option>\r\n </select>\r\n <select class=\"form-control flex-fill flex-lg-fill-unset floor select-menu\" [(ngModel)]=\"selectedFloor\"\r\n (ngModelChange)=\"onFloorClick()\">\r\n <option [ngValue]=\"null\" selected>{{'Floor' | translate}}</option>\r\n <option *ngFor=\"let floor of floorsPerSpace\" [ngValue]=\"floor\">{{ floor.name}}</option>\r\n </select>\r\n <select class=\"form-control flex-fill flex-lg-fill-unset select-menu zone\" [(ngModel)]=\"selectedZone\"\r\n (ngModelChange)=\"onZoneClick()\">\r\n <option [ngValue]=\"null\" selected>{{'Zone' | translate}}</option>\r\n <option *ngFor=\"let zone of zonesPerFloor\" [ngValue]=\"zone\"><span\r\n *ngIf=\"zone.code_ref\">{{zone.code_ref}}_</span>{{zone.name}}</option>\r\n </select>\r\n <select class=\"form-control flex-fill flex-lg-fill-unset select-menu\" [(ngModel)]=\"selectedChildZone\"\r\n *ngIf=\"childrenZones && childrenZones.length>0\" (ngModelChange)=\"onChildZoneClick()\">\r\n <option [ngValue]=\"null\" selected disabled>{{'Zone' | translate}}</option>\r\n <option *ngFor=\"let zone of childrenZones\" [ngValue]=\"zone\">{{ zone.name}}</option>\r\n </select>\r\n <button (click)=\"goTo()\"\r\n class=\"btn btn-outline-primary rounded-pill mx-2 w-md-fit-content h-md-fit-content align-self-md-center btn-3d d-none d-lg-block\"\r\n [disabled]=\"!selectedLocation || !selectedModel3d || !canGo()\">\r\n <i class=\"icon-go-to-3d d-inline-block\"></i> {{'3D visit' | translate}}\r\n </button>\r\n </div>\r\n <div class=\"row align-items-center justify-content-center ml-0 p-1\" *ngIf=\"showLocation()\">\r\n <p class=\"fw-bold lh-base align-middle text-center\">\r\n {{ showLocation() }}\r\n </p>\r\n </div>\r\n <div class=\"row align-items-center justify-content-center ml-0 p-1 d-block d-lg-none\">\r\n <button (click)=\"goTo()\"\r\n class=\"btn btn-outline-primary rounded-pill w-25 mx-2 w-md-fit-content h-md-fit-content align-self-md-center btn-3d\"\r\n [disabled]=\"!selectedLocation || !selectedModel3d || !canGo()\">\r\n <i class=\"icon-go-to-3d d-inline-block\"></i> {{'3D visit' | translate}}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n</nav>", styles: [":host{position:sticky;top:0;height:123px}.navbar{z-index:200;padding:.4rem 1rem}.navbar.second-nav{z-index:199;background-color:var(--smarterplan-primary)}input,select{margin-right:15px}.select{background:transparent;border:none;color:#fff;margin:6px 20px 0}@media (min-width: 992px){.flex-lg-fill-unset{flex:unset!important}}.form-control{width:100%;min-width:80px;max-width:18%}.location{width:auto;max-width:35%}.floor,.zone{width:auto;max-width:30%}button{color:#fff;padding:5px 10px;font-size:1.1em}.navbar-toggler{margin-right:1em}.select-menu{max-width:inherit}.btn-3d .icon-go-to-3d{width:30px;height:30px;background:url('data:image/svg+xml,<?xml version=\"1.0\" encoding=\"utf-8\"?>%0D%0A<!-- Generator: Adobe Illustrator 25.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->%0D%0A<svg version=\"1.1\" id=\"Calque_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\"%0D%0A%09 viewBox=\"0 0 60 60\" style=\"enable-background:new 0 0 60 60;\" xml:space=\"preserve\">%0D%0A<style type=\"text/css\">%0D%0A%09.st0{fill:%23565656;}%0D%0A<\\/style>%0D%0A<g>%0D%0A%09<path class=\"st0\" d=\"M43.9,32.1V17.7c0-0.4-0.2-0.7-0.5-0.9l-12.8-8c-0.2-0.1-0.4-0.2-0.6-0.2c-0.2,0-0.4,0.1-0.6,0.2l-12.8,8%0D%0A%09%09c-0.3,0.2-0.5,0.5-0.5,0.9v14.4c0,0.4,0.2,0.7,0.5,0.9l12.8,8c0.3,0.2,0.8,0.2,1.1,0l12.8-8C43.7,32.8,43.9,32.5,43.9,32.1z%0D%0A%09%09 M28.9,38.2l-10.7-6.7v-12l10.7,6.7V38.2z M30,24.4l-10.8-6.8L30,10.9l10.8,6.8L30,24.4z M41.8,31.5l-10.7,6.7v-12l10.7-6.7V31.5z\"%0D%0A%09%09/>%0D%0A%09<path class=\"st0\" d=\"M56.1,29.3l-10.3-6.8v2l1.4,1v0l0.8,0.5l0,0l7,4.8c0.3,0.2,0.4,0.5,0.4,0.8c0,0.3-0.2,0.6-0.4,0.8l-6.8,4.7%0D%0A%09%09l0,0l-0.9,0.7v0l-2.9,2v0l-13.8,9.6c-0.3,0.2-0.8,0.2-1.1,0l-14.2-9.8l-2.6-1.8v0L5,32.4c-0.3-0.2-0.4-0.5-0.4-0.8%0D%0A%09%09c0-0.3,0.2-0.6,0.4-0.8l0,0l7.3-5l0,0l0.5-0.3v0l1.4-1v-2L3.9,29.3c-0.8,0.5-1.2,1.4-1.2,2.4c0,0.9,0.5,1.8,1.2,2.4l8.8,6.1v0%0D%0A%09%09l4.5,3.1l11.2,7.7c1,0.7,2.3,0.7,3.3,0l13.7-9.5l1.9-1.3v0l8.8-6.1c0.8-0.5,1.2-1.4,1.2-2.4S56.9,29.8,56.1,29.3z\"/>%0D%0A</g>%0D%0A</svg>%0D%0A');border:none;vertical-align:middle;fill:var(--smarterplan-primary)}.btn-3d:hover:enabled .icon-go-to-3d{background:url('data:image/svg+xml,<?xml version=\"1.0\" encoding=\"utf-8\"?>%0D%0A<!-- Generator: Adobe Illustrator 25.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->%0D%0A<svg version=\"1.1\" id=\"Calque_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\"%0D%0A%09 viewBox=\"0 0 60 60\" style=\"enable-background:new 0 0 60 60;\" xml:space=\"preserve\">%0D%0A<style type=\"text/css\">%0D%0A%09.st0{fill:%23FFFFFF;}%0D%0A<\\/style>%0D%0A<g>%0D%0A%09<path class=\"st0\" d=\"M43.9,32.1V17.7c0-0.4-0.2-0.7-0.5-0.9l-12.8-8c-0.2-0.1-0.4-0.2-0.6-0.2c-0.2,0-0.4,0.1-0.6,0.2l-12.8,8%0D%0A%09%09c-0.3,0.2-0.5,0.5-0.5,0.9v14.4c0,0.4,0.2,0.7,0.5,0.9l12.8,8c0.3,0.2,0.8,0.2,1.1,0l12.8-8C43.7,32.8,43.9,32.5,43.9,32.1z%0D%0A%09%09 M28.9,38.2l-10.7-6.7v-12l10.7,6.7V38.2z M30,24.4l-10.8-6.8L30,10.9l10.8,6.8L30,24.4z M41.8,31.5l-10.7,6.7v-12l10.7-6.7V31.5z\"%0D%0A%09%09/>%0D%0A%09<path class=\"st0\" d=\"M56.1,29.3l-10.3-6.8v2l1.4,1v0l0.8,0.5l0,0l7,4.8c0.3,0.2,0.4,0.5,0.4,0.8c0,0.3-0.2,0.6-0.4,0.8l-6.8,4.7%0D%0A%09%09l0,0l-0.9,0.7v0l-2.9,2v0l-13.8,9.6c-0.3,0.2-0.8,0.2-1.1,0l-14.2-9.8l-2.6-1.8v0L5,32.4c-0.3-0.2-0.4-0.5-0.4-0.8%0D%0A%09%09c0-0.3,0.2-0.6,0.4-0.8l0,0l7.3-5l0,0l0.5-0.3v0l1.4-1v-2L3.9,29.3c-0.8,0.5-1.2,1.4-1.2,2.4c0,0.9,0.5,1.8,1.2,2.4l8.8,6.1v0%0D%0A%09%09l4.5,3.1l11.2,7.7c1,0.7,2.3,0.7,3.3,0l13.7-9.5l1.9-1.3v0l8.8-6.1c0.8-0.5,1.2-1.4,1.2-2.4S56.9,29.8,56.1,29.3z\"/>%0D%0A</g>%0D%0A</svg>%0D%0A')}.btn-3d[disabled]:hover .icon-go-to-3d{fill:var(--smarterplan-primary)}.outer_circle{background-color:#ebebeb;border-radius:50%;width:32px;height:32px;padding:1px}@media (max-width: 767px){.navbar{padding:.2rem .5rem}.form-control,.location,.floor,.zone{max-width:100%;margin-bottom:15px}input,select{margin-right:0;margin-bottom:15px}}\n"] }]
16352
17281
  }], ctorParameters: () => [{ type: undefined, decorators: [{
16353
17282
  type: Inject,
16354
17283
  args: ['currentAwsKinesisAnalyticsService']
@@ -16617,11 +17546,11 @@ class MenuBarComponent {
16617
17546
  }
16618
17547
  }
16619
17548
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MenuBarComponent, deps: [{ token: i1$1.Router }, { token: LocaleService }, { token: BaseUserService }, { token: NavigatorService }, { token: SupportService }, { token: BaseVisibilityService }, { token: i1$2.NgbCalendar }, { token: i1$2.NgbDateParserFormatter }, { token: FilterService }, { token: AmplifyCacheService }], target: i0.ɵɵFactoryTarget.Component });
16620
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: MenuBarComponent, selector: "lib-menu-bar", inputs: { cacheName: "cacheName" }, ngImport: i0, template: "<div id=\"menu-bar\">\n <div *ngIf=\"!isVisitor\">\n <nav class=\"navbar navbar-expand-md navbar-light p-0\">\n <div class=\"container-fluid\" *ngIf=\"isVisible\">\n <div class=\"navbar-brand order-md-0\" [routerLink]=\"['/dashboard']\">\n <img alt=\"SmarterPlan\" src=\"../assets/images/logos/logo-immo.png\">\n </div>\n <div class=\"ms-auto nav-item dropdown profile_pic-container order-md-2\">\n <div class=\"profile_pic dropdown-toggle\" data-testid=\"dropdown-menu-button\" data-bs-toggle=\"dropdown\">\n <lib-avatar></lib-avatar>\n </div>\n <div class=\"dropdown-menu dropdown-menu-right\">\n <p *ngIf=\"user && mission\" class=\"p-2\">\n <span class=\"username\">{{ user.displayName }}</span><br />\n <span class=\"user-role\">{{ mission.role }}</span><br />\n <span class=\"org\">{{ mission.organisation ? mission.organisation.name : \"No organisation\" |\n translate}}</span>\n </p>\n <div class=\"dropdown-divider\"></div>\n <!-- <a class=\"dropdown-item\" routerLink=\"/debug\" *ngIf=\"isManager\">Debug</a> -->\n <a class=\"dropdown-item\" (click)=\"localeService.modal()\">{{'Language' | translate}}</a>\n <a class=\"dropdown-item\" routerLink=\"/dashboard\">{{'Data management' | translate}}</a>\n <a class=\"dropdown-item\" data-testid=\"user-management-btn\" routerLink=\"/admin\" *ngIf=\"isManager\">\n {{'Users management' | translate}}</a>\n <a class=\"dropdown-item\" routerLink=\"/profile\">\n {{'Profile' | translate}}</a>\n <div class=\"dropdown-divider\"></div>\n <button class=\"dropdown-item\" (click)=\"openSupportForm()\">{{\"Help\" | translate}}</button>\n <button data-testid=\"logout-auth-btn\" class=\"dropdown-item\" (click)=\"signOut()\">{{\"Logout\" |\n translate}}</button>\n </div>\n </div>\n <button class=\"navbar-toggler order-md-1\" (click)=\"navbarCollapse.toggle()\" type=\"button\"\n [attr.aria-expanded]=\"!isCollapsed\" aria-controls=\"navbar-collapse\">\n <span class=\"navbar-toggler-icon\"></span>\n </button>\n <div class=\"navbar-collapse order-md-1\" #navbarCollapse=\"ngbCollapse\" [(ngbCollapse)]=\"isCollapsed\"\n [animation]=\"false\">\n <form\n class=\"form-inline m-0 mt-2 form-container d-flex justify-content-start align-items-start align-items-md-center flex-column flex-md-row flex-nowrap filters-menu-block\">\n <div class=\"input-group search-button-container d-flex flex-row flex-nowrap p-0 m-0\">\n <input class=\"form-control search-button m-1\" placeholder=\"keyword\" id=\"mainSearchBar\" type=\"text\">\n <button class=\"btn btn-outline-primary rounded-pill m-1 w-fit-content h-fit-content align-self-md-center\"\n (click)=\"onSubmit()\">{{'Search' |\n translate}}</button>\n </div>\n <div class=\"input-group search-button-container d-flex flex-row flex-nowrap p-0 m-0\">\n <div class=\"input-group date-selector m-1\">\n <input #dpFromDate class=\"form-control\" placeholder=\"dd/MM/yyyy\" name=\"dpFromDate\"\n [value]=\"fromDate | formatDateNumberToDigits\"\n (input)=\"fromDate = validateInput(fromDate, dpFromDate.value)\">\n <div class=\"input-group-append\">\n <button class=\"btn btn-outline-secondary calendar\" (click)=\"datepicker.toggle()\"\n type=\"button\"></button>\n </div>\n </div>\n <input name=\"datepicker\" class=\"form-control visually-hidden\" ngbDatepicker #datepicker=\"ngbDatepicker\"\n [autoClose]=\"'outside'\" (dateSelect)=\"onDateSelection($event)\" [displayMonths]=\"2\" [dayTemplate]=\"t\"\n outsideDays=\"hidden\" [startDate]=\"fromDate || calendar.getToday()\" [container]=\"'body'\" tabindex=\"-1\">\n <ng-template #t let-date let-focused=\"focused\">\n <span class=\"custom-day\" [class.focused]=\"focused\" [class.range]=\"isRange(date)\"\n [class.faded]=\"isHovered(date) || isInside(date)\" (mouseenter)=\"hoveredDate = date\"\n (mouseleave)=\"hoveredDate = null\">\n {{ date.day }}\n </span>\n </ng-template>\n <span class=\"ms-1 p-2\">{{'to' | translate}}</span>\n <div class=\"input-group date-selector m-1\">\n <input #dpToDate class=\"form-control\" placeholder=\"dd/MM/yyyy\" name=\"dpToDate\"\n [value]=\"toDate | formatDateNumberToDigits\" (input)=\"toDate = validateInput(toDate, dpToDate.value)\">\n <div class=\"input-group-append\">\n <button class=\"btn btn-outline-secondary calendar\" (click)=\"datepicker.toggle()\"\n type=\"button\"></button>\n </div>\n </div>\n </div>\n <div class=\"input-group search-button-container d-flex flex-row flex-nowrap p-0 m-1 justify-content-evenly\">\n <button\n class=\"btn btn-primary rounded-pill ms-1 w-25 w-md-fit-content h-md-fit-content align-self-md-center\"\n type=\"button\" (click)=\"onApply()\" *ngIf=\"!isDateFilterApplied || dateChanged\">\n {{'Apply' | translate}}\n </button>\n <span\n class=\"appliedText ms-1 py-2 align-self-md-center pb-md-0 mx-2 w-md-fit-content h-md-fit-content align-self-md-center\"\n *ngIf=\"isDateFilterApplied && !dateChanged\">\n <span class=\"iconify\" data-icon=\"ion:checkmark\" data-width=\"15\" data-height=\"15\"></span>\n {{\"Applied\" | translate}}\n </span>\n <button\n class=\"btn btn-outline-primary rounded-pill w-25 mx-2 w-md-fit-content h-md-fit-content align-self-md-center\"\n type=\"button\" (click)=\"onClearDates()\">{{'Clear'\n |\n translate}}</button>\n <div\n class=\"change-position-button align-self-end pb-2 align-self-md-center mx-2 pb-md-0 flex-nowrap w-25 w-md-fit-content h-md-fit-content\">\n <input type=\"checkbox\" class=\"btn-check\" id=\"btn-check-outlined\" autocomplete=\"off\">\n <label class=\"link-primary cursor-pointer text-decoration-underline\" (click)=\"onChangePositionClick()\"\n for=\"btn-check-outlined\">{{\n (isChangePositionVisible ? \"Close\" : \"Move to\") | translate }}</label>\n </div>\n </div>\n </form>\n </div>\n </div>\n </nav>\n <lib-navigation-bar *ngIf=\"isVisible && isChangePositionVisible\"></lib-navigation-bar>\n </div>\n <div *ngIf=\"isVisitor && isVisible\">\n <div class=\"visitor-div\">\n <div class=\"logo-container\" data-testid=\"dropdown-menu-button\">\n <img class=\"img-logo\" alt=\"SmarterPlanLogo\" src=\"../assets/images/logos/logo-reserve.png\">\n </div>\n <div class=\"avatar-container-menu\">\n <div class=\"profile_pic dropdown-toggle cursor-pointer\" data-bs-toggle=\"dropdown\">\n <lib-avatar></lib-avatar>\n </div>\n <div class=\"dropdown-menu dropdown-menu-right\">\n <button class=\"dropdown-item\" (click)=\"localeService.modal()\">{{'Language' | translate}}</button>\n <button class=\"dropdown-item\" (click)=\"openSupportForm()\">{{\"Help\" | translate}}</button>\n <button data-testid=\"logout-auth-btn\" class=\"dropdown-item\" (click)=\"signOut()\">{{\"Logout\" |\n translate}}</button>\n </div>\n </div>\n </div>\n </div>\n</div>", styles: ["#menu-bar{position:sticky;top:0;z-index:200}.navbar{z-index:200}.navbar-brand{padding-top:5px;padding-bottom:5px;cursor:pointer}.navbar-brand img{height:70px}.profile_pic{border-radius:50%;height:45px;width:45px;margin:8px;border:.2px solid rgb(150,150,150)}.navbar-nav .profile-pic-container .dropdown-menu{position:absolute}.navbar.second-nav{z-index:199;background-color:var(--smarterplan-primary)}.profile_pic.dropdown-toggle:after{display:none}.navbar-light{border-bottom:1px solid var(--smarterplan-primary);background-color:#fff}.navbar form{width:100%;margin:0;padding:0}input,select{margin-right:15px}.select{background:transparent;border:none;color:#fff;margin:6px 20px 0;width:auto}button{padding:5px 10px}button:disabled,button[disabled]{color:#c04f4f}.navbar-toggler{margin-right:1em}.icon{height:40px;width:40px;color:var(--smarterplan-primary)}.search-button-container{width:auto;align-items:stretch;justify-items:start}.search-button-container .search-button{width:auto;border-radius:1rem!important}.visitor-div{background-color:var(--smarterplan-primary);height:60px;display:flex;justify-content:space-between}.profile_pic-container{margin-right:1em}.logo-container,.img-logo{height:100%}.change-position-button{margin-left:2rem}.change-position-button label,.change-position-button .btn-check:checked,.change-position-button .btn-check:focus,.change-position-button .btn-outline-primary,.change-position-button .btn{box-shadow:none!important;outline:0}.change-position-button label{border-radius:16px}.change-position-button .btn-check:checked+.btn{background-color:var(--smarterplan-primary);color:#fff!important}.filters-menu-block{width:auto}@media (min-width: 1200px){.navbar-brand img{height:100px}.profile_pic{height:60px;width:60px;margin:15px}}@media (min-width: 992px) and (max-width: 1199.98px){.navbar-brand img{height:80px}.profile_pic{height:50px;width:50px;margin:12px}}@media (min-width: 768px) and (max-width: 991.98px){.navbar-brand img{height:60px}.profile_pic{height:40px;width:40px;margin:10px}}@media (max-width: 767.98px){.navbar-brand img{height:50px}.profile_pic{height:30px;width:30px;margin:8px}.search-button-container,.filters-menu-block{width:100%}}.form-container{margin:.5rem 0}.navbar-collapse.show{display:flex;padding-bottom:.5rem}.hidden{width:0;margin:0;border:none;padding:0}.custom-day{text-align:center;padding:.185rem .25rem;display:inline-block;height:2rem;width:2rem}.custom-day.focused{background-color:#e6e6e6}.custom-day.range,.custom-day:hover{background-color:var(--smarterplan-primary-lighter);color:#fff}.custom-day.faded{background-color:var(--smarterplan-primary-transparent)}.calendar{height:100%}.appliedText,.appliedText span{color:var(--smarterplan-primary)}@media (max-width: 767px){.form-inline{flex-direction:column}}.hidden{opacity:0}.date-selector{flex-wrap:nowrap}.date-selector input{min-width:80px;margin:0}@media (min-width: 768px){.w-md-fit-content{width:fit-content!important}.h-md-content{height:fit-content!important}}\n"], dependencies: [{ kind: "directive", type: i3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i11.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i1$2.NgbCollapse, selector: "[ngbCollapse]", inputs: ["animation", "ngbCollapse", "horizontal"], outputs: ["ngbCollapseChange", "shown", "hidden"], exportAs: ["ngbCollapse"] }, { kind: "directive", type: i1$2.NgbInputDatepicker, selector: "input[ngbDatepicker]", inputs: ["autoClose", "contentTemplate", "datepickerClass", "dayTemplate", "dayTemplateData", "displayMonths", "firstDayOfWeek", "footerTemplate", "markDisabled", "minDate", "maxDate", "navigation", "outsideDays", "placement", "popperOptions", "restoreFocus", "showWeekNumbers", "startDate", "container", "positionTarget", "weekdays", "disabled"], outputs: ["dateSelect", "navigate", "closed"], exportAs: ["ngbDatepicker"] }, { kind: "component", type: AvatarComponent, selector: "lib-avatar", inputs: ["size", "userID"] }, { kind: "component", type: NavigationBarComponent, selector: "lib-navigation-bar" }, { kind: "pipe", type: i1.TranslatePipe, name: "translate" }, { kind: "pipe", type: FormatDateNumberToDigitsPipe, name: "formatDateNumberToDigits" }] });
17549
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: MenuBarComponent, selector: "lib-menu-bar", inputs: { cacheName: "cacheName" }, ngImport: i0, template: "<div id=\"menu-bar\">\r\n <div *ngIf=\"!isVisitor\">\r\n <nav class=\"navbar navbar-expand-md navbar-light p-0\">\r\n <div class=\"container-fluid\" *ngIf=\"isVisible\">\r\n <div class=\"navbar-brand order-md-0\" [routerLink]=\"['/dashboard']\">\r\n <img alt=\"SmarterPlan\" src=\"../assets/images/logos/logo-immo.png\">\r\n </div>\r\n <div class=\"ms-auto nav-item dropdown profile_pic-container order-md-2\">\r\n <div class=\"profile_pic dropdown-toggle\" data-testid=\"dropdown-menu-button\" data-bs-toggle=\"dropdown\">\r\n <lib-avatar></lib-avatar>\r\n </div>\r\n <div class=\"dropdown-menu dropdown-menu-right\">\r\n <p *ngIf=\"user && mission\" class=\"p-2\">\r\n <span class=\"username\">{{ user.displayName }}</span><br />\r\n <span class=\"user-role\">{{ mission.role }}</span><br />\r\n <span class=\"org\">{{ mission.organisation ? mission.organisation.name : \"No organisation\" |\r\n translate}}</span>\r\n </p>\r\n <div class=\"dropdown-divider\"></div>\r\n <!-- <a class=\"dropdown-item\" routerLink=\"/debug\" *ngIf=\"isManager\">Debug</a> -->\r\n <a class=\"dropdown-item\" (click)=\"localeService.modal()\">{{'Language' | translate}}</a>\r\n <a class=\"dropdown-item\" routerLink=\"/dashboard\">{{'Data management' | translate}}</a>\r\n <a class=\"dropdown-item\" data-testid=\"user-management-btn\" routerLink=\"/admin\" *ngIf=\"isManager\">\r\n {{'Users management' | translate}}</a>\r\n <a class=\"dropdown-item\" routerLink=\"/profile\">\r\n {{'Profile' | translate}}</a>\r\n <div class=\"dropdown-divider\"></div>\r\n <button class=\"dropdown-item\" (click)=\"openSupportForm()\">{{\"Help\" | translate}}</button>\r\n <button data-testid=\"logout-auth-btn\" class=\"dropdown-item\" (click)=\"signOut()\">{{\"Logout\" |\r\n translate}}</button>\r\n </div>\r\n </div>\r\n <button class=\"navbar-toggler order-md-1\" (click)=\"navbarCollapse.toggle()\" type=\"button\"\r\n [attr.aria-expanded]=\"!isCollapsed\" aria-controls=\"navbar-collapse\">\r\n <span class=\"navbar-toggler-icon\"></span>\r\n </button>\r\n <div class=\"navbar-collapse order-md-1\" #navbarCollapse=\"ngbCollapse\" [(ngbCollapse)]=\"isCollapsed\"\r\n [animation]=\"false\">\r\n <form\r\n class=\"form-inline m-0 mt-2 form-container d-flex justify-content-start align-items-start align-items-md-center flex-column flex-md-row flex-nowrap filters-menu-block\">\r\n <div class=\"input-group search-button-container d-flex flex-row flex-nowrap p-0 m-0\">\r\n <input class=\"form-control search-button m-1\" placeholder=\"keyword\" id=\"mainSearchBar\" type=\"text\">\r\n <button class=\"btn btn-outline-primary rounded-pill m-1 w-fit-content h-fit-content align-self-md-center\"\r\n (click)=\"onSubmit()\">{{'Search' |\r\n translate}}</button>\r\n </div>\r\n <div class=\"input-group search-button-container d-flex flex-row flex-nowrap p-0 m-0\">\r\n <div class=\"input-group date-selector m-1\">\r\n <input #dpFromDate class=\"form-control\" placeholder=\"dd/MM/yyyy\" name=\"dpFromDate\"\r\n [value]=\"fromDate | formatDateNumberToDigits\"\r\n (input)=\"fromDate = validateInput(fromDate, dpFromDate.value)\">\r\n <div class=\"input-group-append\">\r\n <button class=\"btn btn-outline-secondary calendar\" (click)=\"datepicker.toggle()\"\r\n type=\"button\"></button>\r\n </div>\r\n </div>\r\n <input name=\"datepicker\" class=\"form-control visually-hidden\" ngbDatepicker #datepicker=\"ngbDatepicker\"\r\n [autoClose]=\"'outside'\" (dateSelect)=\"onDateSelection($event)\" [displayMonths]=\"2\" [dayTemplate]=\"t\"\r\n outsideDays=\"hidden\" [startDate]=\"fromDate || calendar.getToday()\" [container]=\"'body'\" tabindex=\"-1\">\r\n <ng-template #t let-date let-focused=\"focused\">\r\n <span class=\"custom-day\" [class.focused]=\"focused\" [class.range]=\"isRange(date)\"\r\n [class.faded]=\"isHovered(date) || isInside(date)\" (mouseenter)=\"hoveredDate = date\"\r\n (mouseleave)=\"hoveredDate = null\">\r\n {{ date.day }}\r\n </span>\r\n </ng-template>\r\n <span class=\"ms-1 p-2\">{{'to' | translate}}</span>\r\n <div class=\"input-group date-selector m-1\">\r\n <input #dpToDate class=\"form-control\" placeholder=\"dd/MM/yyyy\" name=\"dpToDate\"\r\n [value]=\"toDate | formatDateNumberToDigits\" (input)=\"toDate = validateInput(toDate, dpToDate.value)\">\r\n <div class=\"input-group-append\">\r\n <button class=\"btn btn-outline-secondary calendar\" (click)=\"datepicker.toggle()\"\r\n type=\"button\"></button>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"input-group search-button-container d-flex flex-row flex-nowrap p-0 m-1 justify-content-evenly\">\r\n <button\r\n class=\"btn btn-primary rounded-pill ms-1 w-25 w-md-fit-content h-md-fit-content align-self-md-center\"\r\n type=\"button\" (click)=\"onApply()\" *ngIf=\"!isDateFilterApplied || dateChanged\">\r\n {{'Apply' | translate}}\r\n </button>\r\n <span\r\n class=\"appliedText ms-1 py-2 align-self-md-center pb-md-0 mx-2 w-md-fit-content h-md-fit-content align-self-md-center\"\r\n *ngIf=\"isDateFilterApplied && !dateChanged\">\r\n <span class=\"iconify\" data-icon=\"ion:checkmark\" data-width=\"15\" data-height=\"15\"></span>\r\n {{\"Applied\" | translate}}\r\n </span>\r\n <button\r\n class=\"btn btn-outline-primary rounded-pill w-25 mx-2 w-md-fit-content h-md-fit-content align-self-md-center\"\r\n type=\"button\" (click)=\"onClearDates()\">{{'Clear'\r\n |\r\n translate}}</button>\r\n <div\r\n class=\"change-position-button align-self-end pb-2 align-self-md-center mx-2 pb-md-0 flex-nowrap w-25 w-md-fit-content h-md-fit-content\">\r\n <input type=\"checkbox\" class=\"btn-check\" id=\"btn-check-outlined\" autocomplete=\"off\">\r\n <label class=\"link-primary cursor-pointer text-decoration-underline\" (click)=\"onChangePositionClick()\"\r\n for=\"btn-check-outlined\">{{\r\n (isChangePositionVisible ? \"Close\" : \"Move to\") | translate }}</label>\r\n </div>\r\n </div>\r\n </form>\r\n </div>\r\n </div>\r\n </nav>\r\n <lib-navigation-bar *ngIf=\"isVisible && isChangePositionVisible\"></lib-navigation-bar>\r\n </div>\r\n <div *ngIf=\"isVisitor && isVisible\">\r\n <div class=\"visitor-div\">\r\n <div class=\"logo-container\" data-testid=\"dropdown-menu-button\">\r\n <img class=\"img-logo\" alt=\"SmarterPlanLogo\" src=\"../assets/images/logos/logo-reserve.png\">\r\n </div>\r\n <div class=\"avatar-container-menu\">\r\n <div class=\"profile_pic dropdown-toggle cursor-pointer\" data-bs-toggle=\"dropdown\">\r\n <lib-avatar></lib-avatar>\r\n </div>\r\n <div class=\"dropdown-menu dropdown-menu-right\">\r\n <button class=\"dropdown-item\" (click)=\"localeService.modal()\">{{'Language' | translate}}</button>\r\n <button class=\"dropdown-item\" (click)=\"openSupportForm()\">{{\"Help\" | translate}}</button>\r\n <button data-testid=\"logout-auth-btn\" class=\"dropdown-item\" (click)=\"signOut()\">{{\"Logout\" |\r\n translate}}</button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>", styles: ["#menu-bar{position:sticky;top:0;z-index:200}.navbar{z-index:200}.navbar-brand{padding-top:5px;padding-bottom:5px;cursor:pointer}.navbar-brand img{height:70px}.profile_pic{border-radius:50%;height:45px;width:45px;margin:8px;border:.2px solid rgb(150,150,150)}.navbar-nav .profile-pic-container .dropdown-menu{position:absolute}.navbar.second-nav{z-index:199;background-color:var(--smarterplan-primary)}.profile_pic.dropdown-toggle:after{display:none}.navbar-light{border-bottom:1px solid var(--smarterplan-primary);background-color:#fff}.navbar form{width:100%;margin:0;padding:0}input,select{margin-right:15px}.select{background:transparent;border:none;color:#fff;margin:6px 20px 0;width:auto}button{padding:5px 10px}button:disabled,button[disabled]{color:#c04f4f}.navbar-toggler{margin-right:1em}.icon{height:40px;width:40px;color:var(--smarterplan-primary)}.search-button-container{width:auto;align-items:stretch;justify-items:start}.search-button-container .search-button{width:auto;border-radius:1rem!important}.visitor-div{background-color:var(--smarterplan-primary);height:60px;display:flex;justify-content:space-between}.profile_pic-container{margin-right:1em}.logo-container,.img-logo{height:100%}.change-position-button{margin-left:2rem}.change-position-button label,.change-position-button .btn-check:checked,.change-position-button .btn-check:focus,.change-position-button .btn-outline-primary,.change-position-button .btn{box-shadow:none!important;outline:0}.change-position-button label{border-radius:16px}.change-position-button .btn-check:checked+.btn{background-color:var(--smarterplan-primary);color:#fff!important}.filters-menu-block{width:auto}@media (min-width: 1200px){.navbar-brand img{height:100px}.profile_pic{height:60px;width:60px;margin:15px}}@media (min-width: 992px) and (max-width: 1199.98px){.navbar-brand img{height:80px}.profile_pic{height:50px;width:50px;margin:12px}}@media (min-width: 768px) and (max-width: 991.98px){.navbar-brand img{height:60px}.profile_pic{height:40px;width:40px;margin:10px}}@media (max-width: 767.98px){.navbar-brand img{height:50px}.profile_pic{height:30px;width:30px;margin:8px}.search-button-container,.filters-menu-block{width:100%}}.form-container{margin:.5rem 0}.navbar-collapse.show{display:flex;padding-bottom:.5rem}.hidden{width:0;margin:0;border:none;padding:0}.custom-day{text-align:center;padding:.185rem .25rem;display:inline-block;height:2rem;width:2rem}.custom-day.focused{background-color:#e6e6e6}.custom-day.range,.custom-day:hover{background-color:var(--smarterplan-primary-lighter);color:#fff}.custom-day.faded{background-color:var(--smarterplan-primary-transparent)}.calendar{height:100%}.appliedText,.appliedText span{color:var(--smarterplan-primary)}@media (max-width: 767px){.form-inline{flex-direction:column}}.hidden{opacity:0}.date-selector{flex-wrap:nowrap}.date-selector input{min-width:80px;margin:0}@media (min-width: 768px){.w-md-fit-content{width:fit-content!important}.h-md-content{height:fit-content!important}}\n"], dependencies: [{ kind: "directive", type: i3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i11.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i1$2.NgbCollapse, selector: "[ngbCollapse]", inputs: ["animation", "ngbCollapse", "horizontal"], outputs: ["ngbCollapseChange", "shown", "hidden"], exportAs: ["ngbCollapse"] }, { kind: "directive", type: i1$2.NgbInputDatepicker, selector: "input[ngbDatepicker]", inputs: ["autoClose", "contentTemplate", "datepickerClass", "dayTemplate", "dayTemplateData", "displayMonths", "firstDayOfWeek", "footerTemplate", "markDisabled", "minDate", "maxDate", "navigation", "outsideDays", "placement", "popperOptions", "restoreFocus", "showWeekNumbers", "startDate", "container", "positionTarget", "weekdays", "disabled"], outputs: ["dateSelect", "navigate", "closed"], exportAs: ["ngbDatepicker"] }, { kind: "component", type: AvatarComponent, selector: "lib-avatar", inputs: ["size", "userID"] }, { kind: "component", type: NavigationBarComponent, selector: "lib-navigation-bar" }, { kind: "pipe", type: i1.TranslatePipe, name: "translate" }, { kind: "pipe", type: FormatDateNumberToDigitsPipe, name: "formatDateNumberToDigits" }] });
16621
17550
  }
16622
17551
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MenuBarComponent, decorators: [{
16623
17552
  type: Component,
16624
- args: [{ selector: 'lib-menu-bar', template: "<div id=\"menu-bar\">\n <div *ngIf=\"!isVisitor\">\n <nav class=\"navbar navbar-expand-md navbar-light p-0\">\n <div class=\"container-fluid\" *ngIf=\"isVisible\">\n <div class=\"navbar-brand order-md-0\" [routerLink]=\"['/dashboard']\">\n <img alt=\"SmarterPlan\" src=\"../assets/images/logos/logo-immo.png\">\n </div>\n <div class=\"ms-auto nav-item dropdown profile_pic-container order-md-2\">\n <div class=\"profile_pic dropdown-toggle\" data-testid=\"dropdown-menu-button\" data-bs-toggle=\"dropdown\">\n <lib-avatar></lib-avatar>\n </div>\n <div class=\"dropdown-menu dropdown-menu-right\">\n <p *ngIf=\"user && mission\" class=\"p-2\">\n <span class=\"username\">{{ user.displayName }}</span><br />\n <span class=\"user-role\">{{ mission.role }}</span><br />\n <span class=\"org\">{{ mission.organisation ? mission.organisation.name : \"No organisation\" |\n translate}}</span>\n </p>\n <div class=\"dropdown-divider\"></div>\n <!-- <a class=\"dropdown-item\" routerLink=\"/debug\" *ngIf=\"isManager\">Debug</a> -->\n <a class=\"dropdown-item\" (click)=\"localeService.modal()\">{{'Language' | translate}}</a>\n <a class=\"dropdown-item\" routerLink=\"/dashboard\">{{'Data management' | translate}}</a>\n <a class=\"dropdown-item\" data-testid=\"user-management-btn\" routerLink=\"/admin\" *ngIf=\"isManager\">\n {{'Users management' | translate}}</a>\n <a class=\"dropdown-item\" routerLink=\"/profile\">\n {{'Profile' | translate}}</a>\n <div class=\"dropdown-divider\"></div>\n <button class=\"dropdown-item\" (click)=\"openSupportForm()\">{{\"Help\" | translate}}</button>\n <button data-testid=\"logout-auth-btn\" class=\"dropdown-item\" (click)=\"signOut()\">{{\"Logout\" |\n translate}}</button>\n </div>\n </div>\n <button class=\"navbar-toggler order-md-1\" (click)=\"navbarCollapse.toggle()\" type=\"button\"\n [attr.aria-expanded]=\"!isCollapsed\" aria-controls=\"navbar-collapse\">\n <span class=\"navbar-toggler-icon\"></span>\n </button>\n <div class=\"navbar-collapse order-md-1\" #navbarCollapse=\"ngbCollapse\" [(ngbCollapse)]=\"isCollapsed\"\n [animation]=\"false\">\n <form\n class=\"form-inline m-0 mt-2 form-container d-flex justify-content-start align-items-start align-items-md-center flex-column flex-md-row flex-nowrap filters-menu-block\">\n <div class=\"input-group search-button-container d-flex flex-row flex-nowrap p-0 m-0\">\n <input class=\"form-control search-button m-1\" placeholder=\"keyword\" id=\"mainSearchBar\" type=\"text\">\n <button class=\"btn btn-outline-primary rounded-pill m-1 w-fit-content h-fit-content align-self-md-center\"\n (click)=\"onSubmit()\">{{'Search' |\n translate}}</button>\n </div>\n <div class=\"input-group search-button-container d-flex flex-row flex-nowrap p-0 m-0\">\n <div class=\"input-group date-selector m-1\">\n <input #dpFromDate class=\"form-control\" placeholder=\"dd/MM/yyyy\" name=\"dpFromDate\"\n [value]=\"fromDate | formatDateNumberToDigits\"\n (input)=\"fromDate = validateInput(fromDate, dpFromDate.value)\">\n <div class=\"input-group-append\">\n <button class=\"btn btn-outline-secondary calendar\" (click)=\"datepicker.toggle()\"\n type=\"button\"></button>\n </div>\n </div>\n <input name=\"datepicker\" class=\"form-control visually-hidden\" ngbDatepicker #datepicker=\"ngbDatepicker\"\n [autoClose]=\"'outside'\" (dateSelect)=\"onDateSelection($event)\" [displayMonths]=\"2\" [dayTemplate]=\"t\"\n outsideDays=\"hidden\" [startDate]=\"fromDate || calendar.getToday()\" [container]=\"'body'\" tabindex=\"-1\">\n <ng-template #t let-date let-focused=\"focused\">\n <span class=\"custom-day\" [class.focused]=\"focused\" [class.range]=\"isRange(date)\"\n [class.faded]=\"isHovered(date) || isInside(date)\" (mouseenter)=\"hoveredDate = date\"\n (mouseleave)=\"hoveredDate = null\">\n {{ date.day }}\n </span>\n </ng-template>\n <span class=\"ms-1 p-2\">{{'to' | translate}}</span>\n <div class=\"input-group date-selector m-1\">\n <input #dpToDate class=\"form-control\" placeholder=\"dd/MM/yyyy\" name=\"dpToDate\"\n [value]=\"toDate | formatDateNumberToDigits\" (input)=\"toDate = validateInput(toDate, dpToDate.value)\">\n <div class=\"input-group-append\">\n <button class=\"btn btn-outline-secondary calendar\" (click)=\"datepicker.toggle()\"\n type=\"button\"></button>\n </div>\n </div>\n </div>\n <div class=\"input-group search-button-container d-flex flex-row flex-nowrap p-0 m-1 justify-content-evenly\">\n <button\n class=\"btn btn-primary rounded-pill ms-1 w-25 w-md-fit-content h-md-fit-content align-self-md-center\"\n type=\"button\" (click)=\"onApply()\" *ngIf=\"!isDateFilterApplied || dateChanged\">\n {{'Apply' | translate}}\n </button>\n <span\n class=\"appliedText ms-1 py-2 align-self-md-center pb-md-0 mx-2 w-md-fit-content h-md-fit-content align-self-md-center\"\n *ngIf=\"isDateFilterApplied && !dateChanged\">\n <span class=\"iconify\" data-icon=\"ion:checkmark\" data-width=\"15\" data-height=\"15\"></span>\n {{\"Applied\" | translate}}\n </span>\n <button\n class=\"btn btn-outline-primary rounded-pill w-25 mx-2 w-md-fit-content h-md-fit-content align-self-md-center\"\n type=\"button\" (click)=\"onClearDates()\">{{'Clear'\n |\n translate}}</button>\n <div\n class=\"change-position-button align-self-end pb-2 align-self-md-center mx-2 pb-md-0 flex-nowrap w-25 w-md-fit-content h-md-fit-content\">\n <input type=\"checkbox\" class=\"btn-check\" id=\"btn-check-outlined\" autocomplete=\"off\">\n <label class=\"link-primary cursor-pointer text-decoration-underline\" (click)=\"onChangePositionClick()\"\n for=\"btn-check-outlined\">{{\n (isChangePositionVisible ? \"Close\" : \"Move to\") | translate }}</label>\n </div>\n </div>\n </form>\n </div>\n </div>\n </nav>\n <lib-navigation-bar *ngIf=\"isVisible && isChangePositionVisible\"></lib-navigation-bar>\n </div>\n <div *ngIf=\"isVisitor && isVisible\">\n <div class=\"visitor-div\">\n <div class=\"logo-container\" data-testid=\"dropdown-menu-button\">\n <img class=\"img-logo\" alt=\"SmarterPlanLogo\" src=\"../assets/images/logos/logo-reserve.png\">\n </div>\n <div class=\"avatar-container-menu\">\n <div class=\"profile_pic dropdown-toggle cursor-pointer\" data-bs-toggle=\"dropdown\">\n <lib-avatar></lib-avatar>\n </div>\n <div class=\"dropdown-menu dropdown-menu-right\">\n <button class=\"dropdown-item\" (click)=\"localeService.modal()\">{{'Language' | translate}}</button>\n <button class=\"dropdown-item\" (click)=\"openSupportForm()\">{{\"Help\" | translate}}</button>\n <button data-testid=\"logout-auth-btn\" class=\"dropdown-item\" (click)=\"signOut()\">{{\"Logout\" |\n translate}}</button>\n </div>\n </div>\n </div>\n </div>\n</div>", styles: ["#menu-bar{position:sticky;top:0;z-index:200}.navbar{z-index:200}.navbar-brand{padding-top:5px;padding-bottom:5px;cursor:pointer}.navbar-brand img{height:70px}.profile_pic{border-radius:50%;height:45px;width:45px;margin:8px;border:.2px solid rgb(150,150,150)}.navbar-nav .profile-pic-container .dropdown-menu{position:absolute}.navbar.second-nav{z-index:199;background-color:var(--smarterplan-primary)}.profile_pic.dropdown-toggle:after{display:none}.navbar-light{border-bottom:1px solid var(--smarterplan-primary);background-color:#fff}.navbar form{width:100%;margin:0;padding:0}input,select{margin-right:15px}.select{background:transparent;border:none;color:#fff;margin:6px 20px 0;width:auto}button{padding:5px 10px}button:disabled,button[disabled]{color:#c04f4f}.navbar-toggler{margin-right:1em}.icon{height:40px;width:40px;color:var(--smarterplan-primary)}.search-button-container{width:auto;align-items:stretch;justify-items:start}.search-button-container .search-button{width:auto;border-radius:1rem!important}.visitor-div{background-color:var(--smarterplan-primary);height:60px;display:flex;justify-content:space-between}.profile_pic-container{margin-right:1em}.logo-container,.img-logo{height:100%}.change-position-button{margin-left:2rem}.change-position-button label,.change-position-button .btn-check:checked,.change-position-button .btn-check:focus,.change-position-button .btn-outline-primary,.change-position-button .btn{box-shadow:none!important;outline:0}.change-position-button label{border-radius:16px}.change-position-button .btn-check:checked+.btn{background-color:var(--smarterplan-primary);color:#fff!important}.filters-menu-block{width:auto}@media (min-width: 1200px){.navbar-brand img{height:100px}.profile_pic{height:60px;width:60px;margin:15px}}@media (min-width: 992px) and (max-width: 1199.98px){.navbar-brand img{height:80px}.profile_pic{height:50px;width:50px;margin:12px}}@media (min-width: 768px) and (max-width: 991.98px){.navbar-brand img{height:60px}.profile_pic{height:40px;width:40px;margin:10px}}@media (max-width: 767.98px){.navbar-brand img{height:50px}.profile_pic{height:30px;width:30px;margin:8px}.search-button-container,.filters-menu-block{width:100%}}.form-container{margin:.5rem 0}.navbar-collapse.show{display:flex;padding-bottom:.5rem}.hidden{width:0;margin:0;border:none;padding:0}.custom-day{text-align:center;padding:.185rem .25rem;display:inline-block;height:2rem;width:2rem}.custom-day.focused{background-color:#e6e6e6}.custom-day.range,.custom-day:hover{background-color:var(--smarterplan-primary-lighter);color:#fff}.custom-day.faded{background-color:var(--smarterplan-primary-transparent)}.calendar{height:100%}.appliedText,.appliedText span{color:var(--smarterplan-primary)}@media (max-width: 767px){.form-inline{flex-direction:column}}.hidden{opacity:0}.date-selector{flex-wrap:nowrap}.date-selector input{min-width:80px;margin:0}@media (min-width: 768px){.w-md-fit-content{width:fit-content!important}.h-md-content{height:fit-content!important}}\n"] }]
17553
+ args: [{ selector: 'lib-menu-bar', template: "<div id=\"menu-bar\">\r\n <div *ngIf=\"!isVisitor\">\r\n <nav class=\"navbar navbar-expand-md navbar-light p-0\">\r\n <div class=\"container-fluid\" *ngIf=\"isVisible\">\r\n <div class=\"navbar-brand order-md-0\" [routerLink]=\"['/dashboard']\">\r\n <img alt=\"SmarterPlan\" src=\"../assets/images/logos/logo-immo.png\">\r\n </div>\r\n <div class=\"ms-auto nav-item dropdown profile_pic-container order-md-2\">\r\n <div class=\"profile_pic dropdown-toggle\" data-testid=\"dropdown-menu-button\" data-bs-toggle=\"dropdown\">\r\n <lib-avatar></lib-avatar>\r\n </div>\r\n <div class=\"dropdown-menu dropdown-menu-right\">\r\n <p *ngIf=\"user && mission\" class=\"p-2\">\r\n <span class=\"username\">{{ user.displayName }}</span><br />\r\n <span class=\"user-role\">{{ mission.role }}</span><br />\r\n <span class=\"org\">{{ mission.organisation ? mission.organisation.name : \"No organisation\" |\r\n translate}}</span>\r\n </p>\r\n <div class=\"dropdown-divider\"></div>\r\n <!-- <a class=\"dropdown-item\" routerLink=\"/debug\" *ngIf=\"isManager\">Debug</a> -->\r\n <a class=\"dropdown-item\" (click)=\"localeService.modal()\">{{'Language' | translate}}</a>\r\n <a class=\"dropdown-item\" routerLink=\"/dashboard\">{{'Data management' | translate}}</a>\r\n <a class=\"dropdown-item\" data-testid=\"user-management-btn\" routerLink=\"/admin\" *ngIf=\"isManager\">\r\n {{'Users management' | translate}}</a>\r\n <a class=\"dropdown-item\" routerLink=\"/profile\">\r\n {{'Profile' | translate}}</a>\r\n <div class=\"dropdown-divider\"></div>\r\n <button class=\"dropdown-item\" (click)=\"openSupportForm()\">{{\"Help\" | translate}}</button>\r\n <button data-testid=\"logout-auth-btn\" class=\"dropdown-item\" (click)=\"signOut()\">{{\"Logout\" |\r\n translate}}</button>\r\n </div>\r\n </div>\r\n <button class=\"navbar-toggler order-md-1\" (click)=\"navbarCollapse.toggle()\" type=\"button\"\r\n [attr.aria-expanded]=\"!isCollapsed\" aria-controls=\"navbar-collapse\">\r\n <span class=\"navbar-toggler-icon\"></span>\r\n </button>\r\n <div class=\"navbar-collapse order-md-1\" #navbarCollapse=\"ngbCollapse\" [(ngbCollapse)]=\"isCollapsed\"\r\n [animation]=\"false\">\r\n <form\r\n class=\"form-inline m-0 mt-2 form-container d-flex justify-content-start align-items-start align-items-md-center flex-column flex-md-row flex-nowrap filters-menu-block\">\r\n <div class=\"input-group search-button-container d-flex flex-row flex-nowrap p-0 m-0\">\r\n <input class=\"form-control search-button m-1\" placeholder=\"keyword\" id=\"mainSearchBar\" type=\"text\">\r\n <button class=\"btn btn-outline-primary rounded-pill m-1 w-fit-content h-fit-content align-self-md-center\"\r\n (click)=\"onSubmit()\">{{'Search' |\r\n translate}}</button>\r\n </div>\r\n <div class=\"input-group search-button-container d-flex flex-row flex-nowrap p-0 m-0\">\r\n <div class=\"input-group date-selector m-1\">\r\n <input #dpFromDate class=\"form-control\" placeholder=\"dd/MM/yyyy\" name=\"dpFromDate\"\r\n [value]=\"fromDate | formatDateNumberToDigits\"\r\n (input)=\"fromDate = validateInput(fromDate, dpFromDate.value)\">\r\n <div class=\"input-group-append\">\r\n <button class=\"btn btn-outline-secondary calendar\" (click)=\"datepicker.toggle()\"\r\n type=\"button\"></button>\r\n </div>\r\n </div>\r\n <input name=\"datepicker\" class=\"form-control visually-hidden\" ngbDatepicker #datepicker=\"ngbDatepicker\"\r\n [autoClose]=\"'outside'\" (dateSelect)=\"onDateSelection($event)\" [displayMonths]=\"2\" [dayTemplate]=\"t\"\r\n outsideDays=\"hidden\" [startDate]=\"fromDate || calendar.getToday()\" [container]=\"'body'\" tabindex=\"-1\">\r\n <ng-template #t let-date let-focused=\"focused\">\r\n <span class=\"custom-day\" [class.focused]=\"focused\" [class.range]=\"isRange(date)\"\r\n [class.faded]=\"isHovered(date) || isInside(date)\" (mouseenter)=\"hoveredDate = date\"\r\n (mouseleave)=\"hoveredDate = null\">\r\n {{ date.day }}\r\n </span>\r\n </ng-template>\r\n <span class=\"ms-1 p-2\">{{'to' | translate}}</span>\r\n <div class=\"input-group date-selector m-1\">\r\n <input #dpToDate class=\"form-control\" placeholder=\"dd/MM/yyyy\" name=\"dpToDate\"\r\n [value]=\"toDate | formatDateNumberToDigits\" (input)=\"toDate = validateInput(toDate, dpToDate.value)\">\r\n <div class=\"input-group-append\">\r\n <button class=\"btn btn-outline-secondary calendar\" (click)=\"datepicker.toggle()\"\r\n type=\"button\"></button>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"input-group search-button-container d-flex flex-row flex-nowrap p-0 m-1 justify-content-evenly\">\r\n <button\r\n class=\"btn btn-primary rounded-pill ms-1 w-25 w-md-fit-content h-md-fit-content align-self-md-center\"\r\n type=\"button\" (click)=\"onApply()\" *ngIf=\"!isDateFilterApplied || dateChanged\">\r\n {{'Apply' | translate}}\r\n </button>\r\n <span\r\n class=\"appliedText ms-1 py-2 align-self-md-center pb-md-0 mx-2 w-md-fit-content h-md-fit-content align-self-md-center\"\r\n *ngIf=\"isDateFilterApplied && !dateChanged\">\r\n <span class=\"iconify\" data-icon=\"ion:checkmark\" data-width=\"15\" data-height=\"15\"></span>\r\n {{\"Applied\" | translate}}\r\n </span>\r\n <button\r\n class=\"btn btn-outline-primary rounded-pill w-25 mx-2 w-md-fit-content h-md-fit-content align-self-md-center\"\r\n type=\"button\" (click)=\"onClearDates()\">{{'Clear'\r\n |\r\n translate}}</button>\r\n <div\r\n class=\"change-position-button align-self-end pb-2 align-self-md-center mx-2 pb-md-0 flex-nowrap w-25 w-md-fit-content h-md-fit-content\">\r\n <input type=\"checkbox\" class=\"btn-check\" id=\"btn-check-outlined\" autocomplete=\"off\">\r\n <label class=\"link-primary cursor-pointer text-decoration-underline\" (click)=\"onChangePositionClick()\"\r\n for=\"btn-check-outlined\">{{\r\n (isChangePositionVisible ? \"Close\" : \"Move to\") | translate }}</label>\r\n </div>\r\n </div>\r\n </form>\r\n </div>\r\n </div>\r\n </nav>\r\n <lib-navigation-bar *ngIf=\"isVisible && isChangePositionVisible\"></lib-navigation-bar>\r\n </div>\r\n <div *ngIf=\"isVisitor && isVisible\">\r\n <div class=\"visitor-div\">\r\n <div class=\"logo-container\" data-testid=\"dropdown-menu-button\">\r\n <img class=\"img-logo\" alt=\"SmarterPlanLogo\" src=\"../assets/images/logos/logo-reserve.png\">\r\n </div>\r\n <div class=\"avatar-container-menu\">\r\n <div class=\"profile_pic dropdown-toggle cursor-pointer\" data-bs-toggle=\"dropdown\">\r\n <lib-avatar></lib-avatar>\r\n </div>\r\n <div class=\"dropdown-menu dropdown-menu-right\">\r\n <button class=\"dropdown-item\" (click)=\"localeService.modal()\">{{'Language' | translate}}</button>\r\n <button class=\"dropdown-item\" (click)=\"openSupportForm()\">{{\"Help\" | translate}}</button>\r\n <button data-testid=\"logout-auth-btn\" class=\"dropdown-item\" (click)=\"signOut()\">{{\"Logout\" |\r\n translate}}</button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>", styles: ["#menu-bar{position:sticky;top:0;z-index:200}.navbar{z-index:200}.navbar-brand{padding-top:5px;padding-bottom:5px;cursor:pointer}.navbar-brand img{height:70px}.profile_pic{border-radius:50%;height:45px;width:45px;margin:8px;border:.2px solid rgb(150,150,150)}.navbar-nav .profile-pic-container .dropdown-menu{position:absolute}.navbar.second-nav{z-index:199;background-color:var(--smarterplan-primary)}.profile_pic.dropdown-toggle:after{display:none}.navbar-light{border-bottom:1px solid var(--smarterplan-primary);background-color:#fff}.navbar form{width:100%;margin:0;padding:0}input,select{margin-right:15px}.select{background:transparent;border:none;color:#fff;margin:6px 20px 0;width:auto}button{padding:5px 10px}button:disabled,button[disabled]{color:#c04f4f}.navbar-toggler{margin-right:1em}.icon{height:40px;width:40px;color:var(--smarterplan-primary)}.search-button-container{width:auto;align-items:stretch;justify-items:start}.search-button-container .search-button{width:auto;border-radius:1rem!important}.visitor-div{background-color:var(--smarterplan-primary);height:60px;display:flex;justify-content:space-between}.profile_pic-container{margin-right:1em}.logo-container,.img-logo{height:100%}.change-position-button{margin-left:2rem}.change-position-button label,.change-position-button .btn-check:checked,.change-position-button .btn-check:focus,.change-position-button .btn-outline-primary,.change-position-button .btn{box-shadow:none!important;outline:0}.change-position-button label{border-radius:16px}.change-position-button .btn-check:checked+.btn{background-color:var(--smarterplan-primary);color:#fff!important}.filters-menu-block{width:auto}@media (min-width: 1200px){.navbar-brand img{height:100px}.profile_pic{height:60px;width:60px;margin:15px}}@media (min-width: 992px) and (max-width: 1199.98px){.navbar-brand img{height:80px}.profile_pic{height:50px;width:50px;margin:12px}}@media (min-width: 768px) and (max-width: 991.98px){.navbar-brand img{height:60px}.profile_pic{height:40px;width:40px;margin:10px}}@media (max-width: 767.98px){.navbar-brand img{height:50px}.profile_pic{height:30px;width:30px;margin:8px}.search-button-container,.filters-menu-block{width:100%}}.form-container{margin:.5rem 0}.navbar-collapse.show{display:flex;padding-bottom:.5rem}.hidden{width:0;margin:0;border:none;padding:0}.custom-day{text-align:center;padding:.185rem .25rem;display:inline-block;height:2rem;width:2rem}.custom-day.focused{background-color:#e6e6e6}.custom-day.range,.custom-day:hover{background-color:var(--smarterplan-primary-lighter);color:#fff}.custom-day.faded{background-color:var(--smarterplan-primary-transparent)}.calendar{height:100%}.appliedText,.appliedText span{color:var(--smarterplan-primary)}@media (max-width: 767px){.form-inline{flex-direction:column}}.hidden{opacity:0}.date-selector{flex-wrap:nowrap}.date-selector input{min-width:80px;margin:0}@media (min-width: 768px){.w-md-fit-content{width:fit-content!important}.h-md-content{height:fit-content!important}}\n"] }]
16625
17554
  }], ctorParameters: () => [{ type: i1$1.Router }, { type: LocaleService }, { type: BaseUserService }, { type: NavigatorService }, { type: SupportService }, { type: BaseVisibilityService }, { type: i1$2.NgbCalendar }, { type: i1$2.NgbDateParserFormatter }, { type: FilterService }, { type: AmplifyCacheService }], propDecorators: { cacheName: [{
16626
17555
  type: Input
16627
17556
  }] } });
@@ -16776,27 +17705,27 @@ class BaseTagService {
16776
17705
  throw new Error('updatePoiMetadata is not implemented in the base class.');
16777
17706
  }*/
16778
17707
  async prepareEquipmentHtml(equip) {
16779
- throw new Error(`prepareEquipmentHtml is not implemented in the base class.
17708
+ throw new Error(`prepareEquipmentHtml is not implemented in the base class.
16780
17709
  Please override this method in your extended class.`);
16781
17710
  }
16782
17711
  async prepareTicketHtml(ticket) {
16783
- throw new Error(`prepareTicketHtml is not implemented in the base class.
17712
+ throw new Error(`prepareTicketHtml is not implemented in the base class.
16784
17713
  Please override this method in your extended class.`);
16785
17714
  }
16786
17715
  async prepareFeatureHtml(feature) {
16787
- throw new Error(`prepareFeatureHtml is not implemented in the base class.
17716
+ throw new Error(`prepareFeatureHtml is not implemented in the base class.
16788
17717
  Please override this method in your extended class.`);
16789
17718
  }
16790
17719
  async prepareMeasurementHtml(measure) {
16791
- throw new Error(`prepareMeasurementHtml is not implemented in the base class.
17720
+ throw new Error(`prepareMeasurementHtml is not implemented in the base class.
16792
17721
  Please override this method in your extended class.`);
16793
17722
  }
16794
17723
  async prepareDeskHtml(feature) {
16795
- throw new Error(`prepareDeskHtml is not implemented in the base class.
17724
+ throw new Error(`prepareDeskHtml is not implemented in the base class.
16796
17725
  Please override this method in your extended class.`);
16797
17726
  }
16798
17727
  async prepareIndicatorHtml(feature) {
16799
- throw new Error(`prepareDeskHtml is not implemented in the base class.
17728
+ throw new Error(`prepareDeskHtml is not implemented in the base class.
16800
17729
  Please override this method in your extended class.`);
16801
17730
  }
16802
17731
  async getSignedTagIconSource(tagIconSrc) {
@@ -16805,15 +17734,15 @@ class BaseTagService {
16805
17734
  getIconTagImageForFeature(feature, poi) {
16806
17735
  // const tagIcon = JSON.parse(poi.tagIcon);
16807
17736
  // return tagIcon.src;
16808
- throw new Error(`getIconTagImageForFeature is not implemented in the base class.
17737
+ throw new Error(`getIconTagImageForFeature is not implemented in the base class.
16809
17738
  Please override this method in your extended class.`);
16810
17739
  }
16811
17740
  getScriptForTag(object, tagType) {
16812
- throw new Error(`getScriptForTag is not implemented in the base class.
17741
+ throw new Error(`getScriptForTag is not implemented in the base class.
16813
17742
  Please override this method in your extended class.`);
16814
17743
  }
16815
17744
  getAnnexeForCommentTypeInFeature(feature, commentType) {
16816
- throw new Error(`getAnnexeForCommentTypeInFeature is not implemented in the base class.
17745
+ throw new Error(`getAnnexeForCommentTypeInFeature is not implemented in the base class.
16817
17746
  Please override this method in your extended class.`);
16818
17747
  }
16819
17748
  getBillboardMediaToEmbed(object) {
@@ -16822,27 +17751,27 @@ class BaseTagService {
16822
17751
  return { comment: null, tagDescription: '' };
16823
17752
  }
16824
17753
  onActionDetailClick(url) {
16825
- throw new Error(`onActionDetailClick is not implemented in the base class.
17754
+ throw new Error(`onActionDetailClick is not implemented in the base class.
16826
17755
  Please override this method in your extended class.`);
16827
17756
  }
16828
17757
  onActionAudioClick(audioCommentID) {
16829
- throw new Error(`onActionDetailClick is not implemented in the base class.
17758
+ throw new Error(`onActionDetailClick is not implemented in the base class.
16830
17759
  Please override this method in your extended class.`);
16831
17760
  }
16832
17761
  onActionVideoClick(url) {
16833
- throw new Error(`onActionVideoClick is not implemented in the base class.
17762
+ throw new Error(`onActionVideoClick is not implemented in the base class.
16834
17763
  Please override this method in your extended class.`);
16835
17764
  }
16836
17765
  onActionImageClick(imageCommentID) {
16837
- throw new Error(`onActionImageClick is not implemented in the base class.
17766
+ throw new Error(`onActionImageClick is not implemented in the base class.
16838
17767
  Please override this method in your extended class.`);
16839
17768
  }
16840
17769
  onActionDocClick(url) {
16841
- throw new Error(`onActionDocClick is not implemented in the base class.
17770
+ throw new Error(`onActionDocClick is not implemented in the base class.
16842
17771
  Please override this method in your extended class.`);
16843
17772
  }
16844
17773
  onActionYoutubeClick(url) {
16845
- throw new Error(`onActionYoutubeClick is not implemented in the base class.
17774
+ throw new Error(`onActionYoutubeClick is not implemented in the base class.
16846
17775
  Please override this method in your extended class.`);
16847
17776
  }
16848
17777
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: BaseTagService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });