@smarterplan/ngx-smarterplan-core 1.4.19 → 1.4.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -24
- package/esm2022/lib/components/csv-export/csv-export.component.mjs +3 -3
- package/esm2022/lib/components/loader/loader.component.mjs +3 -3
- package/esm2022/lib/components/menu-bar/avatar/avatar.component.mjs +1 -1
- package/esm2022/lib/components/menu-bar/menu-bar.component.mjs +3 -3
- package/esm2022/lib/components/menu-bar/navigation-bar/navigation-bar.component.mjs +3 -3
- package/esm2022/lib/components/modal-switch-visit/modal-switch-visit.component.mjs +3 -3
- package/esm2022/lib/components/search-bar/search-bar.component.mjs +3 -3
- package/esm2022/lib/components/support-modal/support-modal.component.mjs +3 -3
- package/esm2022/lib/config.mjs +1 -1
- package/esm2022/lib/helpers.service.mjs +28 -2
- package/esm2022/lib/matterport-extensions/nest-thermostat/CanvasImage.mjs +1 -1
- package/esm2022/lib/matterport-extensions/nest-thermostat/CanvasRenderer.mjs +1 -1
- package/esm2022/lib/matterport-extensions/nest-thermostat/NestThermostat.mjs +1 -1
- package/esm2022/lib/matterport-extensions/nest-thermostat/PlaneRenderer.mjs +1 -1
- package/esm2022/lib/matterport-extensions/scene-component/SceneComponent.mjs +1 -1
- package/esm2022/lib/matterport-extensions/security-camera/SecurityCamera.mjs +22 -22
- package/esm2022/lib/matterport-extensions/tv-player/TvPlayer.mjs +1 -1
- package/esm2022/lib/matterport-extensions/view-frustum-mesh/ViewFrustumMesh.mjs +22 -22
- package/esm2022/lib/mattertagData.mjs +1 -1
- package/esm2022/lib/ngx-smarterplan-core.module.mjs +1 -1
- package/esm2022/lib/ngx-smarterplan-core.service.mjs +1 -1
- package/esm2022/lib/pipes/duration-to-string.pipe.mjs +1 -1
- package/esm2022/lib/pipes/format-date-number-to-digits.pipe.mjs +1 -1
- package/esm2022/lib/pipes/hashtag-from-id.pipe.mjs +1 -1
- package/esm2022/lib/pipes/safe-url.pipe.mjs +1 -1
- package/esm2022/lib/pipes/time-date-to-local-string.pipe.mjs +1 -1
- package/esm2022/lib/pipes/username-from-id.pipe.mjs +1 -1
- package/esm2022/lib/services/amplify-cache.service.mjs +1 -1
- package/esm2022/lib/services/base-tab.service.mjs +1 -1
- package/esm2022/lib/services/baseVisibility.service.mjs +1 -1
- package/esm2022/lib/services/content.service.mjs +1 -1
- package/esm2022/lib/services/filter.service.mjs +8 -3
- package/esm2022/lib/services/intervention.service.mjs +1 -1
- package/esm2022/lib/services/locale.service.mjs +1 -1
- package/esm2022/lib/services/matterport-import.service.mjs +1 -1
- package/esm2022/lib/services/matterport-measurement.service.mjs +1 -1
- package/esm2022/lib/services/matterport-navigation.service.mjs +1 -1
- package/esm2022/lib/services/matterport-object3d.service.mjs +1 -1
- package/esm2022/lib/services/matterport-pointer.service.mjs +6 -6
- package/esm2022/lib/services/matterport-tag-copy.service.mjs +1 -1
- package/esm2022/lib/services/matterport-tag.service.mjs +1 -1
- package/esm2022/lib/services/matterport.service.mjs +17 -17
- package/esm2022/lib/services/models/affectation.service.mjs +1 -1
- package/esm2022/lib/services/models/base-object.service.mjs +10 -9
- package/esm2022/lib/services/models/capture.service.mjs +1 -1
- package/esm2022/lib/services/models/comment.service.mjs +1 -1
- package/esm2022/lib/services/models/domain.service.mjs +1 -1
- package/esm2022/lib/services/models/equipment.service.mjs +27 -2
- package/esm2022/lib/services/models/event.service.mjs +1 -1
- package/esm2022/lib/services/models/feature.service.mjs +14 -1
- package/esm2022/lib/services/models/hashtag.service.mjs +1 -1
- package/esm2022/lib/services/models/layer.service.mjs +1 -1
- package/esm2022/lib/services/models/measurement.service.mjs +17 -1
- package/esm2022/lib/services/models/mission.service.mjs +1 -1
- package/esm2022/lib/services/models/navigation.service.mjs +1 -1
- package/esm2022/lib/services/models/node.service.mjs +1 -1
- package/esm2022/lib/services/models/object3D.service.mjs +1 -1
- package/esm2022/lib/services/models/operation.service.mjs +1 -1
- package/esm2022/lib/services/models/organisation.service.mjs +1 -1
- package/esm2022/lib/services/models/plan.service.mjs +1 -1
- package/esm2022/lib/services/models/poi.service.mjs +27 -27
- package/esm2022/lib/services/models/profile.service.mjs +1 -1
- package/esm2022/lib/services/models/property.service.mjs +1 -1
- package/esm2022/lib/services/models/space.service.mjs +1 -1
- package/esm2022/lib/services/models/template.service.mjs +1 -1
- package/esm2022/lib/services/models/ticket.service.mjs +1 -1
- package/esm2022/lib/services/models/visit.service.mjs +1 -1
- package/esm2022/lib/services/models/zone.service.mjs +1 -1
- package/esm2022/lib/services/navigator.service.mjs +1 -1
- package/esm2022/lib/services/s3.service.mjs +1 -1
- package/esm2022/lib/services/search.service.mjs +1 -1
- package/esm2022/lib/services/support.service.mjs +1 -1
- package/esm2022/lib/services/sweep-locator.service.mjs +114 -0
- package/esm2022/lib/services/tag.service.mjs +16 -16
- package/esm2022/lib/services/threejs-import.service.mjs +1 -1
- package/esm2022/lib/services/threejs.service.mjs +840 -35
- package/esm2022/lib/services/user.service.mjs +1 -1
- package/esm2022/lib/services/utils/matterport-transform.util.mjs +1 -1
- package/esm2022/lib/services/validators.service.mjs +1 -1
- package/esm2022/lib/services/viewer.service.mjs +193 -10
- package/esm2022/lib/services/zone-drawer.service.mjs +1 -1
- package/esm2022/lib/services/zoneChange.service.mjs +1 -1
- package/esm2022/lib/tokens/matterport-sdk-key.token.mjs +1 -1
- package/esm2022/lib/types.service.mjs +1 -1
- package/esm2022/lib/validators/email.directive.mjs +1 -1
- package/esm2022/lib/validators/no-empty.directive.mjs +1 -1
- package/esm2022/lib/validators/number.directive.mjs +1 -1
- package/esm2022/lib/validators/text.directive.mjs +1 -1
- package/esm2022/public-api.mjs +2 -1
- package/fesm2022/smarterplan-ngx-smarterplan-core.mjs +1771 -590
- package/fesm2022/smarterplan-ngx-smarterplan-core.mjs.map +1 -1
- package/lib/helpers.service.d.ts +17 -0
- package/lib/services/models/base-object.service.d.ts +3 -4
- package/lib/services/models/measurement.service.d.ts +1 -0
- package/lib/services/sweep-locator.service.d.ts +54 -0
- package/lib/services/threejs.service.d.ts +304 -10
- package/lib/services/viewer.service.d.ts +87 -2
- package/package.json +1 -1
- package/public-api.d.ts +1 -0
- package/src/assets/vs-app.json +67 -67
|
@@ -20,6 +20,9 @@ import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader';
|
|
|
20
20
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
|
21
21
|
import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader';
|
|
22
22
|
import { MeshoptDecoder } from 'three/examples/jsm/libs/meshopt_decoder.module.js';
|
|
23
|
+
import { Line2 } from 'three/examples/jsm/lines/Line2';
|
|
24
|
+
import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry';
|
|
25
|
+
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial';
|
|
23
26
|
import * as FileSaver from 'file-saver';
|
|
24
27
|
import levenshtein from 'js-levenshtein';
|
|
25
28
|
import { GlobalWorkerOptions, getDocument } from 'pdfjs-dist';
|
|
@@ -87,11 +90,11 @@ class CsvExportComponent {
|
|
|
87
90
|
link.remove();
|
|
88
91
|
}
|
|
89
92
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CsvExportComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
90
|
-
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\">\
|
|
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" }] });
|
|
91
94
|
}
|
|
92
95
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CsvExportComponent, decorators: [{
|
|
93
96
|
type: Component,
|
|
94
|
-
args: [{ selector: 'lib-csv-export', template: "<span [style.cursor]=\"'pointer'\" (click)=\"onClickDownload()\" class=\"download-csv d-flex\">\
|
|
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"] }]
|
|
95
98
|
}], propDecorators: { headers: [{
|
|
96
99
|
type: Input
|
|
97
100
|
}], rowsInput: [{
|
|
@@ -795,30 +798,30 @@ class SecurityCamera extends SceneComponent {
|
|
|
795
798
|
}
|
|
796
799
|
}
|
|
797
800
|
function vertexShader$1() {
|
|
798
|
-
return `
|
|
799
|
-
uniform mat4 lightMatrix;
|
|
800
|
-
|
|
801
|
-
varying vec4 vLightCoords;
|
|
802
|
-
|
|
803
|
-
void main() {
|
|
804
|
-
// create a UV region [0,1] x [0,1] to represent what the lightMatrix "sees"
|
|
805
|
-
vLightCoords = lightMatrix * modelMatrix * vec4(position, 1.0);
|
|
806
|
-
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
807
|
-
}
|
|
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
|
+
}
|
|
808
811
|
`;
|
|
809
812
|
}
|
|
810
813
|
function fragmentShader$1() {
|
|
811
|
-
return `
|
|
812
|
-
uniform vec3 color;
|
|
813
|
-
|
|
814
|
-
varying vec4 vLightCoords;
|
|
815
|
-
|
|
816
|
-
void main() {
|
|
817
|
-
vec2 lightUV = vLightCoords.xy / vLightCoords.w;
|
|
818
|
-
float inView = float(max(lightUV.x, lightUV.y) <= 1.0 && min(lightUV.x, lightUV.y) >= 0.0 && vLightCoords.z > 0.0);
|
|
819
|
-
|
|
820
|
-
gl_FragColor = vec4(color, min(inView, 0.2));
|
|
821
|
-
}
|
|
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
|
+
}
|
|
822
825
|
`;
|
|
823
826
|
}
|
|
824
827
|
function updateHighlightUniforms$1(projector, uniforms) {
|
|
@@ -1011,30 +1014,30 @@ class ViewFrustumMesh extends SceneComponent {
|
|
|
1011
1014
|
}
|
|
1012
1015
|
}
|
|
1013
1016
|
function vertexShader() {
|
|
1014
|
-
return `
|
|
1015
|
-
uniform mat4 lightMatrix;
|
|
1016
|
-
|
|
1017
|
-
varying vec4 vLightCoords;
|
|
1018
|
-
|
|
1019
|
-
void main() {
|
|
1020
|
-
// create a UV region [0,1] x [0,1] to represent what the lightMatrix "sees"
|
|
1021
|
-
vLightCoords = lightMatrix * modelMatrix * vec4(position, 1.0);
|
|
1022
|
-
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
1023
|
-
}
|
|
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
|
+
}
|
|
1024
1027
|
`;
|
|
1025
1028
|
}
|
|
1026
1029
|
function fragmentShader() {
|
|
1027
|
-
return `
|
|
1028
|
-
uniform vec3 color;
|
|
1029
|
-
|
|
1030
|
-
varying vec4 vLightCoords;
|
|
1031
|
-
|
|
1032
|
-
void main() {
|
|
1033
|
-
vec2 lightUV = vLightCoords.xy / vLightCoords.w;
|
|
1034
|
-
float inView = float(max(lightUV.x, lightUV.y) <= 1.0 && min(lightUV.x, lightUV.y) >= 0.0 && vLightCoords.z > 0.0);
|
|
1035
|
-
|
|
1036
|
-
gl_FragColor = vec4(color, min(inView, 0.2));
|
|
1037
|
-
}
|
|
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
|
+
}
|
|
1038
1041
|
`;
|
|
1039
1042
|
}
|
|
1040
1043
|
function updateHighlightUniforms(projector, uniforms) {
|
|
@@ -2197,32 +2200,32 @@ class PoiService {
|
|
|
2197
2200
|
if (!sweeps || sweeps.length === 0) {
|
|
2198
2201
|
return [];
|
|
2199
2202
|
}
|
|
2200
|
-
const queryStatement = `
|
|
2201
|
-
query ListPOISLight($filter: ModelPOIFilterInput, $limit: Int, $nextToken: String) {
|
|
2202
|
-
listPOIS(filter: $filter, limit: $limit, nextToken: $nextToken) {
|
|
2203
|
-
items {
|
|
2204
|
-
id
|
|
2205
|
-
coordinate
|
|
2206
|
-
specific
|
|
2207
|
-
private
|
|
2208
|
-
global
|
|
2209
|
-
type
|
|
2210
|
-
elementID
|
|
2211
|
-
level
|
|
2212
|
-
role
|
|
2213
|
-
category
|
|
2214
|
-
domainID
|
|
2215
|
-
metadata
|
|
2216
|
-
tagIcon
|
|
2217
|
-
tagDescription
|
|
2218
|
-
tagImageID
|
|
2219
|
-
matterportSweepID
|
|
2220
|
-
createdAt
|
|
2221
|
-
updatedAt
|
|
2222
|
-
}
|
|
2223
|
-
nextToken
|
|
2224
|
-
}
|
|
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
2229
|
`;
|
|
2227
2230
|
const CHUNK_SIZE = 50; // safe margin for an "or" filter expression's practical size
|
|
2228
2231
|
const chunks = [];
|
|
@@ -3027,11 +3030,11 @@ class MatterportPointerService {
|
|
|
3027
3030
|
}
|
|
3028
3031
|
updateCursorDisplay(poseMatterport) {
|
|
3029
3032
|
if (this.textDisplayCursorPositionPanel && poseMatterport) {
|
|
3030
|
-
this.textDisplayCursorPositionPanel.innerHTML = `position:
|
|
3031
|
-
${pointToString(poseMatterport.position)}\n
|
|
3032
|
-
normal:
|
|
3033
|
-
${pointToString(poseMatterport.normal)}\n
|
|
3034
|
-
floorId:
|
|
3033
|
+
this.textDisplayCursorPositionPanel.innerHTML = `position:
|
|
3034
|
+
${pointToString(poseMatterport.position)}\n
|
|
3035
|
+
normal:
|
|
3036
|
+
${pointToString(poseMatterport.normal)}\n
|
|
3037
|
+
floorId:
|
|
3035
3038
|
${poseMatterport.floorId}`;
|
|
3036
3039
|
if (this.getCursorPositionButton) {
|
|
3037
3040
|
this.getCursorPositionButton.style.display = 'none';
|
|
@@ -5181,22 +5184,22 @@ class MatterportService {
|
|
|
5181
5184
|
);
|
|
5182
5185
|
}*/
|
|
5183
5186
|
addCustomCss() {
|
|
5184
|
-
const css = `
|
|
5185
|
-
.footer-ui,
|
|
5186
|
-
.showcase-footer,
|
|
5187
|
-
.mp-nova-btn-group,
|
|
5188
|
-
.footer-divider,
|
|
5189
|
-
.logo-link,
|
|
5190
|
-
.footer-logo,
|
|
5191
|
-
.bottom-ui,
|
|
5192
|
-
img.logo-image,
|
|
5193
|
-
img[class^="logo-image"],
|
|
5194
|
-
img[class*=" logo-image"],
|
|
5195
|
-
[data-testid="showcase-footer"] {
|
|
5196
|
-
opacity: 0 !important;
|
|
5197
|
-
visibility: hidden !important;
|
|
5198
|
-
pointer-events: none !important;
|
|
5199
|
-
}
|
|
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
|
+
}
|
|
5200
5203
|
`;
|
|
5201
5204
|
// 1st method: Via Scene SDK
|
|
5202
5205
|
if (this.sdk?.Scene?.addStyle) {
|
|
@@ -5794,20 +5797,29 @@ function frameFromPoints(a, b, c) {
|
|
|
5794
5797
|
return new THREE.Matrix4().makeBasis(e1, e2, e1.clone().cross(e2));
|
|
5795
5798
|
}
|
|
5796
5799
|
/**
|
|
5797
|
-
* Solves the transform that maps stored tag coordinates onto the loaded model
|
|
5800
|
+
* Solves the transform that maps stored tag coordinates onto the loaded model, from as many matched
|
|
5801
|
+
* tags as there are - up to three.
|
|
5802
|
+
*
|
|
5803
|
+
* Three pairs pin down everything: position, orientation and scale. A model that shares only a
|
|
5804
|
+
* corner with the tags' space may not have three tags to offer, so fewer are accepted, each
|
|
5805
|
+
* settling what it can and assuming the rest:
|
|
5798
5806
|
*
|
|
5799
|
-
*
|
|
5800
|
-
*
|
|
5801
|
-
*
|
|
5802
|
-
* between them free; three resolve it.
|
|
5807
|
+
* - Two pairs: position, heading and scale. The one thing two points cannot say is how the model
|
|
5808
|
+
* is tilted, so both spaces are taken to agree on which way is up.
|
|
5809
|
+
* - One pair: position only. Heading and scale are taken to match already.
|
|
5803
5810
|
*
|
|
5804
|
-
*
|
|
5805
|
-
*
|
|
5811
|
+
* `targetUp` is the model's vertical axis, which the tags' own vertical (+Y) is matched to in those
|
|
5812
|
+
* two cases. Returns null when the points cannot answer the question asked of them - collinear for
|
|
5813
|
+
* three, one above the other for two - because a wrong calibration applied silently is worse than
|
|
5814
|
+
* none.
|
|
5806
5815
|
*/
|
|
5807
|
-
function solveThreeJsAlignment(pairs) {
|
|
5808
|
-
if (!pairs || pairs.length
|
|
5816
|
+
function solveThreeJsAlignment(pairs, targetUp = { x: 0, y: 1, z: 0 }) {
|
|
5817
|
+
if (!pairs || pairs.length === 0) {
|
|
5809
5818
|
return null;
|
|
5810
5819
|
}
|
|
5820
|
+
if (pairs.length < 3) {
|
|
5821
|
+
return solveReducedAlignment(pairs, new THREE.Vector3(targetUp.x, targetUp.y, targetUp.z).normalize());
|
|
5822
|
+
}
|
|
5811
5823
|
const sources = pairs.slice(0, 3).map((pair) => new THREE.Vector3(pair.source.x, pair.source.y, pair.source.z));
|
|
5812
5824
|
const targets = pairs.slice(0, 3).map((pair) => new THREE.Vector3(pair.target.x, pair.target.y, pair.target.z));
|
|
5813
5825
|
const sourceFrame = frameFromPoints(sources[0], sources[1], sources[2]);
|
|
@@ -5848,6 +5860,41 @@ function solveThreeJsAlignment(pairs) {
|
|
|
5848
5860
|
scale,
|
|
5849
5861
|
};
|
|
5850
5862
|
}
|
|
5863
|
+
/** The one- and two-pair cases of solveThreeJsAlignment, which share the "same vertical" assumption. */
|
|
5864
|
+
function solveReducedAlignment(pairs, up) {
|
|
5865
|
+
const sources = pairs.map((pair) => new THREE.Vector3(pair.source.x, pair.source.y, pair.source.z));
|
|
5866
|
+
const targets = pairs.map((pair) => new THREE.Vector3(pair.target.x, pair.target.y, pair.target.z));
|
|
5867
|
+
// Starts from the rotation that only stands the tags' vertical on the model's.
|
|
5868
|
+
const rotation = new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 1, 0), up);
|
|
5869
|
+
let scale = 1;
|
|
5870
|
+
if (pairs.length === 2) {
|
|
5871
|
+
const sourceSpan = sources[1].clone().sub(sources[0]);
|
|
5872
|
+
const targetSpan = targets[1].clone().sub(targets[0]);
|
|
5873
|
+
if (sourceSpan.length() < 1e-9 || targetSpan.length() < 1e-9) {
|
|
5874
|
+
return null;
|
|
5875
|
+
}
|
|
5876
|
+
scale = targetSpan.length() / sourceSpan.length();
|
|
5877
|
+
// Heading is the turn about the vertical that lines the two spans up, so only their horizontal
|
|
5878
|
+
// parts count. Two points one above the other have none, and say nothing about heading.
|
|
5879
|
+
const flatten = (v) => v.clone().sub(up.clone().multiplyScalar(v.dot(up)));
|
|
5880
|
+
const from = flatten(sourceSpan.clone().applyQuaternion(rotation));
|
|
5881
|
+
const to = flatten(targetSpan);
|
|
5882
|
+
if (from.length() < 1e-6 * sourceSpan.length() || to.length() < 1e-6 * targetSpan.length()) {
|
|
5883
|
+
return null;
|
|
5884
|
+
}
|
|
5885
|
+
const heading = Math.atan2(new THREE.Vector3().crossVectors(from, to).dot(up), from.dot(to));
|
|
5886
|
+
rotation.premultiply(new THREE.Quaternion().setFromAxisAngle(up, heading));
|
|
5887
|
+
}
|
|
5888
|
+
// A point lands at t + s*R*p; solved for t at the centroids, as in the three-pair case.
|
|
5889
|
+
const centroid = (points) => points.reduce((sum, v) => sum.add(v), new THREE.Vector3()).multiplyScalar(1 / points.length);
|
|
5890
|
+
const translation = centroid(targets).sub(centroid(sources).applyQuaternion(rotation).multiplyScalar(scale));
|
|
5891
|
+
const euler = new THREE.Euler().setFromQuaternion(rotation);
|
|
5892
|
+
return {
|
|
5893
|
+
translation: { x: translation.x, y: translation.y, z: translation.z },
|
|
5894
|
+
rotation: { x: euler.x, y: euler.y, z: euler.z },
|
|
5895
|
+
scale,
|
|
5896
|
+
};
|
|
5897
|
+
}
|
|
5851
5898
|
class ThreeJsService {
|
|
5852
5899
|
ngZone;
|
|
5853
5900
|
scene;
|
|
@@ -5885,6 +5932,61 @@ class ThreeJsService {
|
|
|
5885
5932
|
pendingTagPlacement = null;
|
|
5886
5933
|
/** Pointer position waiting for a preview pick, coalesced to one per frame. */
|
|
5887
5934
|
pendingPreviewEvent = null;
|
|
5935
|
+
/**
|
|
5936
|
+
* Tags whose position is in this model's own coordinates. A sibling of the tag group that does not
|
|
5937
|
+
* carry the calibration: calibrating a model for the tags it imports must not move the ones that
|
|
5938
|
+
* were placed straight onto it.
|
|
5939
|
+
*/
|
|
5940
|
+
modelTagGroup = new THREE.Group();
|
|
5941
|
+
/**
|
|
5942
|
+
* The capture on screen. A tag placed in a 3D model is recorded with it, which is how the tags
|
|
5943
|
+
* placed in this one are told from those that came from elsewhere.
|
|
5944
|
+
*/
|
|
5945
|
+
currentCaptureID = null;
|
|
5946
|
+
/**
|
|
5947
|
+
* The model's surface as a grid: every cell some part of the surface passes through.
|
|
5948
|
+
*
|
|
5949
|
+
* It answers "is this point near the model?" without visiting the triangles, of which a captured
|
|
5950
|
+
* model has millions - the question is asked for every tag, every time the calibration changes.
|
|
5951
|
+
*/
|
|
5952
|
+
surfaceCells = null;
|
|
5953
|
+
surfaceOrigin = new THREE.Vector3();
|
|
5954
|
+
surfaceCellSize = 1;
|
|
5955
|
+
/**
|
|
5956
|
+
* How far from the model's surface a tag may be and still count as on it, in metres.
|
|
5957
|
+
*
|
|
5958
|
+
* Every tag of the space is offered to every model; this is what decides which ones a model
|
|
5959
|
+
* shows. A tag marks something on a surface, so one that belongs here sits on the surface or
|
|
5960
|
+
* close to it. One that lands deep inside the model, or well away from it, marks something that
|
|
5961
|
+
* is not part of this model. The allowance covers a calibration done by hand and the model not
|
|
5962
|
+
* matching the scan exactly, which together run to a few tens of centimetres.
|
|
5963
|
+
*/
|
|
5964
|
+
static ON_MODEL_METRES = 0.75;
|
|
5965
|
+
/** Cells per axis the grid's keys can address, with room either side for points off the model. */
|
|
5966
|
+
static SURFACE_GRID_SPAN = 1024;
|
|
5967
|
+
static SURFACE_GRID_PAD = 128;
|
|
5968
|
+
/** Own tags moved to keep their place under the last calibration, waiting to be stored. */
|
|
5969
|
+
rebasedTags = [];
|
|
5970
|
+
/** The type filter in force. Null shows every type. */
|
|
5971
|
+
visibleTagTypes = null;
|
|
5972
|
+
measuring = false;
|
|
5973
|
+
/** Points clicked so far, in tag space. */
|
|
5974
|
+
measurePoints = [];
|
|
5975
|
+
/**
|
|
5976
|
+
* Everything drawn for the measurement. A child of the tag group, so its points share the frame
|
|
5977
|
+
* tag coordinates are stored in - which is metres, and what the saved POI has to be in.
|
|
5978
|
+
*/
|
|
5979
|
+
measureGroup = new THREE.Group();
|
|
5980
|
+
/** The segment that follows the pointer from the last point clicked. */
|
|
5981
|
+
measureRubberBand;
|
|
5982
|
+
lastMeasurement = null;
|
|
5983
|
+
/** Half the model's diagonal as placed in the scene, where it is centred on the origin. */
|
|
5984
|
+
modelWorldRadius = 0;
|
|
5985
|
+
/** The outermost group the model and its tags hang from: where "which way is up" is applied. */
|
|
5986
|
+
modelGroup;
|
|
5987
|
+
modelUp = 'y';
|
|
5988
|
+
/** While measuring: how many points and each segment's length. Null when it is abandoned. */
|
|
5989
|
+
measurementProgress$ = new Subject();
|
|
5888
5990
|
/**
|
|
5889
5991
|
* Layer carrying the model alone, so the depth pick can render it without the tags.
|
|
5890
5992
|
*
|
|
@@ -5892,6 +5994,8 @@ class ThreeJsService {
|
|
|
5892
5994
|
* their parent passed, so marking the model's own meshes is enough - no re-parenting.
|
|
5893
5995
|
*/
|
|
5894
5996
|
static PICK_LAYER = 1;
|
|
5997
|
+
/** The app's highlight colour, so a measurement reads as the active thing on screen. */
|
|
5998
|
+
static MEASURE_COLOR = '#FFB103';
|
|
5895
5999
|
/** Cache Storage bucket holding downloaded models, and how many to keep. */
|
|
5896
6000
|
static MODEL_CACHE = 'smarterplan-3d-models';
|
|
5897
6001
|
static MODEL_CACHE_SIZE = 3;
|
|
@@ -5908,6 +6012,8 @@ class ThreeJsService {
|
|
|
5908
6012
|
aligning = false;
|
|
5909
6013
|
alignmentPairs = [];
|
|
5910
6014
|
alignmentSource = null;
|
|
6015
|
+
/** The tag `alignmentSource` was read from, kept so the same tag is not matched twice. */
|
|
6016
|
+
alignmentSourceTagId;
|
|
5911
6017
|
/** Drives the alignment walkthrough's UI: how many pairs are done and what it wants next. */
|
|
5912
6018
|
alignmentProgress$ = new Subject();
|
|
5913
6019
|
/** In-flight camera move started by goToTag, advanced by the render loop. */
|
|
@@ -6023,6 +6129,8 @@ class ThreeJsService {
|
|
|
6023
6129
|
// new model and show the previous visit's tags on it.
|
|
6024
6130
|
this.discardCurrentLoad();
|
|
6025
6131
|
this.clearTags();
|
|
6132
|
+
this.measuring = false;
|
|
6133
|
+
this.clearMeasurement();
|
|
6026
6134
|
this.cameraTween = null;
|
|
6027
6135
|
this.modelRoot = undefined;
|
|
6028
6136
|
// 1. Setup Scene
|
|
@@ -6130,7 +6238,7 @@ class ThreeJsService {
|
|
|
6130
6238
|
* and stays indexed, so far less data reaches the GPU. KTX2 textures are supported too: they
|
|
6131
6239
|
* stay compressed in VRAM instead of being decoded to raw pixels like JPEG/PNG.
|
|
6132
6240
|
*/
|
|
6133
|
-
loadGLTFModel(url, cameraPosition, rotation, cacheKey) {
|
|
6241
|
+
loadGLTFModel(url, cameraPosition, rotation, cacheKey, up) {
|
|
6134
6242
|
const loader = new GLTFLoader();
|
|
6135
6243
|
// KTX2 needs the renderer to report which compressed formats this GPU accepts, so this has to
|
|
6136
6244
|
// happen after init(). Guarded so a model without KTX2 textures still loads if it's missing.
|
|
@@ -6156,7 +6264,7 @@ class ThreeJsService {
|
|
|
6156
6264
|
this.forceDoubleSidedMaterials(gltf.scene);
|
|
6157
6265
|
this.ensureVertexNormals(gltf.scene);
|
|
6158
6266
|
this.tuneTextureSampling(gltf.scene);
|
|
6159
|
-
this.placeLoadedObjectInScene(gltf.scene, cameraPosition, rotation);
|
|
6267
|
+
this.placeLoadedObjectInScene(gltf.scene, cameraPosition, rotation, up);
|
|
6160
6268
|
this.modelLoading$.next(false);
|
|
6161
6269
|
};
|
|
6162
6270
|
const onError = (error) => {
|
|
@@ -6629,7 +6737,7 @@ class ThreeJsService {
|
|
|
6629
6737
|
});
|
|
6630
6738
|
}
|
|
6631
6739
|
/** Centers, scales and adds a loaded object to the scene, then frames the camera on it. */
|
|
6632
|
-
placeLoadedObjectInScene(object, cameraPosition, rotation) {
|
|
6740
|
+
placeLoadedObjectInScene(object, cameraPosition, rotation, up) {
|
|
6633
6741
|
const box = new THREE.Box3().setFromObject(object);
|
|
6634
6742
|
const center = box.getCenter(new THREE.Vector3());
|
|
6635
6743
|
const size = box.getSize(new THREE.Vector3());
|
|
@@ -6644,12 +6752,25 @@ class ThreeJsService {
|
|
|
6644
6752
|
if (rotation) {
|
|
6645
6753
|
object.rotation.set(rotation.x, rotation.y, rotation.z);
|
|
6646
6754
|
}
|
|
6755
|
+
// Which way is up. A choice someone made by hand always wins; otherwise, unless the model came
|
|
6756
|
+
// with an orientation of its own, it is worked out from the geometry. Applied to the outer
|
|
6757
|
+
// group, above both the model and the tags: they turn together, and every coordinate already
|
|
6758
|
+
// stored - tag positions, the calibration - keeps meaning what it meant.
|
|
6759
|
+
this.modelGroup = group;
|
|
6760
|
+
this.modelUp = up ?? (rotation ? 'y' : this.detectModelUp(object, size));
|
|
6761
|
+
group.quaternion.copy(this.uprightRotation(this.modelUp));
|
|
6647
6762
|
this.modelRoot = object;
|
|
6763
|
+
this.modelWorldRadius = (size.length() / 2) * scale;
|
|
6764
|
+
// The box above was measured before any rotation from the metadata was applied; with one, it
|
|
6765
|
+
// has to be measured again to be the model as it actually sits.
|
|
6766
|
+
this.buildSurfaceGrid(object, rotation ? new THREE.Box3().setFromObject(object) : box);
|
|
6767
|
+
this.refreshTagVisibility();
|
|
6648
6768
|
// Marks the model for the depth pick, which renders this layer alone - so tags and the ghost
|
|
6649
6769
|
// itself cannot end up being what the cursor reports as the surface under it.
|
|
6650
6770
|
object.traverse((child) => child.layers.enable(ThreeJsService.PICK_LAYER));
|
|
6651
6771
|
centered.add(object);
|
|
6652
6772
|
centered.add(this.tagGroup);
|
|
6773
|
+
centered.add(this.modelTagGroup);
|
|
6653
6774
|
group.add(centered);
|
|
6654
6775
|
group.scale.setScalar(scale);
|
|
6655
6776
|
this.scene.add(group);
|
|
@@ -6665,6 +6786,98 @@ class ThreeJsService {
|
|
|
6665
6786
|
this.controls.target.set(0, 0, 0);
|
|
6666
6787
|
this.controls.update();
|
|
6667
6788
|
}
|
|
6789
|
+
/**
|
|
6790
|
+
* Which of the model's axes is its vertical: 'y' when it already stands upright, or when it is
|
|
6791
|
+
* not clear enough to act on.
|
|
6792
|
+
*
|
|
6793
|
+
* Models arrive in two conventions: Y up (glTF, and what this viewer assumes) and Z up (most CAD
|
|
6794
|
+
* and scanning exports). A Z-up model loads lying on its side. Two independent cues say which
|
|
6795
|
+
* axis is the vertical one, and both have to agree before anything is turned:
|
|
6796
|
+
*
|
|
6797
|
+
* - Surface area. Floors and ceilings all face along the vertical, while walls are shared out
|
|
6798
|
+
* between the two horizontal axes, so the vertical usually collects the most area.
|
|
6799
|
+
* - Extent. A building is normally wider than it is tall, so the vertical is its shortest side.
|
|
6800
|
+
*
|
|
6801
|
+
* Either alone is wrong often enough to matter - a long corridor has more wall than floor, a
|
|
6802
|
+
* tower is taller than it is wide - and turning a model that was fine is worse than leaving one
|
|
6803
|
+
* that was not. A model that already has Y as its vertical is never touched.
|
|
6804
|
+
*/
|
|
6805
|
+
detectModelUp(object, size) {
|
|
6806
|
+
const axes = ['x', 'y', 'z'];
|
|
6807
|
+
// Projected area facing each way along each axis: [positive, negative].
|
|
6808
|
+
const area = { x: [0, 0], y: [0, 0], z: [0, 0] };
|
|
6809
|
+
let triangleCount = 0;
|
|
6810
|
+
object.updateMatrixWorld(true);
|
|
6811
|
+
object.traverse((child) => {
|
|
6812
|
+
const position = child.isMesh && child.geometry?.attributes?.position;
|
|
6813
|
+
if (position) {
|
|
6814
|
+
triangleCount += (child.geometry.index ? child.geometry.index.count : position.count) / 3;
|
|
6815
|
+
}
|
|
6816
|
+
});
|
|
6817
|
+
if (triangleCount === 0) {
|
|
6818
|
+
return 'y';
|
|
6819
|
+
}
|
|
6820
|
+
// A sample is plenty to see where the area lies, and keeps this cheap on models with millions
|
|
6821
|
+
// of triangles.
|
|
6822
|
+
const stride = Math.max(1, Math.ceil(triangleCount / 200000));
|
|
6823
|
+
const a = new THREE.Vector3();
|
|
6824
|
+
const b = new THREE.Vector3();
|
|
6825
|
+
const c = new THREE.Vector3();
|
|
6826
|
+
const normal = new THREE.Vector3();
|
|
6827
|
+
object.traverse((child) => {
|
|
6828
|
+
const position = child.isMesh && child.geometry?.attributes?.position;
|
|
6829
|
+
if (!position) {
|
|
6830
|
+
return;
|
|
6831
|
+
}
|
|
6832
|
+
const index = child.geometry.index;
|
|
6833
|
+
const count = (index ? index.count : position.count) / 3;
|
|
6834
|
+
for (let triangle = 0; triangle < count; triangle += stride) {
|
|
6835
|
+
const offset = triangle * 3;
|
|
6836
|
+
a.fromBufferAttribute(position, index ? index.getX(offset) : offset).applyMatrix4(child.matrixWorld);
|
|
6837
|
+
b.fromBufferAttribute(position, index ? index.getX(offset + 1) : offset + 1).applyMatrix4(child.matrixWorld);
|
|
6838
|
+
c.fromBufferAttribute(position, index ? index.getX(offset + 2) : offset + 2).applyMatrix4(child.matrixWorld);
|
|
6839
|
+
// The cross product's length is twice the triangle's area, so its components are the
|
|
6840
|
+
// area the triangle shows along each axis.
|
|
6841
|
+
normal.crossVectors(b.sub(a), c.sub(a));
|
|
6842
|
+
axes.forEach((axis) => {
|
|
6843
|
+
area[axis][normal[axis] >= 0 ? 0 : 1] += Math.abs(normal[axis]);
|
|
6844
|
+
});
|
|
6845
|
+
}
|
|
6846
|
+
});
|
|
6847
|
+
const total = { x: area.x[0] + area.x[1], y: area.y[0] + area.y[1], z: area.z[0] + area.z[1] };
|
|
6848
|
+
const byArea = axes.reduce((best, axis) => (total[axis] > total[best] ? axis : best), 'y');
|
|
6849
|
+
const byExtent = axes.reduce((best, axis) => (size[axis] < size[best] ? axis : best), 'y');
|
|
6850
|
+
if (byArea === 'y' || byArea !== byExtent || total[byArea] < 1.25 * total.y) {
|
|
6851
|
+
return 'y';
|
|
6852
|
+
}
|
|
6853
|
+
// Which way along that axis is up. Roofs and ground make an outdoor model face mostly upward,
|
|
6854
|
+
// which settles it; indoors floors and ceilings balance out, and the convention - positive is
|
|
6855
|
+
// up - is the better guess.
|
|
6856
|
+
const [positive, negative] = area[byArea];
|
|
6857
|
+
const up = `${negative > 0.65 * (positive + negative) ? '-' : ''}${byArea}`;
|
|
6858
|
+
console.info(`ThreeJsService: this model's vertical axis looks like ${up}, not y - standing it upright.`);
|
|
6859
|
+
return up;
|
|
6860
|
+
}
|
|
6861
|
+
/** The rotation that brings the given model axis to point up. */
|
|
6862
|
+
uprightRotation(up) {
|
|
6863
|
+
const axis = new THREE.Vector3();
|
|
6864
|
+
axis[up.replace('-', '')] = up.startsWith('-') ? -1 : 1;
|
|
6865
|
+
return new THREE.Quaternion().setFromUnitVectors(axis, new THREE.Vector3(0, 1, 0));
|
|
6866
|
+
}
|
|
6867
|
+
/** Which of the model's axes is currently treated as up, whether chosen or worked out. */
|
|
6868
|
+
getModelUp() {
|
|
6869
|
+
return this.modelUp;
|
|
6870
|
+
}
|
|
6871
|
+
/**
|
|
6872
|
+
* Turns the model so the given axis points up, at once.
|
|
6873
|
+
*
|
|
6874
|
+
* The model and its tags turn as one, so nothing stored has to be recomputed. The caller is
|
|
6875
|
+
* expected to store the choice, or the next load goes back to working it out.
|
|
6876
|
+
*/
|
|
6877
|
+
setModelUp(up) {
|
|
6878
|
+
this.modelUp = up;
|
|
6879
|
+
this.modelGroup?.quaternion.copy(this.uprightRotation(up));
|
|
6880
|
+
}
|
|
6668
6881
|
/**
|
|
6669
6882
|
* Keeps the drawing buffer and camera matched to the canvas as it is resized.
|
|
6670
6883
|
*
|
|
@@ -6690,6 +6903,7 @@ class ThreeJsService {
|
|
|
6690
6903
|
this.camera.aspect = width / height;
|
|
6691
6904
|
this.camera.updateProjectionMatrix();
|
|
6692
6905
|
this.renderer.setSize(width, height);
|
|
6906
|
+
this.updateMeasureLineResolution();
|
|
6693
6907
|
});
|
|
6694
6908
|
this.canvasResizeObserver.observe(container);
|
|
6695
6909
|
}
|
|
@@ -6701,9 +6915,16 @@ class ThreeJsService {
|
|
|
6701
6915
|
* applied until solveAlignmentFromPairs() succeeds.
|
|
6702
6916
|
*/
|
|
6703
6917
|
startTagAlignment() {
|
|
6918
|
+
// One pointer mode at a time, the same rule startMeasuring() applies the other way round.
|
|
6919
|
+
if (this.measuring) {
|
|
6920
|
+
this.cancelMeasuring();
|
|
6921
|
+
}
|
|
6704
6922
|
this.aligning = true;
|
|
6705
6923
|
this.alignmentPairs = [];
|
|
6706
6924
|
this.alignmentSource = null;
|
|
6925
|
+
// While calibrating, the tags from elsewhere are all shown - including the ones off the model,
|
|
6926
|
+
// which are exactly the ones a calibration is for.
|
|
6927
|
+
this.refreshTagVisibility();
|
|
6707
6928
|
if (this.renderer) {
|
|
6708
6929
|
this.renderer.domElement.style.cursor = 'crosshair';
|
|
6709
6930
|
}
|
|
@@ -6716,6 +6937,7 @@ class ThreeJsService {
|
|
|
6716
6937
|
this.alignmentSource = null;
|
|
6717
6938
|
this.pendingPreviewEvent = null;
|
|
6718
6939
|
this.removePlacementPreview();
|
|
6940
|
+
this.refreshTagVisibility();
|
|
6719
6941
|
if (this.renderer) {
|
|
6720
6942
|
this.renderer.domElement.style.cursor = 'default';
|
|
6721
6943
|
}
|
|
@@ -6735,12 +6957,22 @@ class ThreeJsService {
|
|
|
6735
6957
|
* first half of a pair, which is a click on a tag, not on the model.
|
|
6736
6958
|
*/
|
|
6737
6959
|
isPreviewingPlacement() {
|
|
6738
|
-
return this.placingTag || (this.aligning && !!this.alignmentSource);
|
|
6960
|
+
return this.placingTag || this.measuring || (this.aligning && !!this.alignmentSource);
|
|
6739
6961
|
}
|
|
6740
6962
|
/** Whether a tag is currently registered for this element. */
|
|
6741
6963
|
hasTag(elementId) {
|
|
6742
6964
|
return this.tagSprites.has(elementId);
|
|
6743
6965
|
}
|
|
6966
|
+
/** How many tags are on show - the ones a person could actually click. */
|
|
6967
|
+
getTagCount() {
|
|
6968
|
+
let visible = 0;
|
|
6969
|
+
this.tagSprites.forEach((sprite) => {
|
|
6970
|
+
if (sprite.visible) {
|
|
6971
|
+
visible += 1;
|
|
6972
|
+
}
|
|
6973
|
+
});
|
|
6974
|
+
return visible;
|
|
6975
|
+
}
|
|
6744
6976
|
/**
|
|
6745
6977
|
* Solves and applies the alignment collected so far, returning it so the caller can persist it.
|
|
6746
6978
|
*
|
|
@@ -6748,35 +6980,105 @@ class ThreeJsService {
|
|
|
6748
6980
|
* the walkthrough stays open for the person to pick a better third point.
|
|
6749
6981
|
*/
|
|
6750
6982
|
solveAlignmentFromPairs() {
|
|
6751
|
-
const
|
|
6983
|
+
const up = new THREE.Vector3();
|
|
6984
|
+
up[this.modelUp.replace('-', '')] = this.modelUp.startsWith('-') ? -1 : 1;
|
|
6985
|
+
const alignment = solveThreeJsAlignment(this.alignmentPairs, up);
|
|
6752
6986
|
if (!alignment) {
|
|
6753
6987
|
return null;
|
|
6754
6988
|
}
|
|
6755
|
-
|
|
6989
|
+
// Out of calibrating mode first: applying the result re-decides which tags show, and from here
|
|
6990
|
+
// on that is the ones on the model again, not all of them.
|
|
6756
6991
|
this.aligning = false;
|
|
6992
|
+
this.applyAlignmentKeepingOwnTags(alignment);
|
|
6757
6993
|
this.pendingPreviewEvent = null;
|
|
6758
6994
|
this.removePlacementPreview();
|
|
6759
6995
|
this.renderer.domElement.style.cursor = 'default';
|
|
6760
6996
|
this.alignmentProgress$.next({ pairs: this.alignmentPairs.length, awaiting: null });
|
|
6761
6997
|
return alignment;
|
|
6762
6998
|
}
|
|
6999
|
+
/**
|
|
7000
|
+
* Applies a new calibration while keeping every tag placed in this model exactly where it is.
|
|
7001
|
+
*
|
|
7002
|
+
* A calibration is worked out for the tags that came from elsewhere. The model's own tags were put
|
|
7003
|
+
* where they are by someone clicking the model, so they must not follow it. Their stored
|
|
7004
|
+
* coordinates are rewritten instead: into the shared space as the new calibration defines it.
|
|
7005
|
+
* That is also what makes them comparable with other visits from here on - a tag placed before
|
|
7006
|
+
* the model was calibrated was in the model's own coordinates, visible nowhere else.
|
|
7007
|
+
*
|
|
7008
|
+
* The rewritten tags are kept for the caller to store; see takeRebasedTags().
|
|
7009
|
+
*/
|
|
7010
|
+
applyAlignmentKeepingOwnTags(alignment) {
|
|
7011
|
+
const own = [...this.tagSprites.values()].filter((sprite) => this.isOwnedByVisit(sprite));
|
|
7012
|
+
// Where each one is now, in the space the model sits in, and how its parent was turned.
|
|
7013
|
+
const placed = own.map((sprite) => {
|
|
7014
|
+
sprite.parent?.updateMatrix();
|
|
7015
|
+
return {
|
|
7016
|
+
sprite,
|
|
7017
|
+
position: sprite.position.clone().applyMatrix4(sprite.parent?.matrix ?? new THREE.Matrix4()),
|
|
7018
|
+
turn: sprite.parent?.quaternion.clone() ?? new THREE.Quaternion(),
|
|
7019
|
+
};
|
|
7020
|
+
});
|
|
7021
|
+
this.setTagAlignment(alignment);
|
|
7022
|
+
const inverse = this.tagGroup.matrix.clone().invert();
|
|
7023
|
+
const unturn = this.tagGroup.quaternion.clone().invert();
|
|
7024
|
+
this.rebasedTags = placed.map(({ sprite, position, turn }) => {
|
|
7025
|
+
sprite.position.copy(position.applyMatrix4(inverse));
|
|
7026
|
+
sprite.userData.frame = null;
|
|
7027
|
+
this.parentTag(sprite);
|
|
7028
|
+
this.scaleTagForModel(sprite);
|
|
7029
|
+
const rotation = unturn.clone().multiply(turn);
|
|
7030
|
+
return {
|
|
7031
|
+
id: sprite.name,
|
|
7032
|
+
position: { x: sprite.position.x, y: sprite.position.y, z: sprite.position.z },
|
|
7033
|
+
rotateDirection: ({ x, y, z }) => {
|
|
7034
|
+
const turned = new THREE.Vector3(x, y, z).applyQuaternion(rotation);
|
|
7035
|
+
return { x: turned.x, y: turned.y, z: turned.z };
|
|
7036
|
+
},
|
|
7037
|
+
};
|
|
7038
|
+
});
|
|
7039
|
+
this.refreshTagVisibility();
|
|
7040
|
+
}
|
|
7041
|
+
/** Hands over the tags the last calibration rewrote, once, for them to be stored. */
|
|
7042
|
+
takeRebasedTags() {
|
|
7043
|
+
const rebased = this.rebasedTags;
|
|
7044
|
+
this.rebasedTags = [];
|
|
7045
|
+
return rebased;
|
|
7046
|
+
}
|
|
7047
|
+
/**
|
|
7048
|
+
* Chooses the tag for the pair being matched by id, instead of by clicking it.
|
|
7049
|
+
*
|
|
7050
|
+
* Clicking a tag supposes it can be found. On a model that has never been calibrated the tags may
|
|
7051
|
+
* sit far from it, or all on top of one another; a list has neither problem.
|
|
7052
|
+
*/
|
|
7053
|
+
selectAlignmentTag(elementId) {
|
|
7054
|
+
const sprite = this.tagSprites.get(elementId);
|
|
7055
|
+
if (!this.aligning || this.alignmentSource || !sprite || !this.calibrationCandidates().includes(sprite)) {
|
|
7056
|
+
return false;
|
|
7057
|
+
}
|
|
7058
|
+
this.setAlignmentSource(sprite);
|
|
7059
|
+
return true;
|
|
7060
|
+
}
|
|
7061
|
+
setAlignmentSource(sprite) {
|
|
7062
|
+
// The sprite's own position is the stored coordinate, untouched by any alignment already
|
|
7063
|
+
// applied - that transform lives on the group, not on the sprite.
|
|
7064
|
+
const stored = sprite.position;
|
|
7065
|
+
this.alignmentSource = { x: stored.x, y: stored.y, z: stored.z };
|
|
7066
|
+
this.alignmentSourceTagId = sprite.name;
|
|
7067
|
+
this.alignmentProgress$.next({
|
|
7068
|
+
pairs: this.alignmentPairs.length,
|
|
7069
|
+
awaiting: 'point',
|
|
7070
|
+
tagId: sprite.name,
|
|
7071
|
+
});
|
|
7072
|
+
}
|
|
6763
7073
|
/** Handles a click while the alignment walkthrough is running. */
|
|
6764
7074
|
handleAlignmentClick(event) {
|
|
6765
7075
|
if (!this.alignmentSource) {
|
|
6766
7076
|
// First half of a pair: which tag are we talking about?
|
|
6767
|
-
const hit = this.raycaster.intersectObjects(this.
|
|
7077
|
+
const hit = this.raycaster.intersectObjects(this.calibrationCandidates(), false)[0];
|
|
6768
7078
|
if (!hit) {
|
|
6769
7079
|
return;
|
|
6770
7080
|
}
|
|
6771
|
-
|
|
6772
|
-
// applied - that transform lives on the group, not on the sprite.
|
|
6773
|
-
const stored = hit.object.position;
|
|
6774
|
-
this.alignmentSource = { x: stored.x, y: stored.y, z: stored.z };
|
|
6775
|
-
this.alignmentProgress$.next({
|
|
6776
|
-
pairs: this.alignmentPairs.length,
|
|
6777
|
-
awaiting: 'point',
|
|
6778
|
-
tagId: hit.object.name,
|
|
6779
|
-
});
|
|
7081
|
+
this.setAlignmentSource(hit.object);
|
|
6780
7082
|
return;
|
|
6781
7083
|
}
|
|
6782
7084
|
// Second half: where that tag actually belongs on the model.
|
|
@@ -6784,15 +7086,249 @@ class ThreeJsService {
|
|
|
6784
7086
|
if (!target) {
|
|
6785
7087
|
return;
|
|
6786
7088
|
}
|
|
6787
|
-
this.alignmentPairs.push({ source: this.alignmentSource, target });
|
|
7089
|
+
this.alignmentPairs.push({ tagId: this.alignmentSourceTagId, source: this.alignmentSource, target });
|
|
6788
7090
|
this.alignmentSource = null;
|
|
6789
7091
|
// The ghost belongs to the click that just happened; the next half is a click on a tag.
|
|
6790
7092
|
this.pendingPreviewEvent = null;
|
|
6791
7093
|
this.removePlacementPreview();
|
|
6792
7094
|
this.alignmentProgress$.next({
|
|
6793
7095
|
pairs: this.alignmentPairs.length,
|
|
6794
|
-
|
|
7096
|
+
// Done at three, or sooner when there are no more tags to match: fewer pairs still calibrate,
|
|
7097
|
+
// only less completely.
|
|
7098
|
+
awaiting: this.alignmentPairs.length >= 3 || this.calibrationCandidates().length === 0 ? null : 'tag',
|
|
7099
|
+
});
|
|
7100
|
+
}
|
|
7101
|
+
/**
|
|
7102
|
+
* Starts a measurement: every click on the model adds a point, and each pair of consecutive
|
|
7103
|
+
* points is a measured segment.
|
|
7104
|
+
*
|
|
7105
|
+
* It ends through finishMeasuring() or cancelMeasuring() rather than on its own after two points,
|
|
7106
|
+
* because a measurement may have several segments - the same as Matterport's, whose results are
|
|
7107
|
+
* stored as a list of distances.
|
|
7108
|
+
*/
|
|
7109
|
+
startMeasuring() {
|
|
7110
|
+
// One pointer mode at a time: a click cannot be both a tag position and a measured point.
|
|
7111
|
+
this.cancelTagPlacement();
|
|
7112
|
+
if (this.aligning) {
|
|
7113
|
+
this.cancelTagAlignment();
|
|
7114
|
+
}
|
|
7115
|
+
this.clearMeasurement();
|
|
7116
|
+
if (this.measureGroup.parent !== this.tagGroup) {
|
|
7117
|
+
this.tagGroup.add(this.measureGroup);
|
|
7118
|
+
}
|
|
7119
|
+
this.measuring = true;
|
|
7120
|
+
if (this.renderer) {
|
|
7121
|
+
this.renderer.domElement.style.cursor = 'crosshair';
|
|
7122
|
+
}
|
|
7123
|
+
this.measurementProgress$.next({ points: 0, distances: [] });
|
|
7124
|
+
}
|
|
7125
|
+
/**
|
|
7126
|
+
* Abandons the measurement and removes what was drawn.
|
|
7127
|
+
*
|
|
7128
|
+
* Also what clears a finished measurement off the model once its form is saved or closed, which
|
|
7129
|
+
* is why it only reports an abandonment when one was actually in progress.
|
|
7130
|
+
*/
|
|
7131
|
+
cancelMeasuring() {
|
|
7132
|
+
const wasMeasuring = this.measuring;
|
|
7133
|
+
this.measuring = false;
|
|
7134
|
+
this.pendingPreviewEvent = null;
|
|
7135
|
+
this.removePlacementPreview();
|
|
7136
|
+
this.clearMeasurement();
|
|
7137
|
+
// Forgotten here so that a form opened later cannot pick up a measurement already dealt with.
|
|
7138
|
+
this.lastMeasurement = null;
|
|
7139
|
+
if (this.renderer) {
|
|
7140
|
+
this.renderer.domElement.style.cursor = 'default';
|
|
7141
|
+
}
|
|
7142
|
+
if (wasMeasuring) {
|
|
7143
|
+
this.measurementProgress$.next(null);
|
|
7144
|
+
}
|
|
7145
|
+
}
|
|
7146
|
+
/**
|
|
7147
|
+
* Ends the measurement and returns it, or null when there are not yet two points to measure
|
|
7148
|
+
* between - in which case measuring simply carries on.
|
|
7149
|
+
*
|
|
7150
|
+
* The drawing stays on the model so it is still visible while the person names the measurement;
|
|
7151
|
+
* cancelMeasuring() takes it away afterwards.
|
|
7152
|
+
*/
|
|
7153
|
+
finishMeasuring() {
|
|
7154
|
+
if (!this.measuring || this.measurePoints.length < 2) {
|
|
7155
|
+
return null;
|
|
7156
|
+
}
|
|
7157
|
+
this.measuring = false;
|
|
7158
|
+
this.pendingPreviewEvent = null;
|
|
7159
|
+
this.removePlacementPreview();
|
|
7160
|
+
this.updateMeasureRubberBand(null);
|
|
7161
|
+
this.renderer.domElement.style.cursor = 'default';
|
|
7162
|
+
this.lastMeasurement = {
|
|
7163
|
+
points: this.measurePoints.map((point) => ({ x: point.x, y: point.y, z: point.z })),
|
|
7164
|
+
distances: this.measureDistances(),
|
|
7165
|
+
screenshot: this.captureMeasurementScreenshot(),
|
|
7166
|
+
};
|
|
7167
|
+
return this.lastMeasurement;
|
|
7168
|
+
}
|
|
7169
|
+
isMeasuring() {
|
|
7170
|
+
return this.measuring;
|
|
7171
|
+
}
|
|
7172
|
+
/** The measurement finishMeasuring() last produced, for the form that saves it. */
|
|
7173
|
+
getLastMeasurement() {
|
|
7174
|
+
return this.lastMeasurement;
|
|
7175
|
+
}
|
|
7176
|
+
/** Metres between consecutive points. Tag space is in metres, so no conversion is involved. */
|
|
7177
|
+
measureDistances() {
|
|
7178
|
+
const distances = [];
|
|
7179
|
+
for (let index = 1; index < this.measurePoints.length; index += 1) {
|
|
7180
|
+
const distance = this.measurePoints[index - 1].distanceTo(this.measurePoints[index]);
|
|
7181
|
+
// Two decimals, the precision Matterport's own measurements are stored with.
|
|
7182
|
+
distances.push(Math.round(distance * 100) / 100);
|
|
7183
|
+
}
|
|
7184
|
+
return distances;
|
|
7185
|
+
}
|
|
7186
|
+
addMeasurePoint(point) {
|
|
7187
|
+
const previous = this.measurePoints[this.measurePoints.length - 1];
|
|
7188
|
+
this.measurePoints.push(point);
|
|
7189
|
+
const marker = new THREE.Sprite(new THREE.SpriteMaterial({
|
|
7190
|
+
map: this.createTagTexture(ThreeJsService.MEASURE_COLOR),
|
|
7191
|
+
depthTest: false,
|
|
7192
|
+
transparent: true,
|
|
7193
|
+
// Sized against the viewport, not the model: measuring is done zoomed right in, where
|
|
7194
|
+
// anything sized in metres would cover the very thing being measured.
|
|
7195
|
+
sizeAttenuation: false,
|
|
7196
|
+
}));
|
|
7197
|
+
marker.scale.set(0.022, 0.022, 1);
|
|
7198
|
+
marker.position.copy(point);
|
|
7199
|
+
marker.renderOrder = 1001;
|
|
7200
|
+
this.measureGroup.add(marker);
|
|
7201
|
+
if (previous) {
|
|
7202
|
+
this.measureGroup.add(this.createMeasureLine(previous, point));
|
|
7203
|
+
const distance = Math.round(previous.distanceTo(point) * 100) / 100;
|
|
7204
|
+
const label = new THREE.Sprite(new THREE.SpriteMaterial({
|
|
7205
|
+
map: this.createMeasureLabelTexture(`${distance.toFixed(2)} m`),
|
|
7206
|
+
depthTest: false,
|
|
7207
|
+
transparent: true,
|
|
7208
|
+
sizeAttenuation: false,
|
|
7209
|
+
}));
|
|
7210
|
+
label.scale.set(0.15, 0.05, 1);
|
|
7211
|
+
label.position.copy(previous).add(point).multiplyScalar(0.5);
|
|
7212
|
+
label.renderOrder = 1002;
|
|
7213
|
+
this.measureGroup.add(label);
|
|
7214
|
+
}
|
|
7215
|
+
this.measurementProgress$.next({
|
|
7216
|
+
points: this.measurePoints.length,
|
|
7217
|
+
distances: this.measureDistances(),
|
|
7218
|
+
});
|
|
7219
|
+
}
|
|
7220
|
+
/**
|
|
7221
|
+
* A segment with a width in pixels.
|
|
7222
|
+
*
|
|
7223
|
+
* WebGL's own lines are one pixel wide whatever is asked for, which all but disappears over a
|
|
7224
|
+
* photographic texture. This kind is drawn as screen-facing geometry, so the width holds.
|
|
7225
|
+
*/
|
|
7226
|
+
createMeasureLine(from, to) {
|
|
7227
|
+
const geometry = new LineGeometry();
|
|
7228
|
+
geometry.setPositions([from.x, from.y, from.z, to.x, to.y, to.z]);
|
|
7229
|
+
const material = new LineMaterial({
|
|
7230
|
+
color: new THREE.Color(ThreeJsService.MEASURE_COLOR).getHex(),
|
|
7231
|
+
linewidth: 3,
|
|
7232
|
+
depthTest: false,
|
|
7233
|
+
transparent: true,
|
|
7234
|
+
});
|
|
7235
|
+
const size = this.renderer.getSize(new THREE.Vector2());
|
|
7236
|
+
material.resolution.set(size.x, size.y);
|
|
7237
|
+
const line = new Line2(geometry, material);
|
|
7238
|
+
line.renderOrder = 1000;
|
|
7239
|
+
return line;
|
|
7240
|
+
}
|
|
7241
|
+
/** Line widths are in pixels, which the material can only honour if it knows the canvas size. */
|
|
7242
|
+
updateMeasureLineResolution() {
|
|
7243
|
+
if (!this.renderer) {
|
|
7244
|
+
return;
|
|
7245
|
+
}
|
|
7246
|
+
const size = this.renderer.getSize(new THREE.Vector2());
|
|
7247
|
+
this.measureGroup.children.forEach((child) => {
|
|
7248
|
+
if (child.material?.resolution) {
|
|
7249
|
+
child.material.resolution.set(size.x, size.y);
|
|
7250
|
+
}
|
|
7251
|
+
});
|
|
7252
|
+
}
|
|
7253
|
+
/** Stretches a segment from the last point to the pointer, or hides it when given null. */
|
|
7254
|
+
updateMeasureRubberBand(target) {
|
|
7255
|
+
const last = this.measurePoints[this.measurePoints.length - 1];
|
|
7256
|
+
if (!this.measuring || !last || !target) {
|
|
7257
|
+
if (this.measureRubberBand) {
|
|
7258
|
+
this.measureRubberBand.visible = false;
|
|
7259
|
+
}
|
|
7260
|
+
return;
|
|
7261
|
+
}
|
|
7262
|
+
if (!this.measureRubberBand) {
|
|
7263
|
+
this.measureRubberBand = this.createMeasureLine(last, target);
|
|
7264
|
+
this.measureRubberBand.material.opacity = 0.6;
|
|
7265
|
+
this.measureGroup.add(this.measureRubberBand);
|
|
7266
|
+
}
|
|
7267
|
+
this.measureRubberBand.geometry.setPositions([
|
|
7268
|
+
last.x, last.y, last.z,
|
|
7269
|
+
target.x, target.y, target.z,
|
|
7270
|
+
]);
|
|
7271
|
+
this.measureRubberBand.visible = true;
|
|
7272
|
+
}
|
|
7273
|
+
createMeasureLabelTexture(text) {
|
|
7274
|
+
const canvas = document.createElement('canvas');
|
|
7275
|
+
canvas.width = 384;
|
|
7276
|
+
canvas.height = 128;
|
|
7277
|
+
const context = canvas.getContext('2d');
|
|
7278
|
+
const radius = 28;
|
|
7279
|
+
context.fillStyle = 'rgba(0, 0, 0, 0.78)';
|
|
7280
|
+
context.beginPath();
|
|
7281
|
+
context.moveTo(radius, 8);
|
|
7282
|
+
context.arcTo(canvas.width - 8, 8, canvas.width - 8, canvas.height - 8, radius);
|
|
7283
|
+
context.arcTo(canvas.width - 8, canvas.height - 8, 8, canvas.height - 8, radius);
|
|
7284
|
+
context.arcTo(8, canvas.height - 8, 8, 8, radius);
|
|
7285
|
+
context.arcTo(8, 8, canvas.width - 8, 8, radius);
|
|
7286
|
+
context.closePath();
|
|
7287
|
+
context.fill();
|
|
7288
|
+
context.fillStyle = '#FFFFFF';
|
|
7289
|
+
context.font = 'bold 60px sans-serif';
|
|
7290
|
+
context.textAlign = 'center';
|
|
7291
|
+
context.textBaseline = 'middle';
|
|
7292
|
+
context.fillText(text, canvas.width / 2, canvas.height / 2 + 4);
|
|
7293
|
+
return new THREE.CanvasTexture(canvas);
|
|
7294
|
+
}
|
|
7295
|
+
/** Removes everything drawn for a measurement and forgets its points. */
|
|
7296
|
+
clearMeasurement() {
|
|
7297
|
+
[...this.measureGroup.children].forEach((child) => {
|
|
7298
|
+
this.measureGroup.remove(child);
|
|
7299
|
+
child.geometry?.dispose?.();
|
|
7300
|
+
child.material?.map?.dispose?.();
|
|
7301
|
+
child.material?.dispose?.();
|
|
6795
7302
|
});
|
|
7303
|
+
this.measureRubberBand = undefined;
|
|
7304
|
+
this.measurePoints = [];
|
|
7305
|
+
}
|
|
7306
|
+
/**
|
|
7307
|
+
* A picture of the scene with the measurement on it, as Matterport attaches to its own.
|
|
7308
|
+
*
|
|
7309
|
+
* Tags are left out, as they are there: the picture is about the measurement. It is taken straight
|
|
7310
|
+
* after a render because the drawing buffer is only guaranteed to hold the frame until the
|
|
7311
|
+
* browser presents it, and scaled down since it is uploaded and shown as a thumbnail.
|
|
7312
|
+
*/
|
|
7313
|
+
captureMeasurementScreenshot() {
|
|
7314
|
+
const hidden = [];
|
|
7315
|
+
this.tagSprites.forEach((sprite) => {
|
|
7316
|
+
if (sprite.visible) {
|
|
7317
|
+
sprite.visible = false;
|
|
7318
|
+
hidden.push(sprite);
|
|
7319
|
+
}
|
|
7320
|
+
});
|
|
7321
|
+
this.renderer.render(this.scene, this.camera);
|
|
7322
|
+
const source = this.renderer.domElement;
|
|
7323
|
+
const scale = Math.min(1, 800 / source.width, 600 / source.height);
|
|
7324
|
+
const canvas = document.createElement('canvas');
|
|
7325
|
+
canvas.width = Math.max(1, Math.round(source.width * scale));
|
|
7326
|
+
canvas.height = Math.max(1, Math.round(source.height * scale));
|
|
7327
|
+
canvas.getContext('2d').drawImage(source, 0, 0, canvas.width, canvas.height);
|
|
7328
|
+
hidden.forEach((sprite) => {
|
|
7329
|
+
sprite.visible = true;
|
|
7330
|
+
});
|
|
7331
|
+
return canvas.toDataURL('image/jpeg', 0.85);
|
|
6796
7332
|
}
|
|
6797
7333
|
/** Arms tag placement: the next click on the model drops a tag instead of being ignored. */
|
|
6798
7334
|
startTagPlacement(poiType = null) {
|
|
@@ -6823,6 +7359,7 @@ class ThreeJsService {
|
|
|
6823
7359
|
const position = this.pickPointOnGpu(event);
|
|
6824
7360
|
if (!position) {
|
|
6825
7361
|
this.removePlacementPreview();
|
|
7362
|
+
this.updateMeasureRubberBand(null);
|
|
6826
7363
|
this.renderer.domElement.style.cursor = 'not-allowed';
|
|
6827
7364
|
return;
|
|
6828
7365
|
}
|
|
@@ -6839,6 +7376,7 @@ class ThreeJsService {
|
|
|
6839
7376
|
this.scaleTagForModel(this.placementPreview);
|
|
6840
7377
|
}
|
|
6841
7378
|
this.placementPreview.position.copy(position);
|
|
7379
|
+
this.updateMeasureRubberBand(position);
|
|
6842
7380
|
}
|
|
6843
7381
|
/**
|
|
6844
7382
|
* The point under the cursor, read from the depth buffer instead of raycast, in tag space.
|
|
@@ -6940,9 +7478,250 @@ class ThreeJsService {
|
|
|
6940
7478
|
* filter that happens to match nothing.
|
|
6941
7479
|
*/
|
|
6942
7480
|
setVisibleTagTypes(types) {
|
|
7481
|
+
this.visibleTagTypes = types;
|
|
7482
|
+
this.tagSprites.forEach((sprite) => this.applyTagVisibility(sprite));
|
|
7483
|
+
}
|
|
7484
|
+
/** Says which capture is on screen, so the tags placed in it can be told from the rest. */
|
|
7485
|
+
setCurrentCapture(captureID) {
|
|
7486
|
+
this.currentCaptureID = captureID;
|
|
6943
7487
|
this.tagSprites.forEach((sprite) => {
|
|
6944
|
-
|
|
7488
|
+
this.parentTag(sprite);
|
|
7489
|
+
this.applyTagVisibility(sprite);
|
|
7490
|
+
});
|
|
7491
|
+
}
|
|
7492
|
+
/** Every tag registered in the scene, shown or not. */
|
|
7493
|
+
getTags() {
|
|
7494
|
+
const candidates = new Set(this.calibrationCandidates());
|
|
7495
|
+
return [...this.tagSprites.values()].map((sprite) => ({
|
|
7496
|
+
id: sprite.name,
|
|
7497
|
+
label: sprite.userData.label ?? null,
|
|
7498
|
+
poiType: sprite.userData.poiType ?? null,
|
|
7499
|
+
visible: sprite.visible,
|
|
7500
|
+
calibratable: candidates.has(sprite),
|
|
7501
|
+
}));
|
|
7502
|
+
}
|
|
7503
|
+
/**
|
|
7504
|
+
* How far the tags from elsewhere sit from the model, measured in model radii. Null without both.
|
|
7505
|
+
*
|
|
7506
|
+
* Before a model is calibrated those tags are wherever their own coordinates put them, which for
|
|
7507
|
+
* a model with a different origin can be a long way off - too far to find, let alone click.
|
|
7508
|
+
*/
|
|
7509
|
+
getTagDistanceFromModel() {
|
|
7510
|
+
if (!this.modelRoot || !this.modelWorldRadius) {
|
|
7511
|
+
return null;
|
|
7512
|
+
}
|
|
7513
|
+
const centroid = new THREE.Vector3();
|
|
7514
|
+
const candidates = this.calibrationCandidates();
|
|
7515
|
+
candidates.forEach((sprite) => centroid.add(sprite.getWorldPosition(new THREE.Vector3())));
|
|
7516
|
+
const count = candidates.length;
|
|
7517
|
+
if (count === 0) {
|
|
7518
|
+
return null;
|
|
7519
|
+
}
|
|
7520
|
+
// The model is centred on the world origin when it is placed.
|
|
7521
|
+
return centroid.divideScalar(count).length() / this.modelWorldRadius;
|
|
7522
|
+
}
|
|
7523
|
+
/**
|
|
7524
|
+
* Hangs a tag from the group its coordinates belong to: outside the calibration when they are in
|
|
7525
|
+
* this model's own coordinates, under it otherwise.
|
|
7526
|
+
*/
|
|
7527
|
+
parentTag(sprite) {
|
|
7528
|
+
const parent = sprite.userData.frame === 'model' && this.isOwnedByVisit(sprite)
|
|
7529
|
+
? this.modelTagGroup
|
|
7530
|
+
: this.tagGroup;
|
|
7531
|
+
if (sprite.parent !== parent) {
|
|
7532
|
+
parent.add(sprite);
|
|
7533
|
+
}
|
|
7534
|
+
}
|
|
7535
|
+
/**
|
|
7536
|
+
* The tags a calibration can be worked out from: those that came from somewhere else, whether or
|
|
7537
|
+
* not they are on show - before calibrating, most of them will not be.
|
|
7538
|
+
*
|
|
7539
|
+
* Tags placed in this model are where they are because someone clicked there; matching them to
|
|
7540
|
+
* "where they really are" would be circular. A tag in another model's own coordinates cannot be
|
|
7541
|
+
* placed here at all. And a tag already used in this calibration cannot be used again - two
|
|
7542
|
+
* pairs from one tag carry no more than one.
|
|
7543
|
+
*/
|
|
7544
|
+
calibrationCandidates() {
|
|
7545
|
+
const used = new Set(this.alignmentPairs.map((pair) => pair.tagId));
|
|
7546
|
+
return [...this.tagSprites.values()].filter((sprite) => sprite.parent === this.tagGroup
|
|
7547
|
+
&& sprite.userData.frame !== 'model'
|
|
7548
|
+
&& !this.isOwnedByVisit(sprite)
|
|
7549
|
+
&& !used.has(sprite.name));
|
|
7550
|
+
}
|
|
7551
|
+
/** How many tags there are to calibrate with, not counting those already used. */
|
|
7552
|
+
getCalibrationCandidateCount() {
|
|
7553
|
+
return this.calibrationCandidates().length;
|
|
7554
|
+
}
|
|
7555
|
+
/**
|
|
7556
|
+
* How many tags from elsewhere are not on this model, and so not shown on it.
|
|
7557
|
+
*
|
|
7558
|
+
* On a model that has not been calibrated, this is what suggests it should be: the tags are out
|
|
7559
|
+
* there, just not where the model is.
|
|
7560
|
+
*/
|
|
7561
|
+
getOffModelTagCount() {
|
|
7562
|
+
return this.calibrationCandidates().filter((sprite) => !this.isOnModel(sprite)).length;
|
|
7563
|
+
}
|
|
7564
|
+
/** Whether the tag was placed in the visit on screen. */
|
|
7565
|
+
isOwnedByVisit(sprite) {
|
|
7566
|
+
return !!sprite.userData.originCaptureID
|
|
7567
|
+
&& sprite.userData.originCaptureID === this.currentCaptureID;
|
|
7568
|
+
}
|
|
7569
|
+
/**
|
|
7570
|
+
* A tag shows when its type is not filtered out and it belongs on this model.
|
|
7571
|
+
*
|
|
7572
|
+
* One place decides, because the two used to set `visible` separately and whichever ran last
|
|
7573
|
+
* undid the other.
|
|
7574
|
+
*/
|
|
7575
|
+
applyTagVisibility(sprite) {
|
|
7576
|
+
const typeAllowed = this.visibleTagTypes === null
|
|
7577
|
+
|| this.visibleTagTypes.includes(sprite.userData.poiType);
|
|
7578
|
+
sprite.visible = typeAllowed && this.belongsOnThisModel(sprite);
|
|
7579
|
+
}
|
|
7580
|
+
/**
|
|
7581
|
+
* Whether a tag has a place on this model.
|
|
7582
|
+
*
|
|
7583
|
+
* Every tag of the space is offered to every model - there is one set of tags, not one per model.
|
|
7584
|
+
* What a model shows of it is the part that sits on the model.
|
|
7585
|
+
*/
|
|
7586
|
+
belongsOnThisModel(sprite) {
|
|
7587
|
+
// Placed here by a click on this very surface.
|
|
7588
|
+
if (this.isOwnedByVisit(sprite)) {
|
|
7589
|
+
return true;
|
|
7590
|
+
}
|
|
7591
|
+
// Placed in another model before that one was calibrated: its coordinates are that model's own
|
|
7592
|
+
// and say nothing about where it would be in this one.
|
|
7593
|
+
if (sprite.userData.frame === 'model') {
|
|
7594
|
+
return false;
|
|
7595
|
+
}
|
|
7596
|
+
// Calibrating is matching tags to where they belong, so they all have to be on offer.
|
|
7597
|
+
if (this.aligning) {
|
|
7598
|
+
return true;
|
|
7599
|
+
}
|
|
7600
|
+
return this.isOnModel(sprite);
|
|
7601
|
+
}
|
|
7602
|
+
/** Whether a tag, drawn where its coordinates and the calibration put it, sits on the model. */
|
|
7603
|
+
isOnModel(sprite) {
|
|
7604
|
+
if (!this.surfaceCells) {
|
|
7605
|
+
// No model to be on yet.
|
|
7606
|
+
return false;
|
|
7607
|
+
}
|
|
7608
|
+
const parent = sprite.parent ?? this.tagGroup;
|
|
7609
|
+
parent.updateMatrix();
|
|
7610
|
+
// The grid is in the model's own units; the calibration's scale is how many of those a metre is.
|
|
7611
|
+
const reach = ThreeJsService.ON_MODEL_METRES * (parent.scale.x || 1);
|
|
7612
|
+
return this.isNearSurface(sprite.position.clone().applyMatrix4(parent.matrix), reach);
|
|
7613
|
+
}
|
|
7614
|
+
/** Re-decides every tag's visibility: the model, the calibration or the mode changed. */
|
|
7615
|
+
refreshTagVisibility() {
|
|
7616
|
+
this.tagSprites.forEach((sprite) => this.applyTagVisibility(sprite));
|
|
7617
|
+
}
|
|
7618
|
+
surfaceKey(x, y, z) {
|
|
7619
|
+
const span = ThreeJsService.SURFACE_GRID_SPAN;
|
|
7620
|
+
const pad = ThreeJsService.SURFACE_GRID_PAD;
|
|
7621
|
+
const [i, j, k] = [x + pad, y + pad, z + pad];
|
|
7622
|
+
if (i < 0 || j < 0 || k < 0 || i >= span || j >= span || k >= span) {
|
|
7623
|
+
return -1;
|
|
7624
|
+
}
|
|
7625
|
+
return i + span * (j + span * k);
|
|
7626
|
+
}
|
|
7627
|
+
/**
|
|
7628
|
+
* Lays the grid over the model's surface.
|
|
7629
|
+
*
|
|
7630
|
+
* Runs once per model, in one pass over its triangles. A triangle smaller than a cell is marked
|
|
7631
|
+
* by its centre, which is every triangle of a scanned model; a larger one - the two triangles of
|
|
7632
|
+
* a drawn wall - is sampled across its face, or the middle of the wall would read as empty.
|
|
7633
|
+
*/
|
|
7634
|
+
buildSurfaceGrid(object, box) {
|
|
7635
|
+
const size = box.getSize(new THREE.Vector3());
|
|
7636
|
+
const longest = Math.max(size.x, size.y, size.z);
|
|
7637
|
+
if (!(longest > 0)) {
|
|
7638
|
+
this.surfaceCells = null;
|
|
7639
|
+
return;
|
|
7640
|
+
}
|
|
7641
|
+
// A quarter of a metre where the model is in metres; coarser on a very large one, so the grid
|
|
7642
|
+
// stays addressable and its memory bounded.
|
|
7643
|
+
const cell = Math.max(0.25, longest / 384);
|
|
7644
|
+
const origin = box.min.clone();
|
|
7645
|
+
const cells = new Set();
|
|
7646
|
+
const mark = (point) => {
|
|
7647
|
+
const key = this.surfaceKey(Math.floor((point.x - origin.x) / cell), Math.floor((point.y - origin.y) / cell), Math.floor((point.z - origin.z) / cell));
|
|
7648
|
+
if (key >= 0) {
|
|
7649
|
+
cells.add(key);
|
|
7650
|
+
}
|
|
7651
|
+
};
|
|
7652
|
+
let triangleCount = 0;
|
|
7653
|
+
object.updateMatrixWorld(true);
|
|
7654
|
+
object.traverse((child) => {
|
|
7655
|
+
const position = child.isMesh && child.geometry?.attributes?.position;
|
|
7656
|
+
if (position) {
|
|
7657
|
+
triangleCount += (child.geometry.index ? child.geometry.index.count : position.count) / 3;
|
|
7658
|
+
}
|
|
7659
|
+
});
|
|
7660
|
+
// Past a few million triangles they are far smaller than a cell, and every cell is hit many
|
|
7661
|
+
// times over; a sample marks the same cells.
|
|
7662
|
+
const stride = Math.max(1, Math.ceil(triangleCount / 3000000));
|
|
7663
|
+
const a = new THREE.Vector3();
|
|
7664
|
+
const b = new THREE.Vector3();
|
|
7665
|
+
const c = new THREE.Vector3();
|
|
7666
|
+
const point = new THREE.Vector3();
|
|
7667
|
+
object.traverse((child) => {
|
|
7668
|
+
const position = child.isMesh && child.geometry?.attributes?.position;
|
|
7669
|
+
if (!position) {
|
|
7670
|
+
return;
|
|
7671
|
+
}
|
|
7672
|
+
const index = child.geometry.index;
|
|
7673
|
+
const count = (index ? index.count : position.count) / 3;
|
|
7674
|
+
for (let triangle = 0; triangle < count; triangle += stride) {
|
|
7675
|
+
const offset = triangle * 3;
|
|
7676
|
+
a.fromBufferAttribute(position, index ? index.getX(offset) : offset).applyMatrix4(child.matrixWorld);
|
|
7677
|
+
b.fromBufferAttribute(position, index ? index.getX(offset + 1) : offset + 1).applyMatrix4(child.matrixWorld);
|
|
7678
|
+
c.fromBufferAttribute(position, index ? index.getX(offset + 2) : offset + 2).applyMatrix4(child.matrixWorld);
|
|
7679
|
+
const edge = Math.max(a.distanceTo(b), b.distanceTo(c), c.distanceTo(a));
|
|
7680
|
+
if (edge <= cell) {
|
|
7681
|
+
mark(point.copy(a).add(b).add(c).multiplyScalar(1 / 3));
|
|
7682
|
+
continue;
|
|
7683
|
+
}
|
|
7684
|
+
// Half-cell steps across the face. Capped, so one enormous triangle cannot stall the load;
|
|
7685
|
+
// the reach a tag is tested with spans several cells and bridges what the cap leaves.
|
|
7686
|
+
const steps = Math.min(96, Math.ceil((2 * edge) / cell));
|
|
7687
|
+
for (let i = 0; i <= steps; i += 1) {
|
|
7688
|
+
for (let j = 0; j <= steps - i; j += 1) {
|
|
7689
|
+
point.copy(a)
|
|
7690
|
+
.addScaledVector(b.clone().sub(a), i / steps)
|
|
7691
|
+
.addScaledVector(c.clone().sub(a), j / steps);
|
|
7692
|
+
mark(point);
|
|
7693
|
+
}
|
|
7694
|
+
}
|
|
7695
|
+
}
|
|
6945
7696
|
});
|
|
7697
|
+
this.surfaceCells = cells;
|
|
7698
|
+
this.surfaceOrigin.copy(origin);
|
|
7699
|
+
this.surfaceCellSize = cell;
|
|
7700
|
+
}
|
|
7701
|
+
/** Whether some part of the model's surface lies within `reach` of a point, in model units. */
|
|
7702
|
+
isNearSurface(point, reach) {
|
|
7703
|
+
const cell = this.surfaceCellSize;
|
|
7704
|
+
const origin = this.surfaceOrigin;
|
|
7705
|
+
const x = (point.x - origin.x) / cell;
|
|
7706
|
+
const y = (point.y - origin.y) / cell;
|
|
7707
|
+
const z = (point.z - origin.z) / cell;
|
|
7708
|
+
// In cells. Capped: beyond this the calibration's scale makes a metre so many cells that the
|
|
7709
|
+
// search would dwarf the model, and a tag that far off is not on it by any reading.
|
|
7710
|
+
const span = Math.min(16, Math.ceil(reach / cell));
|
|
7711
|
+
// A cell is "within reach" when its centre is, give or take the half-diagonal of the cell.
|
|
7712
|
+
const limit = (reach / cell + 0.87) ** 2;
|
|
7713
|
+
const [cx, cy, cz] = [Math.floor(x), Math.floor(y), Math.floor(z)];
|
|
7714
|
+
for (let k = cz - span; k <= cz + span; k += 1) {
|
|
7715
|
+
for (let j = cy - span; j <= cy + span; j += 1) {
|
|
7716
|
+
for (let i = cx - span; i <= cx + span; i += 1) {
|
|
7717
|
+
const distance = (i + 0.5 - x) ** 2 + (j + 0.5 - y) ** 2 + (k + 0.5 - z) ** 2;
|
|
7718
|
+
if (distance <= limit && this.surfaceCells.has(this.surfaceKey(i, j, k))) {
|
|
7719
|
+
return true;
|
|
7720
|
+
}
|
|
7721
|
+
}
|
|
7722
|
+
}
|
|
7723
|
+
}
|
|
7724
|
+
return false;
|
|
6946
7725
|
}
|
|
6947
7726
|
/**
|
|
6948
7727
|
* Moves the camera to look at a tag, mirroring Matterport's goToTag.
|
|
@@ -7073,8 +7852,11 @@ class ThreeJsService {
|
|
|
7073
7852
|
this.tagGroup.position.set(translation?.x ?? 0, translation?.y ?? 0, translation?.z ?? 0);
|
|
7074
7853
|
this.tagGroup.rotation.set(rotation?.x ?? 0, rotation?.y ?? 0, rotation?.z ?? 0);
|
|
7075
7854
|
this.tagGroup.scale.setScalar(scale ?? 1);
|
|
7076
|
-
|
|
7855
|
+
this.tagGroup.updateMatrix();
|
|
7856
|
+
// Tag size is derived from the world scale it inherits, which this call just changed - and so
|
|
7857
|
+
// is which tags land on the model.
|
|
7077
7858
|
this.tagSprites.forEach((sprite) => this.scaleTagForModel(sprite));
|
|
7859
|
+
this.refreshTagVisibility();
|
|
7078
7860
|
}
|
|
7079
7861
|
/**
|
|
7080
7862
|
* Adds a tag, or moves/restyles the one already registered for this element.
|
|
@@ -7089,6 +7871,10 @@ class ThreeJsService {
|
|
|
7089
7871
|
existing.position.set(tag.position.x, tag.position.y, tag.position.z);
|
|
7090
7872
|
existing.userData.poiType = tag.poiType ?? existing.userData.poiType;
|
|
7091
7873
|
existing.userData.label = tag.label ?? existing.userData.label;
|
|
7874
|
+
existing.userData.originCaptureID = tag.originCaptureID ?? existing.userData.originCaptureID;
|
|
7875
|
+
existing.userData.frame = tag.frame ?? existing.userData.frame;
|
|
7876
|
+
this.parentTag(existing);
|
|
7877
|
+
this.applyTagVisibility(existing);
|
|
7092
7878
|
return;
|
|
7093
7879
|
}
|
|
7094
7880
|
const sprite = new THREE.Sprite(new THREE.SpriteMaterial({
|
|
@@ -7101,10 +7887,13 @@ class ThreeJsService {
|
|
|
7101
7887
|
sprite.name = tag.id;
|
|
7102
7888
|
sprite.userData.poiType = tag.poiType ?? null;
|
|
7103
7889
|
sprite.userData.label = tag.label ?? null;
|
|
7890
|
+
sprite.userData.originCaptureID = tag.originCaptureID ?? null;
|
|
7891
|
+
sprite.userData.frame = tag.frame ?? null;
|
|
7104
7892
|
sprite.position.set(tag.position.x, tag.position.y, tag.position.z);
|
|
7105
7893
|
sprite.renderOrder = 999;
|
|
7894
|
+
this.parentTag(sprite);
|
|
7106
7895
|
this.scaleTagForModel(sprite);
|
|
7107
|
-
this.
|
|
7896
|
+
this.applyTagVisibility(sprite);
|
|
7108
7897
|
this.tagSprites.set(tag.id, sprite);
|
|
7109
7898
|
if (tag.iconUrl) {
|
|
7110
7899
|
this.applyTagIcon(sprite, tag.iconUrl);
|
|
@@ -7116,7 +7905,7 @@ class ThreeJsService {
|
|
|
7116
7905
|
if (!sprite) {
|
|
7117
7906
|
return;
|
|
7118
7907
|
}
|
|
7119
|
-
|
|
7908
|
+
sprite.parent?.remove(sprite);
|
|
7120
7909
|
sprite.material.map?.dispose();
|
|
7121
7910
|
sprite.material.dispose();
|
|
7122
7911
|
this.tagSprites.delete(elementId);
|
|
@@ -7157,7 +7946,8 @@ class ThreeJsService {
|
|
|
7157
7946
|
* size on screen whatever the model's units are.
|
|
7158
7947
|
*/
|
|
7159
7948
|
scaleTagForModel(sprite) {
|
|
7160
|
-
|
|
7949
|
+
// Its own parent's scale: tags in the model's coordinates do not inherit the calibration's.
|
|
7950
|
+
const worldScale = (sprite.parent ?? this.tagGroup).getWorldScale(new THREE.Vector3()).x || 1;
|
|
7161
7951
|
const size = 0.4 / worldScale;
|
|
7162
7952
|
sprite.scale.set(size, size, 1);
|
|
7163
7953
|
}
|
|
@@ -7216,7 +8006,7 @@ class ThreeJsService {
|
|
|
7216
8006
|
this.removePlacementPreview();
|
|
7217
8007
|
// Tags first: they draw on top of the model, so they have to answer the pointer first too.
|
|
7218
8008
|
const hoveredTag = this.raycaster
|
|
7219
|
-
.intersectObjects(this.
|
|
8009
|
+
.intersectObjects([...this.tagSprites.values()], false)
|
|
7220
8010
|
.find((hit) => hit.object.visible)?.object;
|
|
7221
8011
|
if (hoveredTag) {
|
|
7222
8012
|
this.renderer.domElement.style.cursor = 'pointer';
|
|
@@ -7255,6 +8045,15 @@ class ThreeJsService {
|
|
|
7255
8045
|
this.handleAlignmentClick(event);
|
|
7256
8046
|
return;
|
|
7257
8047
|
}
|
|
8048
|
+
// Measuring takes every click as a point, the same way placement takes it as a position.
|
|
8049
|
+
if (this.measuring) {
|
|
8050
|
+
const placement = this.pickPlacementOnModel(event);
|
|
8051
|
+
if (placement) {
|
|
8052
|
+
const { x, y, z } = placement.position;
|
|
8053
|
+
this.addMeasurePoint(new THREE.Vector3(x, y, z));
|
|
8054
|
+
}
|
|
8055
|
+
return;
|
|
8056
|
+
}
|
|
7258
8057
|
// Placement wins over everything: while armed, a click means "put it here", not "open that".
|
|
7259
8058
|
if (this.placingTag) {
|
|
7260
8059
|
const placement = this.pickPlacementOnModel(event);
|
|
@@ -7270,7 +8069,7 @@ class ThreeJsService {
|
|
|
7270
8069
|
}
|
|
7271
8070
|
this.raycaster.setFromCamera(this.mouse, this.camera);
|
|
7272
8071
|
// Same precedence as hover: a tag drawn over the model claims the click.
|
|
7273
|
-
const tagHits = this.raycaster.intersectObjects(this.
|
|
8072
|
+
const tagHits = this.raycaster.intersectObjects([...this.tagSprites.values()], false)
|
|
7274
8073
|
.filter((hit) => hit.object.visible);
|
|
7275
8074
|
if (tagHits.length > 0) {
|
|
7276
8075
|
const tag = tagHits[0].object;
|
|
@@ -7305,6 +8104,10 @@ class ThreeJsService {
|
|
|
7305
8104
|
this.cancelTagPlacement();
|
|
7306
8105
|
return;
|
|
7307
8106
|
}
|
|
8107
|
+
if (this.measuring) {
|
|
8108
|
+
this.cancelMeasuring();
|
|
8109
|
+
return;
|
|
8110
|
+
}
|
|
7308
8111
|
if (this.aligning) {
|
|
7309
8112
|
this.cancelTagAlignment();
|
|
7310
8113
|
}
|
|
@@ -7332,6 +8135,8 @@ class ThreeJsService {
|
|
|
7332
8135
|
}
|
|
7333
8136
|
this.discardCurrentLoad();
|
|
7334
8137
|
this.clearTags();
|
|
8138
|
+
this.measuring = false;
|
|
8139
|
+
this.clearMeasurement();
|
|
7335
8140
|
if (this.renderer) {
|
|
7336
8141
|
this.renderer.dispose();
|
|
7337
8142
|
}
|
|
@@ -7349,16 +8154,481 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
7349
8154
|
}]
|
|
7350
8155
|
}], ctorParameters: () => [{ type: i0.NgZone }] });
|
|
7351
8156
|
|
|
7352
|
-
|
|
7353
|
-
|
|
7354
|
-
|
|
7355
|
-
|
|
7356
|
-
|
|
7357
|
-
|
|
7358
|
-
|
|
7359
|
-
|
|
8157
|
+
class CaptureService {
|
|
8158
|
+
API; //AWS
|
|
8159
|
+
constructor(apiInjected) {
|
|
8160
|
+
this.API = apiInjected;
|
|
8161
|
+
}
|
|
8162
|
+
async getCapturesBySpace(spaceID) {
|
|
8163
|
+
return this.API.CapturesBySpace(spaceID).then((cap) => cap.items);
|
|
8164
|
+
}
|
|
8165
|
+
async getCapture(captureID) {
|
|
8166
|
+
return this.API.__proto__.GetCapture(captureID);
|
|
8167
|
+
}
|
|
8168
|
+
async deleteCapture(captureID) {
|
|
8169
|
+
return this.API.__proto__.DeleteCapture({ id: captureID });
|
|
8170
|
+
}
|
|
8171
|
+
async updateCapture(capture) {
|
|
8172
|
+
return this.API.__proto__.UpdateCapture(capture);
|
|
8173
|
+
}
|
|
8174
|
+
async createCapture(capture) {
|
|
8175
|
+
return this.API.__proto__.CreateCapture(capture);
|
|
8176
|
+
}
|
|
8177
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CaptureService, deps: [{ token: 'currentAPIService' }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
8178
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CaptureService, providedIn: "root" });
|
|
8179
|
+
}
|
|
8180
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CaptureService, decorators: [{
|
|
8181
|
+
type: Injectable,
|
|
8182
|
+
args: [{
|
|
8183
|
+
providedIn: "root",
|
|
8184
|
+
}]
|
|
8185
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
8186
|
+
type: Inject,
|
|
8187
|
+
args: ['currentAPIService']
|
|
8188
|
+
}] }] });
|
|
8189
|
+
|
|
8190
|
+
/* eslint-disable class-methods-use-this */
|
|
8191
|
+
class ZoneService {
|
|
8192
|
+
userService;
|
|
8193
|
+
API; //AWS APIService
|
|
8194
|
+
constructor(apiInjected, userService) {
|
|
8195
|
+
this.userService = userService;
|
|
8196
|
+
this.API = apiInjected;
|
|
8197
|
+
}
|
|
8198
|
+
async getZonesBySpace(spaceID) {
|
|
8199
|
+
const list = [];
|
|
8200
|
+
if (this.API.__proto__.hasOwnProperty('ZonesBySpace')) {
|
|
8201
|
+
// Paginated: without a nextToken loop, only the first page (100 zones) was returned and
|
|
8202
|
+
// the sweeps of the other zones silently disappeared.
|
|
8203
|
+
let nextToken = null;
|
|
8204
|
+
do {
|
|
8205
|
+
const zones = await this.API.__proto__.ZonesBySpace(spaceID, null, null, 1000, nextToken);
|
|
8206
|
+
for (const zone of zones?.items || []) {
|
|
8207
|
+
const zoneObject = { ...zone };
|
|
8208
|
+
zoneObject.virtual = this.zoneIsVirtual(zone);
|
|
8209
|
+
list.push(zoneObject);
|
|
8210
|
+
}
|
|
8211
|
+
nextToken = zones?.nextToken ?? null;
|
|
8212
|
+
} while (nextToken);
|
|
8213
|
+
return list;
|
|
8214
|
+
}
|
|
8215
|
+
else {
|
|
8216
|
+
throw new Error('ZonesBySpace not implemented');
|
|
8217
|
+
}
|
|
8218
|
+
}
|
|
8219
|
+
async getZonesBySpaceForUser(spaceID) {
|
|
8220
|
+
const missionForLocation = this.userService.currentMission(spaceID);
|
|
8221
|
+
const list = [];
|
|
8222
|
+
if (missionForLocation && missionForLocation.zoneID) {
|
|
8223
|
+
const allowedZone = await this.getZone(missionForLocation.zoneID);
|
|
8224
|
+
allowedZone.virtual = this.zoneIsVirtual(allowedZone);
|
|
8225
|
+
if (!allowedZone.parent) {
|
|
8226
|
+
// the whole building is authorized
|
|
8227
|
+
return this.getZonesBySpace(spaceID);
|
|
8228
|
+
}
|
|
8229
|
+
list.push(allowedZone);
|
|
8230
|
+
// allow all children zones of an allowed parent
|
|
8231
|
+
const children = (await this.API.__proto__.ZonesByZone(allowedZone.id))
|
|
8232
|
+
.items;
|
|
8233
|
+
if (children.length > 0) {
|
|
8234
|
+
for (const child of children) {
|
|
8235
|
+
const childZone = { ...child };
|
|
8236
|
+
childZone.virtual = this.zoneIsVirtual(child);
|
|
8237
|
+
list.push(childZone);
|
|
8238
|
+
}
|
|
8239
|
+
}
|
|
8240
|
+
// allow the parent Floor of the allowed zone
|
|
8241
|
+
if (allowedZone.parent.layer &&
|
|
8242
|
+
allowedZone.parent.layer.name === 'FLOOR') {
|
|
8243
|
+
list.push(allowedZone.parent);
|
|
8244
|
+
}
|
|
8245
|
+
}
|
|
8246
|
+
else {
|
|
8247
|
+
return this.getZonesBySpace(spaceID);
|
|
8248
|
+
}
|
|
8249
|
+
return list;
|
|
8250
|
+
}
|
|
8251
|
+
async filterZonesIDsForUser(zones) {
|
|
8252
|
+
const { spaceID } = zones[0];
|
|
8253
|
+
const zoneIDs = new Set(zones.map((zone) => zone.id));
|
|
8254
|
+
const allowedZones = await this.getZonesBySpaceForUser(spaceID);
|
|
8255
|
+
const filteredZones = allowedZones.filter((zone) => zoneIDs.has(zone.id));
|
|
8256
|
+
return filteredZones;
|
|
8257
|
+
}
|
|
8258
|
+
async deleteZone(zoneID) {
|
|
8259
|
+
return this.API.__proto__.DeleteZone({ id: zoneID });
|
|
8260
|
+
}
|
|
8261
|
+
async getZonesForSweepForSpace(sweepID, spaceID) {
|
|
8262
|
+
const zonesSpace = (await this.API.__proto__.ZonesBySpace(spaceID)).items;
|
|
8263
|
+
return zonesSpace.filter((zone) => zone.sweepIDs && zone.sweepIDs.includes(sweepID));
|
|
8264
|
+
}
|
|
8265
|
+
// // eslint-disable-next-line class-methods-use-this
|
|
8266
|
+
sortZonesFromChildToParent(zonesToSort) {
|
|
8267
|
+
// get default
|
|
8268
|
+
const defaultZone = zonesToSort.find((z) => !z.parentID);
|
|
8269
|
+
if (defaultZone) {
|
|
8270
|
+
const indexOf = zonesToSort.indexOf(defaultZone);
|
|
8271
|
+
zonesToSort.splice(indexOf, 1);
|
|
8272
|
+
}
|
|
8273
|
+
const zones = zonesToSort.sort((a, b) => {
|
|
8274
|
+
if (a.parentID === b.id) {
|
|
8275
|
+
return -1;
|
|
8276
|
+
}
|
|
8277
|
+
return 0;
|
|
8278
|
+
});
|
|
8279
|
+
// so far by number of navigation points in them
|
|
8280
|
+
// const zones = zonesToSort.sort(
|
|
8281
|
+
// (a, b) => a.navigations.items.length - b.navigations.items.length,
|
|
8282
|
+
// );
|
|
8283
|
+
if (defaultZone) {
|
|
8284
|
+
zones.push(defaultZone);
|
|
8285
|
+
}
|
|
8286
|
+
return zones;
|
|
8287
|
+
}
|
|
8288
|
+
async getZonesForObject(object) {
|
|
8289
|
+
if (object.pois.items && object.pois.items.length > 0) {
|
|
8290
|
+
const [poi] = object.pois.items;
|
|
8291
|
+
if (poi.matterportSweepID) {
|
|
8292
|
+
const zones = await this.getZonesForSweepForSpace(poi.matterportSweepID, object.spaceID);
|
|
8293
|
+
return zones;
|
|
8294
|
+
}
|
|
8295
|
+
}
|
|
8296
|
+
return null;
|
|
8297
|
+
}
|
|
8298
|
+
async createVirtualZone(zoneInput) {
|
|
8299
|
+
zoneInput.sweepIDs = [];
|
|
8300
|
+
const zone = await this.API.__proto__.CreateZone(zoneInput);
|
|
8301
|
+
console.log('virtual zone created', zone);
|
|
8302
|
+
return zone;
|
|
8303
|
+
}
|
|
8304
|
+
zoneIsVirtual(zone) {
|
|
8305
|
+
if (!zone.sweepIDs) {
|
|
8306
|
+
return true;
|
|
8307
|
+
}
|
|
8308
|
+
if ((zone.tags && zone.tags.includes('virtual')) ||
|
|
8309
|
+
zone.sweepIDs.length === 0) {
|
|
8310
|
+
return true;
|
|
8311
|
+
}
|
|
8312
|
+
return false;
|
|
8313
|
+
}
|
|
8314
|
+
async getModel3DForZone(zoneID) {
|
|
8315
|
+
const zone = await this.API.__proto__.GetZone(zoneID);
|
|
8316
|
+
if (zone.nodeIDs) {
|
|
8317
|
+
const node = await this.API.__proto__.GetNode(zone.nodeIDs[0]);
|
|
8318
|
+
return node.model3D;
|
|
8319
|
+
}
|
|
8320
|
+
return null;
|
|
8321
|
+
}
|
|
8322
|
+
async getZone(zoneID) {
|
|
8323
|
+
return this.API.__proto__.GetZone(zoneID);
|
|
8324
|
+
}
|
|
8325
|
+
async getDefaultZoneForSpace(spaceID) {
|
|
8326
|
+
return this.getZonesBySpace(spaceID).then(async (zones) => {
|
|
8327
|
+
const zonesBuilding = zones.filter((zone) => zone.layer && zone.layer.name === 'BUILDING');
|
|
8328
|
+
if (zonesBuilding.length === 1) {
|
|
8329
|
+
return zonesBuilding[0];
|
|
8330
|
+
}
|
|
8331
|
+
const parentZone = zones.find((zone) => !zone.parentID);
|
|
8332
|
+
return parentZone;
|
|
8333
|
+
});
|
|
8334
|
+
}
|
|
8335
|
+
async create(input) {
|
|
8336
|
+
return this.API.__proto__.CreateZone(input);
|
|
8337
|
+
}
|
|
8338
|
+
// async getSortedZonesForSweepForUser(
|
|
8339
|
+
// sweep: string,
|
|
8340
|
+
// spaceID: string,
|
|
8341
|
+
// ): Promise<Zone[]> {
|
|
8342
|
+
// if (!spaceID) {
|
|
8343
|
+
// return [];
|
|
8344
|
+
// }
|
|
8345
|
+
// const zones = await this.getZonesForSweepForSpace(sweep, spaceID);
|
|
8346
|
+
// if (zones.length === 0) {
|
|
8347
|
+
// return [];
|
|
8348
|
+
// }
|
|
8349
|
+
// const filteredZones = await this.filterZonesIDsForUser(zones);
|
|
8350
|
+
// if (filteredZones.length > 0) {
|
|
8351
|
+
// let zonesSorted = this.sortZonesFromChildToParent(filteredZones);
|
|
8352
|
+
// zonesSorted = zonesSorted.filter((zone) => zone !== undefined);
|
|
8353
|
+
// return zonesSorted;
|
|
8354
|
+
// }
|
|
8355
|
+
// return [];
|
|
8356
|
+
// }
|
|
8357
|
+
async updateZone(input) {
|
|
8358
|
+
return this.API.__proto__.UpdateZone(input);
|
|
8359
|
+
}
|
|
8360
|
+
async getZonesForNodeForSpace(nodeID, spaceID) {
|
|
8361
|
+
return this.API.__proto__.ZonesBySpace(spaceID).then(async (zones) => {
|
|
8362
|
+
const filteredZones = zones.items.filter((zone) => zone.nodeIDs && zone.nodeIDs.includes(nodeID));
|
|
8363
|
+
return filteredZones;
|
|
8364
|
+
});
|
|
8365
|
+
}
|
|
8366
|
+
/**
|
|
8367
|
+
* Gets the Zone (layer FLOOR) for a matterport sweep ID in a given Space
|
|
8368
|
+
* @param sweepID
|
|
8369
|
+
* @param spaceID
|
|
8370
|
+
* @returns Zone
|
|
8371
|
+
*/
|
|
8372
|
+
async getFloorZoneForSweepInSpace(sweepID, spaceID) {
|
|
8373
|
+
const zones = await this.getZonesForSweepForSpace(sweepID, spaceID);
|
|
8374
|
+
const floors = zones.filter((zone) => zone.layer && zone.layer.name.includes('FLOOR'));
|
|
8375
|
+
return floors.length > 0 ? floors[0] : null;
|
|
8376
|
+
}
|
|
8377
|
+
/**
|
|
8378
|
+
* Get zones with audio track for entire space
|
|
8379
|
+
* @param spaceID
|
|
8380
|
+
* @returns Promise<Zone[]>
|
|
8381
|
+
*/
|
|
8382
|
+
async getAudioZonesForSpace(spaceID) {
|
|
8383
|
+
const allZones = await this.getZonesBySpace(spaceID);
|
|
8384
|
+
return allZones.filter((zone) => zone.audioID);
|
|
8385
|
+
}
|
|
8386
|
+
/**
|
|
8387
|
+
* Get audio zones for user (within allowed zones)
|
|
8388
|
+
* @param spaceID
|
|
8389
|
+
* @returns Promise<Zone[]>
|
|
8390
|
+
*/
|
|
8391
|
+
async getAudioZonesForUserForSpace(spaceID) {
|
|
8392
|
+
const userZones = await this.getZonesBySpaceForUser(spaceID);
|
|
8393
|
+
return userZones.filter((zone) => zone.audioID);
|
|
8394
|
+
}
|
|
8395
|
+
/**
|
|
8396
|
+
* Get Zones by Parent Zone ID
|
|
8397
|
+
* @param parentZoneID string
|
|
8398
|
+
* @returns Promise<Zone[]>
|
|
8399
|
+
*/
|
|
8400
|
+
async getZonesByParentZone(parentZoneID) {
|
|
8401
|
+
return this.API.__proto__
|
|
8402
|
+
.ZonesByZone(parentZoneID)
|
|
8403
|
+
.then((res) => res.items);
|
|
8404
|
+
}
|
|
8405
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ZoneService, deps: [{ token: 'currentAPIService' }, { token: BaseUserService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
8406
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ZoneService, providedIn: 'root' });
|
|
8407
|
+
}
|
|
8408
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ZoneService, decorators: [{
|
|
8409
|
+
type: Injectable,
|
|
8410
|
+
args: [{
|
|
8411
|
+
providedIn: 'root',
|
|
8412
|
+
}]
|
|
8413
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
8414
|
+
type: Inject,
|
|
8415
|
+
args: ['currentAPIService']
|
|
8416
|
+
}] }, { type: BaseUserService }] });
|
|
8417
|
+
|
|
8418
|
+
class NavigationService {
|
|
8419
|
+
userService;
|
|
8420
|
+
zoneService;
|
|
8421
|
+
API;
|
|
8422
|
+
constructor(apiInjected, userService, zoneService) {
|
|
8423
|
+
this.userService = userService;
|
|
8424
|
+
this.zoneService = zoneService;
|
|
8425
|
+
this.API = apiInjected;
|
|
8426
|
+
}
|
|
8427
|
+
async createNavigation(navInput) {
|
|
8428
|
+
return this.API.__proto__.CreateNavigation(navInput);
|
|
8429
|
+
}
|
|
8430
|
+
async deleteNavigation(id) {
|
|
8431
|
+
return this.API.__proto__.DeleteNavigation({ id });
|
|
8432
|
+
}
|
|
8433
|
+
async getNavigationsForNode(nodeID) {
|
|
8434
|
+
const limit = 200;
|
|
8435
|
+
let navigationsTokenDict = { navigations: [], nextToken: null };
|
|
8436
|
+
do {
|
|
8437
|
+
navigationsTokenDict =
|
|
8438
|
+
await this.mergeAPIResponseWithNextTokenWithLimit(nodeID, limit, navigationsTokenDict);
|
|
8439
|
+
} while (navigationsTokenDict.nextToken);
|
|
8440
|
+
return navigationsTokenDict.navigations;
|
|
8441
|
+
}
|
|
8442
|
+
async mergeAPIResponseWithNextTokenWithLimit(nodeID, limit, navigationsTokenDict) {
|
|
8443
|
+
return this.API.__proto__.NavigationsByNode(nodeID, null, null, limit, navigationsTokenDict.nextToken).then((response) => {
|
|
8444
|
+
const navigations = response.items;
|
|
8445
|
+
const navObject = {
|
|
8446
|
+
navigations: [...navigationsTokenDict.navigations, ...navigations],
|
|
8447
|
+
nextToken: response.nextToken,
|
|
8448
|
+
};
|
|
8449
|
+
return navObject;
|
|
8450
|
+
});
|
|
8451
|
+
}
|
|
8452
|
+
async getNavigation(id) {
|
|
8453
|
+
return this.API.__proto__.GetNavigation(id);
|
|
8454
|
+
}
|
|
8455
|
+
async getSweepsIDsForSpace(spaceID) {
|
|
8456
|
+
const zones = await this.zoneService.getZonesBySpace(spaceID);
|
|
8457
|
+
const zonesWithSweeps = zones.filter((zone) => zone.sweepIDs && zone.sweepIDs.length > 0);
|
|
8458
|
+
const allSweeps = zonesWithSweeps.flatMap((zone) => zone.sweepIDs);
|
|
8459
|
+
return [...new Set(allSweeps)];
|
|
8460
|
+
}
|
|
8461
|
+
async getAllowedSweepsIDsForUserInSpace(spaceID) {
|
|
8462
|
+
const missionsForSpace = this.userService.currentMissions.filter((mis) => mis.spaceID === spaceID);
|
|
8463
|
+
console.log(missionsForSpace);
|
|
8464
|
+
let navIDs = [];
|
|
8465
|
+
if (this.userService.userHasAccessToAllZonesInSpace(spaceID)) {
|
|
8466
|
+
console.log(1);
|
|
8467
|
+
return this.getSweepsIDsForSpace(spaceID);
|
|
8468
|
+
}
|
|
8469
|
+
await Promise.all(missionsForSpace.map(async (mission) => {
|
|
8470
|
+
navIDs = [...navIDs, ...mission.zone.sweepIDs];
|
|
8471
|
+
}));
|
|
8472
|
+
console.log('final navids = ', navIDs);
|
|
8473
|
+
return navIDs;
|
|
8474
|
+
}
|
|
8475
|
+
async getForbiddenNavigationIDsForUserInSpace(spaceID) {
|
|
8476
|
+
const allowed = await this.getAllowedSweepsIDsForUserInSpace(spaceID);
|
|
8477
|
+
const allNavs = await this.getSweepsIDsForSpace(spaceID);
|
|
8478
|
+
const difference = allNavs.filter((x) => !allowed.includes(x));
|
|
8479
|
+
return difference.length > 0 ? difference : null;
|
|
8480
|
+
}
|
|
8481
|
+
async getNavigationsForMatterportIDForSpace(matterportID, spaceID) {
|
|
8482
|
+
return this.API.__proto__.NavigationsByMatterportIDBySpace(matterportID, {
|
|
8483
|
+
eq: spaceID,
|
|
8484
|
+
}).then((navs) => navs.items);
|
|
8485
|
+
}
|
|
8486
|
+
async getNavigationsForZone(zone) {
|
|
8487
|
+
const responseNavs = [];
|
|
8488
|
+
await Promise.all(zone.sweepIDs.map(async (sweepID) => {
|
|
8489
|
+
const navs = await this.getNavigationsForMatterportIDForSpace(sweepID, zone.spaceID);
|
|
8490
|
+
if (navs.length > 0) {
|
|
8491
|
+
responseNavs.push(navs[0]);
|
|
8492
|
+
}
|
|
8493
|
+
}));
|
|
8494
|
+
return responseNavs;
|
|
8495
|
+
}
|
|
8496
|
+
async updateNavigation(navInput) {
|
|
8497
|
+
return this.API.__proto__.UpdateNavigation(navInput);
|
|
8498
|
+
}
|
|
8499
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NavigationService, deps: [{ token: 'currentAPIService' }, { token: BaseUserService }, { token: ZoneService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
8500
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NavigationService, providedIn: "root" });
|
|
8501
|
+
}
|
|
8502
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NavigationService, decorators: [{
|
|
8503
|
+
type: Injectable,
|
|
8504
|
+
args: [{
|
|
8505
|
+
providedIn: "root",
|
|
8506
|
+
}]
|
|
8507
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
8508
|
+
type: Inject,
|
|
8509
|
+
args: ['currentAPIService']
|
|
8510
|
+
}] }, { type: BaseUserService }, { type: ZoneService }] });
|
|
8511
|
+
|
|
8512
|
+
/**
|
|
8513
|
+
* Finds the Matterport sweep a point in a space sits closest to.
|
|
8514
|
+
*
|
|
8515
|
+
* A tag placed in Matterport is recorded with the sweep it was placed from, and much of the
|
|
8516
|
+
* Matterport side keys on that: which floor plan draws the tag, which zone it belongs to, which
|
|
8517
|
+
* floor it shows on. A tag placed in the Three.js viewer has no sweep, so it was missing from all
|
|
8518
|
+
* of them. Resolving the nearest one at creation - what the tag copy tool already does for copied
|
|
8519
|
+
* tags - lets every one of those readers work unchanged instead of each growing its own exception.
|
|
8520
|
+
*/
|
|
8521
|
+
class SweepLocatorService {
|
|
8522
|
+
captureService;
|
|
8523
|
+
navigationService;
|
|
8524
|
+
/**
|
|
8525
|
+
* Sweeps sit a few metres apart, so a point on a scanned surface always has one nearby. Anything
|
|
8526
|
+
* further means the coordinates are not in the Matterport capture's space - a model that was
|
|
8527
|
+
* never calibrated - and a sweep picked from that far would file the tag under an arbitrary
|
|
8528
|
+
* floor. Leaving it without one is the honest answer there.
|
|
8529
|
+
*/
|
|
8530
|
+
static MAX_DISTANCE_METRES = 10;
|
|
8531
|
+
/** Sweep positions per space. They only change on import, so one load per session is enough. */
|
|
8532
|
+
sweepsBySpace = new Map();
|
|
8533
|
+
constructor(captureService, navigationService) {
|
|
8534
|
+
this.captureService = captureService;
|
|
8535
|
+
this.navigationService = navigationService;
|
|
8536
|
+
}
|
|
8537
|
+
/**
|
|
8538
|
+
* The sweep nearest to a point, or null when the space has none within reach.
|
|
8539
|
+
*
|
|
8540
|
+
* Never throws: this only refines where a tag is filed, and a tag that cannot be filed is still
|
|
8541
|
+
* better saved without a sweep than not saved at all.
|
|
8542
|
+
*/
|
|
8543
|
+
async findNearestSweep(point, spaceID) {
|
|
8544
|
+
if (!point || !spaceID) {
|
|
8545
|
+
return null;
|
|
8546
|
+
}
|
|
8547
|
+
try {
|
|
8548
|
+
return this.nearestSweep(point, await this.loadSweeps(spaceID));
|
|
8549
|
+
}
|
|
8550
|
+
catch (error) {
|
|
8551
|
+
console.warn(`SweepLocatorService: could not resolve a sweep in space ${spaceID}`, error);
|
|
8552
|
+
return null;
|
|
8553
|
+
}
|
|
8554
|
+
}
|
|
8555
|
+
/** The sweep closest to a point, or null when none is within reach. */
|
|
8556
|
+
nearestSweep(point, sweeps) {
|
|
8557
|
+
let nearest = null;
|
|
8558
|
+
let nearestDistance = SweepLocatorService.MAX_DISTANCE_METRES;
|
|
8559
|
+
for (const sweep of sweeps) {
|
|
8560
|
+
const distance = Math.hypot(sweep.x - point.x, sweep.y - point.y, sweep.z - point.z);
|
|
8561
|
+
if (distance <= nearestDistance) {
|
|
8562
|
+
nearestDistance = distance;
|
|
8563
|
+
nearest = sweep.sweepID;
|
|
8564
|
+
}
|
|
8565
|
+
}
|
|
8566
|
+
return nearest;
|
|
8567
|
+
}
|
|
8568
|
+
loadSweeps(spaceID) {
|
|
8569
|
+
let sweeps = this.sweepsBySpace.get(spaceID);
|
|
8570
|
+
if (!sweeps) {
|
|
8571
|
+
sweeps = this.fetchSweeps(spaceID);
|
|
8572
|
+
this.sweepsBySpace.set(spaceID, sweeps);
|
|
8573
|
+
// A failed load must not be remembered as "this space has no sweeps".
|
|
8574
|
+
sweeps.catch(() => this.sweepsBySpace.delete(spaceID));
|
|
8575
|
+
}
|
|
8576
|
+
return sweeps;
|
|
8577
|
+
}
|
|
8578
|
+
async fetchSweeps(spaceID) {
|
|
8579
|
+
const captures = await this.captureService.getCapturesBySpace(spaceID);
|
|
8580
|
+
// A 3D model is imported with one navigation entry of its own, a placeholder sitting at the
|
|
8581
|
+
// origin. It is not a sweep, and counting it would make it the "nearest" for any tag close to
|
|
8582
|
+
// the origin.
|
|
8583
|
+
const nodeIDs = (captures ?? [])
|
|
8584
|
+
.filter((capture) => capture.viewer !== CaptureViewer.THREEJS)
|
|
8585
|
+
.reduce((all, capture) => all.concat(capture.nodes?.items ?? []), [])
|
|
8586
|
+
.map((node) => node?.id)
|
|
8587
|
+
.filter((id) => !!id);
|
|
8588
|
+
const navigations = (await Promise.all(nodeIDs.map((id) => this.navigationService.getNavigationsForNode(id)))).reduce((all, list) => all.concat(list), []);
|
|
8589
|
+
const sweeps = [];
|
|
8590
|
+
for (const navigation of navigations) {
|
|
8591
|
+
if (!navigation?.matterportSweepID || !navigation.position) {
|
|
8592
|
+
continue;
|
|
8593
|
+
}
|
|
8594
|
+
try {
|
|
8595
|
+
const position = JSON.parse(navigation.position);
|
|
8596
|
+
if ([position?.x, position?.y, position?.z].every((value) => typeof value === 'number')) {
|
|
8597
|
+
sweeps.push({
|
|
8598
|
+
sweepID: navigation.matterportSweepID,
|
|
8599
|
+
x: position.x,
|
|
8600
|
+
y: position.y,
|
|
8601
|
+
z: position.z,
|
|
8602
|
+
});
|
|
8603
|
+
}
|
|
8604
|
+
}
|
|
8605
|
+
catch {
|
|
8606
|
+
// One unreadable position should not cost the space all of its sweeps.
|
|
8607
|
+
}
|
|
8608
|
+
}
|
|
8609
|
+
return sweeps;
|
|
8610
|
+
}
|
|
8611
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: SweepLocatorService, deps: [{ token: CaptureService }, { token: NavigationService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
8612
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: SweepLocatorService, providedIn: 'root' });
|
|
8613
|
+
}
|
|
8614
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: SweepLocatorService, decorators: [{
|
|
8615
|
+
type: Injectable,
|
|
8616
|
+
args: [{
|
|
8617
|
+
providedIn: 'root',
|
|
8618
|
+
}]
|
|
8619
|
+
}], ctorParameters: () => [{ type: CaptureService }, { type: NavigationService }] });
|
|
8620
|
+
|
|
8621
|
+
/* eslint-disable no-await-in-loop */
|
|
8622
|
+
/* eslint-disable camelcase */
|
|
8623
|
+
/* eslint-disable no-underscore-dangle */
|
|
8624
|
+
/* eslint-disable class-methods-use-this */
|
|
8625
|
+
class ViewerService {
|
|
8626
|
+
sdkKey;
|
|
8627
|
+
config;
|
|
8628
|
+
poiService;
|
|
7360
8629
|
matterportService;
|
|
7361
8630
|
threeJsService;
|
|
8631
|
+
sweepLocator;
|
|
7362
8632
|
viewerMode = 'Matterport';
|
|
7363
8633
|
currentModel3D;
|
|
7364
8634
|
tourUrl;
|
|
@@ -7374,6 +8644,20 @@ class ViewerService {
|
|
|
7374
8644
|
* lists that feed tags are exactly that.
|
|
7375
8645
|
*/
|
|
7376
8646
|
currentSpaceID = null;
|
|
8647
|
+
/**
|
|
8648
|
+
* The Three.js capture on screen, or null in Matterport.
|
|
8649
|
+
*
|
|
8650
|
+
* A space can hold several 3D models. A tag placed in one is recorded with it, so that model
|
|
8651
|
+
* keeps showing the tag whatever selection of tags it is otherwise restricted to.
|
|
8652
|
+
*/
|
|
8653
|
+
currentCaptureID = null;
|
|
8654
|
+
/**
|
|
8655
|
+
* Whether the 3D model on screen has been calibrated against the tags' shared space.
|
|
8656
|
+
*
|
|
8657
|
+
* Calibrating is what says the model and that space overlap. Until then a tag placed in the model
|
|
8658
|
+
* is in the model's own coordinates: it belongs to no Matterport sweep and to no other visit.
|
|
8659
|
+
*/
|
|
8660
|
+
threeJsCalibrated = false;
|
|
7377
8661
|
sweepToMove;
|
|
7378
8662
|
rotationToMove;
|
|
7379
8663
|
goToTriggered = new Subject();
|
|
@@ -7402,12 +8686,13 @@ class ViewerService {
|
|
|
7402
8686
|
},
|
|
7403
8687
|
};
|
|
7404
8688
|
isObjectLoaded = false;
|
|
7405
|
-
constructor(sdkKey, config, poiService, matterportService, threeJsService) {
|
|
8689
|
+
constructor(sdkKey, config, poiService, matterportService, threeJsService, sweepLocator) {
|
|
7406
8690
|
this.sdkKey = sdkKey;
|
|
7407
8691
|
this.config = config;
|
|
7408
8692
|
this.poiService = poiService;
|
|
7409
8693
|
this.matterportService = matterportService;
|
|
7410
8694
|
this.threeJsService = threeJsService;
|
|
8695
|
+
this.sweepLocator = sweepLocator;
|
|
7411
8696
|
this.setMode('Matterport');
|
|
7412
8697
|
}
|
|
7413
8698
|
/*-----------------------------------------------------------------------------------------------------------------
|
|
@@ -7613,14 +8898,77 @@ class ViewerService {
|
|
|
7613
8898
|
if (![position?.x, position?.y, position?.z].every((value) => typeof value === 'number')) {
|
|
7614
8899
|
return;
|
|
7615
8900
|
}
|
|
8901
|
+
const origin = getThreeJsPoiOrigin(poi);
|
|
7616
8902
|
this.threeJsService.upsertTag({
|
|
7617
8903
|
id: object.id,
|
|
7618
8904
|
position,
|
|
7619
8905
|
poiType: poi.type,
|
|
7620
8906
|
label: object.name ?? object.title ?? null,
|
|
7621
8907
|
iconUrl: await this.resolveTagIconUrl(poi),
|
|
8908
|
+
originCaptureID: origin?.captureID ?? null,
|
|
8909
|
+
frame: origin?.frame ?? null,
|
|
7622
8910
|
});
|
|
7623
8911
|
}
|
|
8912
|
+
/**
|
|
8913
|
+
* Stores the tags the last calibration had to rewrite, and returns how many it could not.
|
|
8914
|
+
*
|
|
8915
|
+
* Calibrating keeps the tags placed in this model where they are by changing their coordinates
|
|
8916
|
+
* rather than their place (see ThreeJsService.applyAlignmentKeepingOwnTags). The scene already
|
|
8917
|
+
* shows the result; this writes it down. Call it once the calibration itself is stored - with the
|
|
8918
|
+
* old calibration still on record, the old coordinates are the ones that match it.
|
|
8919
|
+
*/
|
|
8920
|
+
async persistRebasedThreeJsTags() {
|
|
8921
|
+
const rebased = this.threeJsService.takeRebasedTags();
|
|
8922
|
+
let failed = 0;
|
|
8923
|
+
await Promise.all(rebased.map(async (tag) => {
|
|
8924
|
+
try {
|
|
8925
|
+
const poi = await this.poiService.getPoiByElementId(tag.id);
|
|
8926
|
+
if (!poi?.id) {
|
|
8927
|
+
return;
|
|
8928
|
+
}
|
|
8929
|
+
let metadata = {};
|
|
8930
|
+
try {
|
|
8931
|
+
metadata = (poi.metadata ? JSON.parse(poi.metadata) : {}) ?? {};
|
|
8932
|
+
}
|
|
8933
|
+
catch {
|
|
8934
|
+
metadata = {};
|
|
8935
|
+
}
|
|
8936
|
+
// In the shared space from here on, so no longer marked as being in the model's own.
|
|
8937
|
+
delete metadata.frame;
|
|
8938
|
+
if (metadata.normal) {
|
|
8939
|
+
metadata.normal = tag.rotateDirection(metadata.normal);
|
|
8940
|
+
}
|
|
8941
|
+
if (this.currentCaptureID) {
|
|
8942
|
+
metadata.captureID = this.currentCaptureID;
|
|
8943
|
+
}
|
|
8944
|
+
await this.poiService.updatePoi({
|
|
8945
|
+
id: poi.id,
|
|
8946
|
+
coordinate: JSON.stringify(tag.position),
|
|
8947
|
+
metadata: JSON.stringify(metadata),
|
|
8948
|
+
// Its place in the tour may have changed with its coordinates - or it may have one for
|
|
8949
|
+
// the first time, now that the model is calibrated.
|
|
8950
|
+
matterportSweepID: await this.findNearestSweepForThreeJs(tag.position),
|
|
8951
|
+
});
|
|
8952
|
+
}
|
|
8953
|
+
catch (error) {
|
|
8954
|
+
failed += 1;
|
|
8955
|
+
console.warn(`ViewerService: could not store the new coordinates of tag ${tag.id}`, error);
|
|
8956
|
+
}
|
|
8957
|
+
}));
|
|
8958
|
+
return { stored: rebased.length - failed, failed };
|
|
8959
|
+
}
|
|
8960
|
+
/** How many tags the calibration walkthrough could still use: the ones from elsewhere. */
|
|
8961
|
+
getThreeJsCalibrationCandidateCount() {
|
|
8962
|
+
return this.checkMode('threejs') ? this.threeJsService.getCalibrationCandidateCount() : 0;
|
|
8963
|
+
}
|
|
8964
|
+
/** How many tags from elsewhere are not on the 3D model, and so are not shown on it. */
|
|
8965
|
+
getThreeJsOffModelTagCount() {
|
|
8966
|
+
return this.checkMode('threejs') ? this.threeJsService.getOffModelTagCount() : 0;
|
|
8967
|
+
}
|
|
8968
|
+
/** The tags registered in the Three.js scene, shown or not. */
|
|
8969
|
+
getThreeJsTags() {
|
|
8970
|
+
return this.checkMode('threejs') ? this.threeJsService.getTags() : [];
|
|
8971
|
+
}
|
|
7624
8972
|
/**
|
|
7625
8973
|
* The image to draw a tag with, mirroring what Matterport shows for the same POI.
|
|
7626
8974
|
*
|
|
@@ -7713,6 +9061,7 @@ class ViewerService {
|
|
|
7713
9061
|
if (!alignment) {
|
|
7714
9062
|
return null;
|
|
7715
9063
|
}
|
|
9064
|
+
this.threeJsCalibrated = true;
|
|
7716
9065
|
// Merged into whatever the capture already stores, so an existing camera placement survives.
|
|
7717
9066
|
let stored = {};
|
|
7718
9067
|
try {
|
|
@@ -7767,7 +9116,11 @@ class ViewerService {
|
|
|
7767
9116
|
if (!this.checkMode('threejs')) {
|
|
7768
9117
|
return;
|
|
7769
9118
|
}
|
|
7770
|
-
|
|
9119
|
+
const alignment = parseThreeJsPlacement(metadata)?.alignment ?? null;
|
|
9120
|
+
this.threeJsCalibrated = !!alignment;
|
|
9121
|
+
// Which tags were placed in this model is decided against the capture on screen.
|
|
9122
|
+
this.threeJsService.setCurrentCapture(this.currentCaptureID);
|
|
9123
|
+
this.threeJsService.setTagAlignment(alignment);
|
|
7771
9124
|
}
|
|
7772
9125
|
/**
|
|
7773
9126
|
* Enable tag placement menu to add a new element depending on its type (TICKET, DATA, EQUIPMENT etc)
|
|
@@ -7836,22 +9189,116 @@ class ViewerService {
|
|
|
7836
9189
|
if (this.checkMode('Matterport')) {
|
|
7837
9190
|
await this.matterportService.action_toolbox_floorplan();
|
|
7838
9191
|
}
|
|
7839
|
-
}
|
|
7840
|
-
action_toolbox_mesure() {
|
|
7841
|
-
if (this.matterportService.getInteractionMode() !== ViewerInteractions.DEFAULT) {
|
|
7842
|
-
this.action_canceled();
|
|
9192
|
+
}
|
|
9193
|
+
action_toolbox_mesure() {
|
|
9194
|
+
if (this.matterportService.getInteractionMode() !== ViewerInteractions.DEFAULT) {
|
|
9195
|
+
this.action_canceled();
|
|
9196
|
+
}
|
|
9197
|
+
this.action_toolbox_inside_view();
|
|
9198
|
+
// Matterport
|
|
9199
|
+
if (this.checkMode('Matterport')) {
|
|
9200
|
+
this.matterportService.action_toolbox_mesure();
|
|
9201
|
+
}
|
|
9202
|
+
// Matterport's SDK brings its own measuring tool; the Three.js viewer has to provide one.
|
|
9203
|
+
if (this.checkMode('threejs')) {
|
|
9204
|
+
this.threeJsService.startMeasuring();
|
|
9205
|
+
}
|
|
9206
|
+
}
|
|
9207
|
+
action_toolbox_cancel_mesure() {
|
|
9208
|
+
// Matterport
|
|
9209
|
+
if (this.checkMode('Matterport')) {
|
|
9210
|
+
this.matterportService.action_toolbox_cancel_mesure();
|
|
9211
|
+
}
|
|
9212
|
+
if (this.checkMode('threejs')) {
|
|
9213
|
+
this.threeJsService.cancelMeasuring();
|
|
9214
|
+
// The toolbar toggles on this flag; left on, the next click would switch measuring "off"
|
|
9215
|
+
// instead of starting a new one.
|
|
9216
|
+
this.toolboxStates.mesure.stat = false;
|
|
9217
|
+
}
|
|
9218
|
+
}
|
|
9219
|
+
/** While measuring in the Three.js viewer: points and segment lengths so far, null if abandoned. */
|
|
9220
|
+
get threeJsMeasurementProgress$() {
|
|
9221
|
+
return this.threeJsService.measurementProgress$;
|
|
9222
|
+
}
|
|
9223
|
+
/** Ends the Three.js measurement in progress, or returns null when it has fewer than two points. */
|
|
9224
|
+
finishThreeJsMeasurement() {
|
|
9225
|
+
if (!this.checkMode('threejs')) {
|
|
9226
|
+
return null;
|
|
9227
|
+
}
|
|
9228
|
+
const measurement = this.threeJsService.finishMeasuring();
|
|
9229
|
+
if (measurement) {
|
|
9230
|
+
this.toolboxStates.mesure.stat = false;
|
|
9231
|
+
}
|
|
9232
|
+
return measurement;
|
|
9233
|
+
}
|
|
9234
|
+
/** The measurement last finished in the Three.js viewer, for the form that saves it. */
|
|
9235
|
+
getThreeJsLastMeasurement() {
|
|
9236
|
+
return this.checkMode('threejs') ? this.threeJsService.getLastMeasurement() : null;
|
|
9237
|
+
}
|
|
9238
|
+
/**
|
|
9239
|
+
* The Matterport sweep nearest to a point picked in the Three.js viewer, or null.
|
|
9240
|
+
*
|
|
9241
|
+
* For callers that write their own POI, as the measurement form does - anything created through
|
|
9242
|
+
* createPoiForThreeJsPlacement already gets this.
|
|
9243
|
+
*/
|
|
9244
|
+
findNearestSweepForThreeJs(point) {
|
|
9245
|
+
// An uncalibrated model's coordinates are its own. Measured against the Matterport sweeps they
|
|
9246
|
+
// could land near one by coincidence, and the tag would turn up in the tour somewhere arbitrary.
|
|
9247
|
+
if (!this.threeJsCalibrated) {
|
|
9248
|
+
return Promise.resolve(null);
|
|
9249
|
+
}
|
|
9250
|
+
return this.sweepLocator.findNearestSweep(point, this.currentSpaceID);
|
|
9251
|
+
}
|
|
9252
|
+
/**
|
|
9253
|
+
* The metadata a POI placed in the Three.js viewer is stored with: which 3D model it was placed
|
|
9254
|
+
* in, and - when that model is not calibrated - that its coordinates are the model's own.
|
|
9255
|
+
*/
|
|
9256
|
+
buildThreeJsPoiMetadata(extra = {}) {
|
|
9257
|
+
return JSON.stringify({
|
|
9258
|
+
...extra,
|
|
9259
|
+
...(this.currentCaptureID ? { captureID: this.currentCaptureID } : {}),
|
|
9260
|
+
...(this.currentCaptureID && !this.threeJsCalibrated ? { frame: 'model' } : {}),
|
|
9261
|
+
});
|
|
9262
|
+
}
|
|
9263
|
+
/** Which of the 3D model's axes is being treated as up. */
|
|
9264
|
+
getThreeJsModelUp() {
|
|
9265
|
+
return this.threeJsService.getModelUp();
|
|
9266
|
+
}
|
|
9267
|
+
/**
|
|
9268
|
+
* Turns the 3D model to the next candidate for "which axis is up", and returns the capture
|
|
9269
|
+
* metadata with that choice written in, for the caller to store.
|
|
9270
|
+
*
|
|
9271
|
+
* The vertical axis is worked out from the geometry when a model loads, and that can be wrong:
|
|
9272
|
+
* a narrow corridor and a low slab are the same shape turned over. This is the way to correct it.
|
|
9273
|
+
*/
|
|
9274
|
+
cycleThreeJsModelUp(existingMetadata) {
|
|
9275
|
+
if (!this.checkMode('threejs')) {
|
|
9276
|
+
return null;
|
|
9277
|
+
}
|
|
9278
|
+
const current = THREEJS_MODEL_UP_OPTIONS.indexOf(this.threeJsService.getModelUp());
|
|
9279
|
+
const up = THREEJS_MODEL_UP_OPTIONS[(current + 1) % THREEJS_MODEL_UP_OPTIONS.length];
|
|
9280
|
+
this.threeJsService.setModelUp(up);
|
|
9281
|
+
let stored = {};
|
|
9282
|
+
try {
|
|
9283
|
+
stored = existingMetadata ? JSON.parse(existingMetadata) : {};
|
|
7843
9284
|
}
|
|
7844
|
-
|
|
7845
|
-
|
|
7846
|
-
if (this.checkMode('Matterport')) {
|
|
7847
|
-
this.matterportService.action_toolbox_mesure();
|
|
9285
|
+
catch {
|
|
9286
|
+
stored = {};
|
|
7848
9287
|
}
|
|
9288
|
+
stored.up = up;
|
|
9289
|
+
return { up, metadata: JSON.stringify(stored) };
|
|
7849
9290
|
}
|
|
7850
|
-
|
|
7851
|
-
|
|
7852
|
-
|
|
7853
|
-
|
|
7854
|
-
|
|
9291
|
+
/** Chooses the tag for the calibration pair being matched by id, instead of by clicking it. */
|
|
9292
|
+
selectThreeJsAlignmentTag(elementId) {
|
|
9293
|
+
return this.checkMode('threejs') && this.threeJsService.selectAlignmentTag(elementId);
|
|
9294
|
+
}
|
|
9295
|
+
/** How far the visible tags sit from the 3D model, in model radii. Null without both. */
|
|
9296
|
+
getThreeJsTagDistanceFromModel() {
|
|
9297
|
+
return this.checkMode('threejs') ? this.threeJsService.getTagDistanceFromModel() : null;
|
|
9298
|
+
}
|
|
9299
|
+
/** How many tags the Three.js scene is showing. */
|
|
9300
|
+
getThreeJsTagCount() {
|
|
9301
|
+
return this.checkMode('threejs') ? this.threeJsService.getTagCount() : 0;
|
|
7855
9302
|
}
|
|
7856
9303
|
async action_update_poi(poi, isValid, element) {
|
|
7857
9304
|
if (this.checkMode('Matterport')) {
|
|
@@ -7941,11 +9388,15 @@ class ViewerService {
|
|
|
7941
9388
|
if (!placement || !element?.id) {
|
|
7942
9389
|
return null;
|
|
7943
9390
|
}
|
|
9391
|
+
// Without a sweep the tag is left off everything on the Matterport side that is organised by
|
|
9392
|
+
// sweep - the floor plan most visibly, which only draws tags whose sweep belongs to it.
|
|
9393
|
+
const matterportSweepID = await this.findNearestSweepForThreeJs(placement.position);
|
|
7944
9394
|
const poi = await this.poiService.createPOIForSpaceID({
|
|
7945
9395
|
coordinate: JSON.stringify(placement.position),
|
|
7946
9396
|
type: poiType,
|
|
7947
9397
|
elementID: element.id,
|
|
7948
|
-
metadata:
|
|
9398
|
+
metadata: this.buildThreeJsPoiMetadata({ normal: placement.normal }),
|
|
9399
|
+
...(matterportSweepID ? { matterportSweepID } : {}),
|
|
7949
9400
|
});
|
|
7950
9401
|
// Draw it straight away rather than waiting for the next refresh, so the user sees where the
|
|
7951
9402
|
// thing they just created ended up. Through the same path the refresh uses, so it arrives with
|
|
@@ -8075,7 +9526,7 @@ class ViewerService {
|
|
|
8075
9526
|
const placement = parseThreeJsPlacement(placementMetadata);
|
|
8076
9527
|
// The key, not the signed URL, is what identifies the model across loads - the signature makes
|
|
8077
9528
|
// every URL unique, which is exactly why the browser's own cache never helps here.
|
|
8078
|
-
this.threeJsService.loadGLTFModel(url, placement?.cameraPosition, placement?.rotation, key);
|
|
9529
|
+
this.threeJsService.loadGLTFModel(url, placement?.cameraPosition, placement?.rotation, key, placement?.up);
|
|
8079
9530
|
}
|
|
8080
9531
|
/**
|
|
8081
9532
|
* Loads a 3D model into the Three.js scene (OBJ).
|
|
@@ -8170,7 +9621,7 @@ class ViewerService {
|
|
|
8170
9621
|
isObject3DLoadedInPosition() {
|
|
8171
9622
|
return this.isObjectLoaded;
|
|
8172
9623
|
}
|
|
8173
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ViewerService, deps: [{ token: MATTERPORT_SDK_KEY }, { token: 'config' }, { token: PoiService }, { token: MatterportService }, { token: ThreeJsService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
9624
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ViewerService, deps: [{ token: MATTERPORT_SDK_KEY }, { token: 'config' }, { token: PoiService }, { token: MatterportService }, { token: ThreeJsService }, { token: SweepLocatorService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
8174
9625
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ViewerService, providedIn: 'root' });
|
|
8175
9626
|
}
|
|
8176
9627
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ViewerService, decorators: [{
|
|
@@ -8184,7 +9635,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
8184
9635
|
}] }, { type: Config, decorators: [{
|
|
8185
9636
|
type: Inject,
|
|
8186
9637
|
args: ['config']
|
|
8187
|
-
}] }, { type: PoiService }, { type: MatterportService }, { type: ThreeJsService }] });
|
|
9638
|
+
}] }, { type: PoiService }, { type: MatterportService }, { type: ThreeJsService }, { type: SweepLocatorService }] });
|
|
8188
9639
|
|
|
8189
9640
|
class ModalSwitchVisitComponent {
|
|
8190
9641
|
activeModal;
|
|
@@ -8212,11 +9663,11 @@ class ModalSwitchVisitComponent {
|
|
|
8212
9663
|
});
|
|
8213
9664
|
}
|
|
8214
9665
|
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 });
|
|
8215
|
-
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\">\
|
|
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" }] });
|
|
8216
9667
|
}
|
|
8217
9668
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ModalSwitchVisitComponent, decorators: [{
|
|
8218
9669
|
type: Component,
|
|
8219
|
-
args: [{ selector: 'lib-modal-switch-visit', template: "<div class=\"modal-container\">\
|
|
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"] }]
|
|
8220
9671
|
}], ctorParameters: () => [{ type: i1$2.NgbActiveModal }, { type: i1$1.Router }, { type: ViewerService }], propDecorators: { content: [{
|
|
8221
9672
|
type: Input
|
|
8222
9673
|
}], model3D: [{
|
|
@@ -8227,6 +9678,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
8227
9678
|
type: Input
|
|
8228
9679
|
}] } });
|
|
8229
9680
|
|
|
9681
|
+
/**
|
|
9682
|
+
* Where a POI placed in a Three.js model says it was placed, or null for any other POI.
|
|
9683
|
+
*
|
|
9684
|
+
* `frame` is 'model' when its coordinates are in that model's own space - it was placed before the
|
|
9685
|
+
* model was calibrated - rather than in the space tags share with the other visits.
|
|
9686
|
+
*/
|
|
9687
|
+
function getThreeJsPoiOrigin(poi) {
|
|
9688
|
+
if (!poi?.metadata) {
|
|
9689
|
+
return null;
|
|
9690
|
+
}
|
|
9691
|
+
try {
|
|
9692
|
+
const parsed = JSON.parse(poi.metadata);
|
|
9693
|
+
return typeof parsed?.captureID === "string"
|
|
9694
|
+
? { captureID: parsed.captureID, frame: parsed.frame === "model" ? "model" : null }
|
|
9695
|
+
: null;
|
|
9696
|
+
}
|
|
9697
|
+
catch {
|
|
9698
|
+
return null;
|
|
9699
|
+
}
|
|
9700
|
+
}
|
|
9701
|
+
const THREEJS_MODEL_UP_OPTIONS = ["y", "z", "x", "-y", "-z", "-x"];
|
|
8230
9702
|
/**
|
|
8231
9703
|
* Parses the placement stored in a Three.js capture's metadata, or null when there is none.
|
|
8232
9704
|
*
|
|
@@ -8265,7 +9737,12 @@ function parseThreeJsPlacement(metadata) {
|
|
|
8265
9737
|
placement.alignment = alignment;
|
|
8266
9738
|
}
|
|
8267
9739
|
}
|
|
8268
|
-
|
|
9740
|
+
if (THREEJS_MODEL_UP_OPTIONS.includes(parsed.up)) {
|
|
9741
|
+
placement.up = parsed.up;
|
|
9742
|
+
}
|
|
9743
|
+
return placement.cameraPosition || placement.rotation || placement.alignment || placement.up
|
|
9744
|
+
? placement
|
|
9745
|
+
: null;
|
|
8269
9746
|
}
|
|
8270
9747
|
catch {
|
|
8271
9748
|
console.warn("Ignoring unparseable Three.js placement metadata:", metadata);
|
|
@@ -8690,285 +10167,57 @@ function shuffleArray(array) {
|
|
|
8690
10167
|
const newArray = [...array];
|
|
8691
10168
|
for (let index = array.length - 1; index > 0; index--) {
|
|
8692
10169
|
const indexNew = Math.floor(Math.random() * (index + 1));
|
|
8693
|
-
// eslint-disable-next-line no-param-reassign
|
|
8694
|
-
[newArray[index], newArray[indexNew]] = [
|
|
8695
|
-
newArray[indexNew],
|
|
8696
|
-
newArray[index],
|
|
8697
|
-
];
|
|
8698
|
-
}
|
|
8699
|
-
return newArray;
|
|
8700
|
-
}
|
|
8701
|
-
function numberToDateString(timestamp) {
|
|
8702
|
-
const jsDate = new Date(timestamp);
|
|
8703
|
-
return jsDate.toLocaleString("fr-FR");
|
|
8704
|
-
}
|
|
8705
|
-
function durationToString(duration, translate) {
|
|
8706
|
-
const days = Math.floor(duration / 3600 / 24);
|
|
8707
|
-
const hours = Math.floor((duration - days * 24 * 3600) / 3600);
|
|
8708
|
-
const minutes = Math.floor((duration % 3600) / 60);
|
|
8709
|
-
const seconds = Math.floor(duration % 60);
|
|
8710
|
-
const daysString = translate.instant("day(s)");
|
|
8711
|
-
const hoursString = translate.instant("hour(s)");
|
|
8712
|
-
const oneHourString = translate.instant("hour");
|
|
8713
|
-
const minutesString = translate.instant("minute(s)");
|
|
8714
|
-
const secondsString = translate.instant("second(s)");
|
|
8715
|
-
if (duration > 3600 * 24 * 7) {
|
|
8716
|
-
return `${days} ${daysString}`;
|
|
8717
|
-
}
|
|
8718
|
-
if (duration > 3600 * 24) {
|
|
8719
|
-
return `${days} ${daysString} ${hours} ${hoursString}`;
|
|
8720
|
-
}
|
|
8721
|
-
if (duration === 3600) {
|
|
8722
|
-
return `${hours} ${oneHourString}`;
|
|
8723
|
-
}
|
|
8724
|
-
if (duration > 3600) {
|
|
8725
|
-
if (minutes === 0) {
|
|
8726
|
-
return `${hours} ${hoursString}`;
|
|
8727
|
-
}
|
|
8728
|
-
return `${hours} ${hoursString} ${minutes} ${minutesString}`;
|
|
8729
|
-
}
|
|
8730
|
-
if (duration >= 60) {
|
|
8731
|
-
if (seconds === 0) {
|
|
8732
|
-
return `${minutes} ${minutesString}`;
|
|
8733
|
-
}
|
|
8734
|
-
return `${minutes} ${minutesString} ${seconds} ${secondsString}`;
|
|
8735
|
-
}
|
|
8736
|
-
return `${seconds} ${secondsString}`;
|
|
8737
|
-
}
|
|
8738
|
-
function pointToString(point) {
|
|
8739
|
-
const x = point.x.toFixed(3);
|
|
8740
|
-
const y = point.y.toFixed(3);
|
|
8741
|
-
const z = point.z.toFixed(3);
|
|
8742
|
-
return `{ x: ${x}, y: ${y}, z: ${z} }`;
|
|
8743
|
-
}
|
|
8744
|
-
|
|
8745
|
-
/* eslint-disable class-methods-use-this */
|
|
8746
|
-
class ZoneService {
|
|
8747
|
-
userService;
|
|
8748
|
-
API; //AWS APIService
|
|
8749
|
-
constructor(apiInjected, userService) {
|
|
8750
|
-
this.userService = userService;
|
|
8751
|
-
this.API = apiInjected;
|
|
8752
|
-
}
|
|
8753
|
-
async getZonesBySpace(spaceID) {
|
|
8754
|
-
const list = [];
|
|
8755
|
-
if (this.API.__proto__.hasOwnProperty('ZonesBySpace')) {
|
|
8756
|
-
// Paginated: without a nextToken loop, only the first page (100 zones) was returned and
|
|
8757
|
-
// the sweeps of the other zones silently disappeared.
|
|
8758
|
-
let nextToken = null;
|
|
8759
|
-
do {
|
|
8760
|
-
const zones = await this.API.__proto__.ZonesBySpace(spaceID, null, null, 1000, nextToken);
|
|
8761
|
-
for (const zone of zones?.items || []) {
|
|
8762
|
-
const zoneObject = { ...zone };
|
|
8763
|
-
zoneObject.virtual = this.zoneIsVirtual(zone);
|
|
8764
|
-
list.push(zoneObject);
|
|
8765
|
-
}
|
|
8766
|
-
nextToken = zones?.nextToken ?? null;
|
|
8767
|
-
} while (nextToken);
|
|
8768
|
-
return list;
|
|
8769
|
-
}
|
|
8770
|
-
else {
|
|
8771
|
-
throw new Error('ZonesBySpace not implemented');
|
|
8772
|
-
}
|
|
8773
|
-
}
|
|
8774
|
-
async getZonesBySpaceForUser(spaceID) {
|
|
8775
|
-
const missionForLocation = this.userService.currentMission(spaceID);
|
|
8776
|
-
const list = [];
|
|
8777
|
-
if (missionForLocation && missionForLocation.zoneID) {
|
|
8778
|
-
const allowedZone = await this.getZone(missionForLocation.zoneID);
|
|
8779
|
-
allowedZone.virtual = this.zoneIsVirtual(allowedZone);
|
|
8780
|
-
if (!allowedZone.parent) {
|
|
8781
|
-
// the whole building is authorized
|
|
8782
|
-
return this.getZonesBySpace(spaceID);
|
|
8783
|
-
}
|
|
8784
|
-
list.push(allowedZone);
|
|
8785
|
-
// allow all children zones of an allowed parent
|
|
8786
|
-
const children = (await this.API.__proto__.ZonesByZone(allowedZone.id))
|
|
8787
|
-
.items;
|
|
8788
|
-
if (children.length > 0) {
|
|
8789
|
-
for (const child of children) {
|
|
8790
|
-
const childZone = { ...child };
|
|
8791
|
-
childZone.virtual = this.zoneIsVirtual(child);
|
|
8792
|
-
list.push(childZone);
|
|
8793
|
-
}
|
|
8794
|
-
}
|
|
8795
|
-
// allow the parent Floor of the allowed zone
|
|
8796
|
-
if (allowedZone.parent.layer &&
|
|
8797
|
-
allowedZone.parent.layer.name === 'FLOOR') {
|
|
8798
|
-
list.push(allowedZone.parent);
|
|
8799
|
-
}
|
|
8800
|
-
}
|
|
8801
|
-
else {
|
|
8802
|
-
return this.getZonesBySpace(spaceID);
|
|
8803
|
-
}
|
|
8804
|
-
return list;
|
|
8805
|
-
}
|
|
8806
|
-
async filterZonesIDsForUser(zones) {
|
|
8807
|
-
const { spaceID } = zones[0];
|
|
8808
|
-
const zoneIDs = new Set(zones.map((zone) => zone.id));
|
|
8809
|
-
const allowedZones = await this.getZonesBySpaceForUser(spaceID);
|
|
8810
|
-
const filteredZones = allowedZones.filter((zone) => zoneIDs.has(zone.id));
|
|
8811
|
-
return filteredZones;
|
|
8812
|
-
}
|
|
8813
|
-
async deleteZone(zoneID) {
|
|
8814
|
-
return this.API.__proto__.DeleteZone({ id: zoneID });
|
|
8815
|
-
}
|
|
8816
|
-
async getZonesForSweepForSpace(sweepID, spaceID) {
|
|
8817
|
-
const zonesSpace = (await this.API.__proto__.ZonesBySpace(spaceID)).items;
|
|
8818
|
-
return zonesSpace.filter((zone) => zone.sweepIDs && zone.sweepIDs.includes(sweepID));
|
|
8819
|
-
}
|
|
8820
|
-
// // eslint-disable-next-line class-methods-use-this
|
|
8821
|
-
sortZonesFromChildToParent(zonesToSort) {
|
|
8822
|
-
// get default
|
|
8823
|
-
const defaultZone = zonesToSort.find((z) => !z.parentID);
|
|
8824
|
-
if (defaultZone) {
|
|
8825
|
-
const indexOf = zonesToSort.indexOf(defaultZone);
|
|
8826
|
-
zonesToSort.splice(indexOf, 1);
|
|
8827
|
-
}
|
|
8828
|
-
const zones = zonesToSort.sort((a, b) => {
|
|
8829
|
-
if (a.parentID === b.id) {
|
|
8830
|
-
return -1;
|
|
8831
|
-
}
|
|
8832
|
-
return 0;
|
|
8833
|
-
});
|
|
8834
|
-
// so far by number of navigation points in them
|
|
8835
|
-
// const zones = zonesToSort.sort(
|
|
8836
|
-
// (a, b) => a.navigations.items.length - b.navigations.items.length,
|
|
8837
|
-
// );
|
|
8838
|
-
if (defaultZone) {
|
|
8839
|
-
zones.push(defaultZone);
|
|
8840
|
-
}
|
|
8841
|
-
return zones;
|
|
8842
|
-
}
|
|
8843
|
-
async getZonesForObject(object) {
|
|
8844
|
-
if (object.pois.items && object.pois.items.length > 0) {
|
|
8845
|
-
const [poi] = object.pois.items;
|
|
8846
|
-
if (poi.matterportSweepID) {
|
|
8847
|
-
const zones = await this.getZonesForSweepForSpace(poi.matterportSweepID, object.spaceID);
|
|
8848
|
-
return zones;
|
|
8849
|
-
}
|
|
8850
|
-
}
|
|
8851
|
-
return null;
|
|
8852
|
-
}
|
|
8853
|
-
async createVirtualZone(zoneInput) {
|
|
8854
|
-
zoneInput.sweepIDs = [];
|
|
8855
|
-
const zone = await this.API.__proto__.CreateZone(zoneInput);
|
|
8856
|
-
console.log('virtual zone created', zone);
|
|
8857
|
-
return zone;
|
|
8858
|
-
}
|
|
8859
|
-
zoneIsVirtual(zone) {
|
|
8860
|
-
if (!zone.sweepIDs) {
|
|
8861
|
-
return true;
|
|
8862
|
-
}
|
|
8863
|
-
if ((zone.tags && zone.tags.includes('virtual')) ||
|
|
8864
|
-
zone.sweepIDs.length === 0) {
|
|
8865
|
-
return true;
|
|
8866
|
-
}
|
|
8867
|
-
return false;
|
|
8868
|
-
}
|
|
8869
|
-
async getModel3DForZone(zoneID) {
|
|
8870
|
-
const zone = await this.API.__proto__.GetZone(zoneID);
|
|
8871
|
-
if (zone.nodeIDs) {
|
|
8872
|
-
const node = await this.API.__proto__.GetNode(zone.nodeIDs[0]);
|
|
8873
|
-
return node.model3D;
|
|
8874
|
-
}
|
|
8875
|
-
return null;
|
|
8876
|
-
}
|
|
8877
|
-
async getZone(zoneID) {
|
|
8878
|
-
return this.API.__proto__.GetZone(zoneID);
|
|
8879
|
-
}
|
|
8880
|
-
async getDefaultZoneForSpace(spaceID) {
|
|
8881
|
-
return this.getZonesBySpace(spaceID).then(async (zones) => {
|
|
8882
|
-
const zonesBuilding = zones.filter((zone) => zone.layer && zone.layer.name === 'BUILDING');
|
|
8883
|
-
if (zonesBuilding.length === 1) {
|
|
8884
|
-
return zonesBuilding[0];
|
|
8885
|
-
}
|
|
8886
|
-
const parentZone = zones.find((zone) => !zone.parentID);
|
|
8887
|
-
return parentZone;
|
|
8888
|
-
});
|
|
8889
|
-
}
|
|
8890
|
-
async create(input) {
|
|
8891
|
-
return this.API.__proto__.CreateZone(input);
|
|
8892
|
-
}
|
|
8893
|
-
// async getSortedZonesForSweepForUser(
|
|
8894
|
-
// sweep: string,
|
|
8895
|
-
// spaceID: string,
|
|
8896
|
-
// ): Promise<Zone[]> {
|
|
8897
|
-
// if (!spaceID) {
|
|
8898
|
-
// return [];
|
|
8899
|
-
// }
|
|
8900
|
-
// const zones = await this.getZonesForSweepForSpace(sweep, spaceID);
|
|
8901
|
-
// if (zones.length === 0) {
|
|
8902
|
-
// return [];
|
|
8903
|
-
// }
|
|
8904
|
-
// const filteredZones = await this.filterZonesIDsForUser(zones);
|
|
8905
|
-
// if (filteredZones.length > 0) {
|
|
8906
|
-
// let zonesSorted = this.sortZonesFromChildToParent(filteredZones);
|
|
8907
|
-
// zonesSorted = zonesSorted.filter((zone) => zone !== undefined);
|
|
8908
|
-
// return zonesSorted;
|
|
8909
|
-
// }
|
|
8910
|
-
// return [];
|
|
8911
|
-
// }
|
|
8912
|
-
async updateZone(input) {
|
|
8913
|
-
return this.API.__proto__.UpdateZone(input);
|
|
10170
|
+
// eslint-disable-next-line no-param-reassign
|
|
10171
|
+
[newArray[index], newArray[indexNew]] = [
|
|
10172
|
+
newArray[indexNew],
|
|
10173
|
+
newArray[index],
|
|
10174
|
+
];
|
|
8914
10175
|
}
|
|
8915
|
-
|
|
8916
|
-
|
|
8917
|
-
|
|
8918
|
-
|
|
8919
|
-
|
|
10176
|
+
return newArray;
|
|
10177
|
+
}
|
|
10178
|
+
function numberToDateString(timestamp) {
|
|
10179
|
+
const jsDate = new Date(timestamp);
|
|
10180
|
+
return jsDate.toLocaleString("fr-FR");
|
|
10181
|
+
}
|
|
10182
|
+
function durationToString(duration, translate) {
|
|
10183
|
+
const days = Math.floor(duration / 3600 / 24);
|
|
10184
|
+
const hours = Math.floor((duration - days * 24 * 3600) / 3600);
|
|
10185
|
+
const minutes = Math.floor((duration % 3600) / 60);
|
|
10186
|
+
const seconds = Math.floor(duration % 60);
|
|
10187
|
+
const daysString = translate.instant("day(s)");
|
|
10188
|
+
const hoursString = translate.instant("hour(s)");
|
|
10189
|
+
const oneHourString = translate.instant("hour");
|
|
10190
|
+
const minutesString = translate.instant("minute(s)");
|
|
10191
|
+
const secondsString = translate.instant("second(s)");
|
|
10192
|
+
if (duration > 3600 * 24 * 7) {
|
|
10193
|
+
return `${days} ${daysString}`;
|
|
8920
10194
|
}
|
|
8921
|
-
|
|
8922
|
-
|
|
8923
|
-
* @param sweepID
|
|
8924
|
-
* @param spaceID
|
|
8925
|
-
* @returns Zone
|
|
8926
|
-
*/
|
|
8927
|
-
async getFloorZoneForSweepInSpace(sweepID, spaceID) {
|
|
8928
|
-
const zones = await this.getZonesForSweepForSpace(sweepID, spaceID);
|
|
8929
|
-
const floors = zones.filter((zone) => zone.layer && zone.layer.name.includes('FLOOR'));
|
|
8930
|
-
return floors.length > 0 ? floors[0] : null;
|
|
10195
|
+
if (duration > 3600 * 24) {
|
|
10196
|
+
return `${days} ${daysString} ${hours} ${hoursString}`;
|
|
8931
10197
|
}
|
|
8932
|
-
|
|
8933
|
-
|
|
8934
|
-
* @param spaceID
|
|
8935
|
-
* @returns Promise<Zone[]>
|
|
8936
|
-
*/
|
|
8937
|
-
async getAudioZonesForSpace(spaceID) {
|
|
8938
|
-
const allZones = await this.getZonesBySpace(spaceID);
|
|
8939
|
-
return allZones.filter((zone) => zone.audioID);
|
|
10198
|
+
if (duration === 3600) {
|
|
10199
|
+
return `${hours} ${oneHourString}`;
|
|
8940
10200
|
}
|
|
8941
|
-
|
|
8942
|
-
|
|
8943
|
-
|
|
8944
|
-
|
|
8945
|
-
|
|
8946
|
-
async getAudioZonesForUserForSpace(spaceID) {
|
|
8947
|
-
const userZones = await this.getZonesBySpaceForUser(spaceID);
|
|
8948
|
-
return userZones.filter((zone) => zone.audioID);
|
|
10201
|
+
if (duration > 3600) {
|
|
10202
|
+
if (minutes === 0) {
|
|
10203
|
+
return `${hours} ${hoursString}`;
|
|
10204
|
+
}
|
|
10205
|
+
return `${hours} ${hoursString} ${minutes} ${minutesString}`;
|
|
8949
10206
|
}
|
|
8950
|
-
|
|
8951
|
-
|
|
8952
|
-
|
|
8953
|
-
|
|
8954
|
-
|
|
8955
|
-
async getZonesByParentZone(parentZoneID) {
|
|
8956
|
-
return this.API.__proto__
|
|
8957
|
-
.ZonesByZone(parentZoneID)
|
|
8958
|
-
.then((res) => res.items);
|
|
10207
|
+
if (duration >= 60) {
|
|
10208
|
+
if (seconds === 0) {
|
|
10209
|
+
return `${minutes} ${minutesString}`;
|
|
10210
|
+
}
|
|
10211
|
+
return `${minutes} ${minutesString} ${seconds} ${secondsString}`;
|
|
8959
10212
|
}
|
|
8960
|
-
|
|
8961
|
-
|
|
10213
|
+
return `${seconds} ${secondsString}`;
|
|
10214
|
+
}
|
|
10215
|
+
function pointToString(point) {
|
|
10216
|
+
const x = point.x.toFixed(3);
|
|
10217
|
+
const y = point.y.toFixed(3);
|
|
10218
|
+
const z = point.z.toFixed(3);
|
|
10219
|
+
return `{ x: ${x}, y: ${y}, z: ${z} }`;
|
|
8962
10220
|
}
|
|
8963
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ZoneService, decorators: [{
|
|
8964
|
-
type: Injectable,
|
|
8965
|
-
args: [{
|
|
8966
|
-
providedIn: 'root',
|
|
8967
|
-
}]
|
|
8968
|
-
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
8969
|
-
type: Inject,
|
|
8970
|
-
args: ['currentAPIService']
|
|
8971
|
-
}] }, { type: BaseUserService }] });
|
|
8972
10221
|
|
|
8973
10222
|
/* eslint-disable no-case-declarations */
|
|
8974
10223
|
/* eslint-disable no-await-in-loop */
|
|
@@ -9186,7 +10435,12 @@ class FilterService {
|
|
|
9186
10435
|
// visibility filter already reads "no sweep" as "not floor-scoped, show it";
|
|
9187
10436
|
// this is the same rule applied to zones.
|
|
9188
10437
|
if (poi && poi.coordinate && !poi.matterportSweepID) {
|
|
9189
|
-
|
|
10438
|
+
// ...unless it was placed in a 3D model. With no sweep, that model was never
|
|
10439
|
+
// tied to this tour: the coordinates are the model's own, and here they would
|
|
10440
|
+
// put the tag somewhere arbitrary.
|
|
10441
|
+
if (!getThreeJsPoiOrigin(poi)) {
|
|
10442
|
+
filteredObjects.push(object);
|
|
10443
|
+
}
|
|
9190
10444
|
return;
|
|
9191
10445
|
}
|
|
9192
10446
|
if (poi && zone) {
|
|
@@ -9581,133 +10835,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
9581
10835
|
}]
|
|
9582
10836
|
}], ctorParameters: () => [{ type: BaseUserService }, { type: ZoneService }, { type: PoiService }] });
|
|
9583
10837
|
|
|
9584
|
-
class NavigationService {
|
|
9585
|
-
userService;
|
|
9586
|
-
zoneService;
|
|
9587
|
-
API;
|
|
9588
|
-
constructor(apiInjected, userService, zoneService) {
|
|
9589
|
-
this.userService = userService;
|
|
9590
|
-
this.zoneService = zoneService;
|
|
9591
|
-
this.API = apiInjected;
|
|
9592
|
-
}
|
|
9593
|
-
async createNavigation(navInput) {
|
|
9594
|
-
return this.API.__proto__.CreateNavigation(navInput);
|
|
9595
|
-
}
|
|
9596
|
-
async deleteNavigation(id) {
|
|
9597
|
-
return this.API.__proto__.DeleteNavigation({ id });
|
|
9598
|
-
}
|
|
9599
|
-
async getNavigationsForNode(nodeID) {
|
|
9600
|
-
const limit = 200;
|
|
9601
|
-
let navigationsTokenDict = { navigations: [], nextToken: null };
|
|
9602
|
-
do {
|
|
9603
|
-
navigationsTokenDict =
|
|
9604
|
-
await this.mergeAPIResponseWithNextTokenWithLimit(nodeID, limit, navigationsTokenDict);
|
|
9605
|
-
} while (navigationsTokenDict.nextToken);
|
|
9606
|
-
return navigationsTokenDict.navigations;
|
|
9607
|
-
}
|
|
9608
|
-
async mergeAPIResponseWithNextTokenWithLimit(nodeID, limit, navigationsTokenDict) {
|
|
9609
|
-
return this.API.__proto__.NavigationsByNode(nodeID, null, null, limit, navigationsTokenDict.nextToken).then((response) => {
|
|
9610
|
-
const navigations = response.items;
|
|
9611
|
-
const navObject = {
|
|
9612
|
-
navigations: [...navigationsTokenDict.navigations, ...navigations],
|
|
9613
|
-
nextToken: response.nextToken,
|
|
9614
|
-
};
|
|
9615
|
-
return navObject;
|
|
9616
|
-
});
|
|
9617
|
-
}
|
|
9618
|
-
async getNavigation(id) {
|
|
9619
|
-
return this.API.__proto__.GetNavigation(id);
|
|
9620
|
-
}
|
|
9621
|
-
async getSweepsIDsForSpace(spaceID) {
|
|
9622
|
-
const zones = await this.zoneService.getZonesBySpace(spaceID);
|
|
9623
|
-
const zonesWithSweeps = zones.filter((zone) => zone.sweepIDs && zone.sweepIDs.length > 0);
|
|
9624
|
-
const allSweeps = zonesWithSweeps.flatMap((zone) => zone.sweepIDs);
|
|
9625
|
-
return [...new Set(allSweeps)];
|
|
9626
|
-
}
|
|
9627
|
-
async getAllowedSweepsIDsForUserInSpace(spaceID) {
|
|
9628
|
-
const missionsForSpace = this.userService.currentMissions.filter((mis) => mis.spaceID === spaceID);
|
|
9629
|
-
console.log(missionsForSpace);
|
|
9630
|
-
let navIDs = [];
|
|
9631
|
-
if (this.userService.userHasAccessToAllZonesInSpace(spaceID)) {
|
|
9632
|
-
console.log(1);
|
|
9633
|
-
return this.getSweepsIDsForSpace(spaceID);
|
|
9634
|
-
}
|
|
9635
|
-
await Promise.all(missionsForSpace.map(async (mission) => {
|
|
9636
|
-
navIDs = [...navIDs, ...mission.zone.sweepIDs];
|
|
9637
|
-
}));
|
|
9638
|
-
console.log('final navids = ', navIDs);
|
|
9639
|
-
return navIDs;
|
|
9640
|
-
}
|
|
9641
|
-
async getForbiddenNavigationIDsForUserInSpace(spaceID) {
|
|
9642
|
-
const allowed = await this.getAllowedSweepsIDsForUserInSpace(spaceID);
|
|
9643
|
-
const allNavs = await this.getSweepsIDsForSpace(spaceID);
|
|
9644
|
-
const difference = allNavs.filter((x) => !allowed.includes(x));
|
|
9645
|
-
return difference.length > 0 ? difference : null;
|
|
9646
|
-
}
|
|
9647
|
-
async getNavigationsForMatterportIDForSpace(matterportID, spaceID) {
|
|
9648
|
-
return this.API.__proto__.NavigationsByMatterportIDBySpace(matterportID, {
|
|
9649
|
-
eq: spaceID,
|
|
9650
|
-
}).then((navs) => navs.items);
|
|
9651
|
-
}
|
|
9652
|
-
async getNavigationsForZone(zone) {
|
|
9653
|
-
const responseNavs = [];
|
|
9654
|
-
await Promise.all(zone.sweepIDs.map(async (sweepID) => {
|
|
9655
|
-
const navs = await this.getNavigationsForMatterportIDForSpace(sweepID, zone.spaceID);
|
|
9656
|
-
if (navs.length > 0) {
|
|
9657
|
-
responseNavs.push(navs[0]);
|
|
9658
|
-
}
|
|
9659
|
-
}));
|
|
9660
|
-
return responseNavs;
|
|
9661
|
-
}
|
|
9662
|
-
async updateNavigation(navInput) {
|
|
9663
|
-
return this.API.__proto__.UpdateNavigation(navInput);
|
|
9664
|
-
}
|
|
9665
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NavigationService, deps: [{ token: 'currentAPIService' }, { token: BaseUserService }, { token: ZoneService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
9666
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NavigationService, providedIn: "root" });
|
|
9667
|
-
}
|
|
9668
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NavigationService, decorators: [{
|
|
9669
|
-
type: Injectable,
|
|
9670
|
-
args: [{
|
|
9671
|
-
providedIn: "root",
|
|
9672
|
-
}]
|
|
9673
|
-
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
9674
|
-
type: Inject,
|
|
9675
|
-
args: ['currentAPIService']
|
|
9676
|
-
}] }, { type: BaseUserService }, { type: ZoneService }] });
|
|
9677
|
-
|
|
9678
|
-
class CaptureService {
|
|
9679
|
-
API; //AWS
|
|
9680
|
-
constructor(apiInjected) {
|
|
9681
|
-
this.API = apiInjected;
|
|
9682
|
-
}
|
|
9683
|
-
async getCapturesBySpace(spaceID) {
|
|
9684
|
-
return this.API.CapturesBySpace(spaceID).then((cap) => cap.items);
|
|
9685
|
-
}
|
|
9686
|
-
async getCapture(captureID) {
|
|
9687
|
-
return this.API.__proto__.GetCapture(captureID);
|
|
9688
|
-
}
|
|
9689
|
-
async deleteCapture(captureID) {
|
|
9690
|
-
return this.API.__proto__.DeleteCapture({ id: captureID });
|
|
9691
|
-
}
|
|
9692
|
-
async updateCapture(capture) {
|
|
9693
|
-
return this.API.__proto__.UpdateCapture(capture);
|
|
9694
|
-
}
|
|
9695
|
-
async createCapture(capture) {
|
|
9696
|
-
return this.API.__proto__.CreateCapture(capture);
|
|
9697
|
-
}
|
|
9698
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CaptureService, deps: [{ token: 'currentAPIService' }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
9699
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CaptureService, providedIn: "root" });
|
|
9700
|
-
}
|
|
9701
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CaptureService, decorators: [{
|
|
9702
|
-
type: Injectable,
|
|
9703
|
-
args: [{
|
|
9704
|
-
providedIn: "root",
|
|
9705
|
-
}]
|
|
9706
|
-
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
9707
|
-
type: Inject,
|
|
9708
|
-
args: ['currentAPIService']
|
|
9709
|
-
}] }] });
|
|
9710
|
-
|
|
9711
10838
|
class NodeService {
|
|
9712
10839
|
API;
|
|
9713
10840
|
constructor(apiInjected) {
|
|
@@ -11043,7 +12170,7 @@ class BaseObjectService {
|
|
|
11043
12170
|
// is no sweep to resolve a visit from, which is why this used to report the model
|
|
11044
12171
|
// as unreachable. The space's 3D visit is the one that can show it.
|
|
11045
12172
|
if (poi && poi.coordinate && !poi.matterportSweepID) {
|
|
11046
|
-
const visit = await this.getThreeJsVisitForSpace(object.spaceID);
|
|
12173
|
+
const visit = await this.getThreeJsVisitForSpace(object.spaceID, getThreeJsPoiOrigin(poi)?.captureID);
|
|
11047
12174
|
return visit
|
|
11048
12175
|
? { model3D: visit.model3d, sweep: null, rotation: null }
|
|
11049
12176
|
: null;
|
|
@@ -11079,15 +12206,15 @@ class BaseObjectService {
|
|
|
11079
12206
|
});
|
|
11080
12207
|
}
|
|
11081
12208
|
/**
|
|
11082
|
-
* The
|
|
12209
|
+
* The 3D visit to show a POI in, or null when the space has none.
|
|
11083
12210
|
*
|
|
11084
|
-
*
|
|
11085
|
-
*
|
|
11086
|
-
* which capture it was placed in, so with several 3D models this is a guess either way.
|
|
12211
|
+
* The one the POI was placed in, when it says. Older POIs do not, and for those it is the first
|
|
12212
|
+
* 3D visit the database returns - a guess, when the space has several.
|
|
11087
12213
|
*/
|
|
11088
|
-
async getThreeJsVisitForSpace(spaceID) {
|
|
11089
|
-
const visits = await this.visitService.getVisitsForSpaceForUser(spaceID, false)
|
|
11090
|
-
|
|
12214
|
+
async getThreeJsVisitForSpace(spaceID, captureID) {
|
|
12215
|
+
const visits = (await this.visitService.getVisitsForSpaceForUser(spaceID, false) ?? [])
|
|
12216
|
+
.filter((visit) => visit.viewer === CaptureViewer.THREEJS);
|
|
12217
|
+
return visits.find((visit) => visit.captureID === captureID) ?? visits[0] ?? null;
|
|
11091
12218
|
}
|
|
11092
12219
|
async updateIconForAllObjects(objects, tagIcon) {
|
|
11093
12220
|
const IDs = [];
|
|
@@ -12483,7 +13610,7 @@ class EquipmentService extends BaseObjectService {
|
|
|
12483
13610
|
user: this.userService.cu,
|
|
12484
13611
|
locationId: this.currentSpaceID,
|
|
12485
13612
|
timestamp: new Date(),
|
|
12486
|
-
zoneId: this.currentZone
|
|
13613
|
+
zoneId: this.currentZone?.id,
|
|
12487
13614
|
floorId: this.selectedFloor?.id,
|
|
12488
13615
|
},
|
|
12489
13616
|
streamName: 'smarterplanKinesis-analytics',
|
|
@@ -12947,6 +14074,11 @@ class EquipmentService extends BaseObjectService {
|
|
|
12947
14074
|
}
|
|
12948
14075
|
updateDueToCreate(createdEquip) {
|
|
12949
14076
|
this.currentEquipments.space.unshift(createdEquip);
|
|
14077
|
+
// initEquips only records a zone's list when it has a zone or found equipments, so the first
|
|
14078
|
+
// equipment of a space viewed without zones (Three.js) arrives with no list to join.
|
|
14079
|
+
if (!this.currentEquipments.zonesMap.has(this.zoneIDFilter)) {
|
|
14080
|
+
this.currentEquipments.zonesMap.set(this.zoneIDFilter, []);
|
|
14081
|
+
}
|
|
12950
14082
|
this.currentEquipments.zonesMap
|
|
12951
14083
|
.get(this.zoneIDFilter)
|
|
12952
14084
|
.unshift(createdEquip);
|
|
@@ -13020,6 +14152,26 @@ class EquipmentService extends BaseObjectService {
|
|
|
13020
14152
|
}
|
|
13021
14153
|
}
|
|
13022
14154
|
});
|
|
14155
|
+
// The Three.js viewer shows a whole space at once, with no zone to navigate into, so it
|
|
14156
|
+
// announces itself here instead of through `zoneChange`. Leaving the zone filter empty is what
|
|
14157
|
+
// makes every equipment of the space show up.
|
|
14158
|
+
this.zoneChangeService.spaceWithoutZones
|
|
14159
|
+
.pipe(takeUntil(this.destroy$))
|
|
14160
|
+
.subscribe((spaceID) => {
|
|
14161
|
+
this.currentSpaceID = spaceID;
|
|
14162
|
+
this.zoneIDFilter = null;
|
|
14163
|
+
this.currentZone = null;
|
|
14164
|
+
this.currentEquipments = {
|
|
14165
|
+
space: [],
|
|
14166
|
+
zonesMap: new Map(),
|
|
14167
|
+
};
|
|
14168
|
+
if (!spaceID) {
|
|
14169
|
+
this.equipmentsTags.next(null);
|
|
14170
|
+
this.equipmentsUpdated.next({ space: [], zone: null });
|
|
14171
|
+
return;
|
|
14172
|
+
}
|
|
14173
|
+
this.initEquips();
|
|
14174
|
+
});
|
|
13023
14175
|
this.deleteObservable
|
|
13024
14176
|
.pipe(takeUntil(this.destroy$))
|
|
13025
14177
|
.subscribe((equipment) => {
|
|
@@ -13234,11 +14386,11 @@ class SearchBarComponent {
|
|
|
13234
14386
|
this.searchEvent.emit(results);
|
|
13235
14387
|
}
|
|
13236
14388
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: SearchBarComponent, deps: [{ token: SearchService }], target: i0.ɵɵFactoryTarget.Component });
|
|
13237
|
-
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\">\
|
|
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" }] });
|
|
13238
14390
|
}
|
|
13239
14391
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: SearchBarComponent, decorators: [{
|
|
13240
14392
|
type: Component,
|
|
13241
|
-
args: [{ selector: 'lib-search-bar', template: "<div class=\"col-md-8 searchbar\">\
|
|
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"] }]
|
|
13242
14394
|
}], ctorParameters: () => [{ type: SearchService }], propDecorators: { searchable: [{
|
|
13243
14395
|
type: Input
|
|
13244
14396
|
}], objectType: [{
|
|
@@ -13686,11 +14838,11 @@ class LoaderComponent {
|
|
|
13686
14838
|
document.documentElement.style.setProperty('--color', this.color);
|
|
13687
14839
|
}
|
|
13688
14840
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: LoaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13689
|
-
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\">\
|
|
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"] }] });
|
|
13690
14842
|
}
|
|
13691
14843
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: LoaderComponent, decorators: [{
|
|
13692
14844
|
type: Component,
|
|
13693
|
-
args: [{ selector: 'lib-loader', template: "<div class=\"sk-fading-circle\">\
|
|
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"] }]
|
|
13694
14846
|
}], ctorParameters: () => [], propDecorators: { useLogo: [{
|
|
13695
14847
|
type: Input
|
|
13696
14848
|
}], color: [{
|
|
@@ -13748,11 +14900,11 @@ class SupportModalComponent {
|
|
|
13748
14900
|
}
|
|
13749
14901
|
}
|
|
13750
14902
|
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 });
|
|
13751
|
-
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\">\
|
|
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" }] });
|
|
13752
14904
|
}
|
|
13753
14905
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: SupportModalComponent, decorators: [{
|
|
13754
14906
|
type: Component,
|
|
13755
|
-
args: [{ selector: 'app-support-modal', template: "<div class=\"modal-header\">\
|
|
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"] }]
|
|
13756
14908
|
}], ctorParameters: () => [{ type: SupportService }, { type: i1$2.NgbActiveModal }, { type: i3.UntypedFormBuilder }, { type: BaseUserService }, { type: ValidatorsService }] });
|
|
13757
14909
|
|
|
13758
14910
|
class LocaleService {
|
|
@@ -13994,6 +15146,19 @@ class FeatureService extends BaseObjectService {
|
|
|
13994
15146
|
this.initFeatures();
|
|
13995
15147
|
}
|
|
13996
15148
|
});
|
|
15149
|
+
// The Three.js viewer shows a whole space at once, with no zone to navigate into, so it
|
|
15150
|
+
// announces itself here instead of through `zoneChange`.
|
|
15151
|
+
this.zoneChangeService.spaceWithoutZones.subscribe((spaceID) => {
|
|
15152
|
+
this.currentSpaceID = spaceID;
|
|
15153
|
+
this.zoneIDFilter = null;
|
|
15154
|
+
if (!spaceID) {
|
|
15155
|
+
this.featureTags.next(null);
|
|
15156
|
+
this.featureUpdated.next({ space: [], zone: null });
|
|
15157
|
+
}
|
|
15158
|
+
else if (!this.userService.isMuseumUser(spaceID)) {
|
|
15159
|
+
this.initFeatures();
|
|
15160
|
+
}
|
|
15161
|
+
});
|
|
13997
15162
|
this.updateDone.subscribe(() => {
|
|
13998
15163
|
if (this.currentSpaceID) {
|
|
13999
15164
|
this.initFeatures();
|
|
@@ -14384,6 +15549,19 @@ class MeasurementService {
|
|
|
14384
15549
|
this.initMeasurements();
|
|
14385
15550
|
}
|
|
14386
15551
|
});
|
|
15552
|
+
// The Three.js viewer shows a whole space at once, with no zone to navigate into, so it
|
|
15553
|
+
// announces itself here instead of through `zoneChange`.
|
|
15554
|
+
this.zoneChangeService.spaceWithoutZones.subscribe((spaceID) => {
|
|
15555
|
+
this.currentSpaceID = spaceID;
|
|
15556
|
+
this.zoneIDFilter = null;
|
|
15557
|
+
if (!spaceID) {
|
|
15558
|
+
this.measurementsTags.next(null);
|
|
15559
|
+
this.measurementsUpdated.next({ space: [], zone: null });
|
|
15560
|
+
this.measurementFilter = null;
|
|
15561
|
+
return;
|
|
15562
|
+
}
|
|
15563
|
+
this.initMeasurements();
|
|
15564
|
+
});
|
|
14387
15565
|
this.filterService.subscribeToDataFilterUpdate((dateRange) => {
|
|
14388
15566
|
this.dateFilter = dateRange;
|
|
14389
15567
|
this.initMeasurements();
|
|
@@ -14460,6 +15638,9 @@ class MeasurementService {
|
|
|
14460
15638
|
type: PoiType.MEASURE,
|
|
14461
15639
|
elementID: comment.id,
|
|
14462
15640
|
matterportSweepID: sweepID,
|
|
15641
|
+
// Only the Three.js viewer sends this: it records which 3D model the measurement was taken
|
|
15642
|
+
// in, the same as createPoiForThreeJsPlacement does for the other tags.
|
|
15643
|
+
...(dataMeasurements.metadata ? { metadata: dataMeasurements.metadata } : {}),
|
|
14463
15644
|
};
|
|
14464
15645
|
const poi = await this.poiService.createPOIForSpaceID(poiInput);
|
|
14465
15646
|
const screen = await fetch(screenshot);
|
|
@@ -15163,11 +16344,11 @@ class NavigationBarComponent {
|
|
|
15163
16344
|
this.baseVisibilityService.isChangePositionVisible.next(false);
|
|
15164
16345
|
}
|
|
15165
16346
|
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 });
|
|
15166
|
-
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\">\
|
|
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" }] });
|
|
15167
16348
|
}
|
|
15168
16349
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NavigationBarComponent, decorators: [{
|
|
15169
16350
|
type: Component,
|
|
15170
|
-
args: [{ selector: 'lib-navigation-bar', template: "<nav class=\"navbar navbar-expand-md navbar-dark navbar-inverse second-nav\">\
|
|
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"] }]
|
|
15171
16352
|
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
15172
16353
|
type: Inject,
|
|
15173
16354
|
args: ['currentAwsKinesisAnalyticsService']
|
|
@@ -15436,11 +16617,11 @@ class MenuBarComponent {
|
|
|
15436
16617
|
}
|
|
15437
16618
|
}
|
|
15438
16619
|
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 });
|
|
15439
|
-
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" }] });
|
|
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" }] });
|
|
15440
16621
|
}
|
|
15441
16622
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MenuBarComponent, decorators: [{
|
|
15442
16623
|
type: Component,
|
|
15443
|
-
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"] }]
|
|
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"] }]
|
|
15444
16625
|
}], 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: [{
|
|
15445
16626
|
type: Input
|
|
15446
16627
|
}] } });
|
|
@@ -15595,27 +16776,27 @@ class BaseTagService {
|
|
|
15595
16776
|
throw new Error('updatePoiMetadata is not implemented in the base class.');
|
|
15596
16777
|
}*/
|
|
15597
16778
|
async prepareEquipmentHtml(equip) {
|
|
15598
|
-
throw new Error(`prepareEquipmentHtml is not implemented in the base class.
|
|
16779
|
+
throw new Error(`prepareEquipmentHtml is not implemented in the base class.
|
|
15599
16780
|
Please override this method in your extended class.`);
|
|
15600
16781
|
}
|
|
15601
16782
|
async prepareTicketHtml(ticket) {
|
|
15602
|
-
throw new Error(`prepareTicketHtml is not implemented in the base class.
|
|
16783
|
+
throw new Error(`prepareTicketHtml is not implemented in the base class.
|
|
15603
16784
|
Please override this method in your extended class.`);
|
|
15604
16785
|
}
|
|
15605
16786
|
async prepareFeatureHtml(feature) {
|
|
15606
|
-
throw new Error(`prepareFeatureHtml is not implemented in the base class.
|
|
16787
|
+
throw new Error(`prepareFeatureHtml is not implemented in the base class.
|
|
15607
16788
|
Please override this method in your extended class.`);
|
|
15608
16789
|
}
|
|
15609
16790
|
async prepareMeasurementHtml(measure) {
|
|
15610
|
-
throw new Error(`prepareMeasurementHtml is not implemented in the base class.
|
|
16791
|
+
throw new Error(`prepareMeasurementHtml is not implemented in the base class.
|
|
15611
16792
|
Please override this method in your extended class.`);
|
|
15612
16793
|
}
|
|
15613
16794
|
async prepareDeskHtml(feature) {
|
|
15614
|
-
throw new Error(`prepareDeskHtml is not implemented in the base class.
|
|
16795
|
+
throw new Error(`prepareDeskHtml is not implemented in the base class.
|
|
15615
16796
|
Please override this method in your extended class.`);
|
|
15616
16797
|
}
|
|
15617
16798
|
async prepareIndicatorHtml(feature) {
|
|
15618
|
-
throw new Error(`prepareDeskHtml is not implemented in the base class.
|
|
16799
|
+
throw new Error(`prepareDeskHtml is not implemented in the base class.
|
|
15619
16800
|
Please override this method in your extended class.`);
|
|
15620
16801
|
}
|
|
15621
16802
|
async getSignedTagIconSource(tagIconSrc) {
|
|
@@ -15624,15 +16805,15 @@ class BaseTagService {
|
|
|
15624
16805
|
getIconTagImageForFeature(feature, poi) {
|
|
15625
16806
|
// const tagIcon = JSON.parse(poi.tagIcon);
|
|
15626
16807
|
// return tagIcon.src;
|
|
15627
|
-
throw new Error(`getIconTagImageForFeature is not implemented in the base class.
|
|
16808
|
+
throw new Error(`getIconTagImageForFeature is not implemented in the base class.
|
|
15628
16809
|
Please override this method in your extended class.`);
|
|
15629
16810
|
}
|
|
15630
16811
|
getScriptForTag(object, tagType) {
|
|
15631
|
-
throw new Error(`getScriptForTag is not implemented in the base class.
|
|
16812
|
+
throw new Error(`getScriptForTag is not implemented in the base class.
|
|
15632
16813
|
Please override this method in your extended class.`);
|
|
15633
16814
|
}
|
|
15634
16815
|
getAnnexeForCommentTypeInFeature(feature, commentType) {
|
|
15635
|
-
throw new Error(`getAnnexeForCommentTypeInFeature is not implemented in the base class.
|
|
16816
|
+
throw new Error(`getAnnexeForCommentTypeInFeature is not implemented in the base class.
|
|
15636
16817
|
Please override this method in your extended class.`);
|
|
15637
16818
|
}
|
|
15638
16819
|
getBillboardMediaToEmbed(object) {
|
|
@@ -15641,27 +16822,27 @@ class BaseTagService {
|
|
|
15641
16822
|
return { comment: null, tagDescription: '' };
|
|
15642
16823
|
}
|
|
15643
16824
|
onActionDetailClick(url) {
|
|
15644
|
-
throw new Error(`onActionDetailClick is not implemented in the base class.
|
|
16825
|
+
throw new Error(`onActionDetailClick is not implemented in the base class.
|
|
15645
16826
|
Please override this method in your extended class.`);
|
|
15646
16827
|
}
|
|
15647
16828
|
onActionAudioClick(audioCommentID) {
|
|
15648
|
-
throw new Error(`onActionDetailClick is not implemented in the base class.
|
|
16829
|
+
throw new Error(`onActionDetailClick is not implemented in the base class.
|
|
15649
16830
|
Please override this method in your extended class.`);
|
|
15650
16831
|
}
|
|
15651
16832
|
onActionVideoClick(url) {
|
|
15652
|
-
throw new Error(`onActionVideoClick is not implemented in the base class.
|
|
16833
|
+
throw new Error(`onActionVideoClick is not implemented in the base class.
|
|
15653
16834
|
Please override this method in your extended class.`);
|
|
15654
16835
|
}
|
|
15655
16836
|
onActionImageClick(imageCommentID) {
|
|
15656
|
-
throw new Error(`onActionImageClick is not implemented in the base class.
|
|
16837
|
+
throw new Error(`onActionImageClick is not implemented in the base class.
|
|
15657
16838
|
Please override this method in your extended class.`);
|
|
15658
16839
|
}
|
|
15659
16840
|
onActionDocClick(url) {
|
|
15660
|
-
throw new Error(`onActionDocClick is not implemented in the base class.
|
|
16841
|
+
throw new Error(`onActionDocClick is not implemented in the base class.
|
|
15661
16842
|
Please override this method in your extended class.`);
|
|
15662
16843
|
}
|
|
15663
16844
|
onActionYoutubeClick(url) {
|
|
15664
|
-
throw new Error(`onActionYoutubeClick is not implemented in the base class.
|
|
16845
|
+
throw new Error(`onActionYoutubeClick is not implemented in the base class.
|
|
15665
16846
|
Please override this method in your extended class.`);
|
|
15666
16847
|
}
|
|
15667
16848
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: BaseTagService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
@@ -19306,5 +20487,5 @@ function floatValidator() {
|
|
|
19306
20487
|
* Generated bundle index. Do not edit.
|
|
19307
20488
|
*/
|
|
19308
20489
|
|
|
19309
|
-
export { AffectationService, AvatarComponent, BaseLateralTabService, BaseTagService, BaseUserService, BaseVisibilityService, CameraMode, CaptureService, CaptureViewer, CommentService, CommentType, Config, ContentService, CsvExportComponent, DomainService, DomainType, DurationToStringPipe, EmailStatus, EquipmentService, EventService, EventStatus, EventType, FeatureService, FeatureType, FilterService, HashtagFromIdPipe, HashtagService, InterventionService, InventoryStatus, LayerService, LevelStatus, LoaderComponent, Locale, LocaleService, MATTERPORT_SDK_KEY, MatterportImportService, MatterportService, MatterportTagCopyService, MatterportViewType, MattertagActionMode, MattertagData, MeasurementService, MenuBarComponent, MissionService, NavigationService, NavigatorService, NgxSmarterplanCoreModule, NgxSmarterplanCoreService, NodeService, Object3DService, OperationService, OrganisationService, PaymentStatus, PlanService, PoiService, PoiType, ProfileEntity, ProfileService, ProfileStatus, PropertyService, PropertyType, RoleStatus, SafeUrlPipe, SearchBarComponent, SearchObjectType, SearchService, SpModule, SpaceService, SpaceStatus, StatusEquipment, SupportModalComponent, SupportService, TagAction, TemplateService, ThreeJsImportService, ThreeJsService, TicketPriority, TicketStatus, TicketType, TicketsService, TimeDateToLocalStringPipe, TypeNote, UsernameFromIdPipe, ValidatorsService, ViewerInteractions, ViewerService, VisitService, ZoneChangeService, ZoneService, applyRigidTransformToDirection, applyRigidTransformToPoint, applyRigidTransformToYRotationDeg, arraysContainSameElements, checkElementById, computeRigidTransform, convertTimestampToLocalZone, copyFileInS3, copyFileInS3ViaDownload, dateHasExpired, dateTimeToLocalString, deleteFromS3, distance, downloadBlob, downloadEquipmentDocument, downloadFile, downloadFileAsObject, durationToString, emailValidator, enumToArray, filterUniqueArrayByID, floatValidator, getBufferForFileFromS3, getCoefficientsForImage, getCurrentLang, getDistanceBetweenTwoPoints, getHighestLevelForMissions, getHighestRoleForMissions, getLevelsBelow, getLocaleLong, getLocaleShort, getMetaForImage, getRolesBelowForManager, getSignedFile, getSignedImageUrlForEquipment, getSignedImageUrlForProfile, getSignedImageUrlForSpace, getSpaceIDFromUrl, getVisitUrl, isEmptyObject, listFilesInFolder, mean, noEmptyValidator, numberToDateString, openDocument, openModalForVisitSwitch, parseThreeJsPlacement, poiTypeToString, pointToString, removeAllFilesFromFolderS3, removeNullKeysFromObject, showScanPointsOnPlanInDiv, shuffleArray, solveThreeJsAlignment, sortAlphabeticallyOnName, stringLocaleToEnum, stringToLowercaseNoSpaces, styleButton, textValidator, translateDatePeriod, uploadBase64Image, uploadBase64ImageWithRetry, uploadFileToS3, uploadFileToS3WithProgress, uploadJsonToS3, validEmail, wait, waitUntil };
|
|
20490
|
+
export { AffectationService, AvatarComponent, BaseLateralTabService, BaseTagService, BaseUserService, BaseVisibilityService, CameraMode, CaptureService, CaptureViewer, CommentService, CommentType, Config, ContentService, CsvExportComponent, DomainService, DomainType, DurationToStringPipe, EmailStatus, EquipmentService, EventService, EventStatus, EventType, FeatureService, FeatureType, FilterService, HashtagFromIdPipe, HashtagService, InterventionService, InventoryStatus, LayerService, LevelStatus, LoaderComponent, Locale, LocaleService, MATTERPORT_SDK_KEY, MatterportImportService, MatterportService, MatterportTagCopyService, MatterportViewType, MattertagActionMode, MattertagData, MeasurementService, MenuBarComponent, MissionService, NavigationService, NavigatorService, NgxSmarterplanCoreModule, NgxSmarterplanCoreService, NodeService, Object3DService, OperationService, OrganisationService, PaymentStatus, PlanService, PoiService, PoiType, ProfileEntity, ProfileService, ProfileStatus, PropertyService, PropertyType, RoleStatus, SafeUrlPipe, SearchBarComponent, SearchObjectType, SearchService, SpModule, SpaceService, SpaceStatus, StatusEquipment, SupportModalComponent, SupportService, SweepLocatorService, THREEJS_MODEL_UP_OPTIONS, TagAction, TemplateService, ThreeJsImportService, ThreeJsService, TicketPriority, TicketStatus, TicketType, TicketsService, TimeDateToLocalStringPipe, TypeNote, UsernameFromIdPipe, ValidatorsService, ViewerInteractions, ViewerService, VisitService, ZoneChangeService, ZoneService, applyRigidTransformToDirection, applyRigidTransformToPoint, applyRigidTransformToYRotationDeg, arraysContainSameElements, checkElementById, computeRigidTransform, convertTimestampToLocalZone, copyFileInS3, copyFileInS3ViaDownload, dateHasExpired, dateTimeToLocalString, deleteFromS3, distance, downloadBlob, downloadEquipmentDocument, downloadFile, downloadFileAsObject, durationToString, emailValidator, enumToArray, filterUniqueArrayByID, floatValidator, getBufferForFileFromS3, getCoefficientsForImage, getCurrentLang, getDistanceBetweenTwoPoints, getHighestLevelForMissions, getHighestRoleForMissions, getLevelsBelow, getLocaleLong, getLocaleShort, getMetaForImage, getRolesBelowForManager, getSignedFile, getSignedImageUrlForEquipment, getSignedImageUrlForProfile, getSignedImageUrlForSpace, getSpaceIDFromUrl, getThreeJsPoiOrigin, getVisitUrl, isEmptyObject, listFilesInFolder, mean, noEmptyValidator, numberToDateString, openDocument, openModalForVisitSwitch, parseThreeJsPlacement, poiTypeToString, pointToString, removeAllFilesFromFolderS3, removeNullKeysFromObject, showScanPointsOnPlanInDiv, shuffleArray, solveThreeJsAlignment, sortAlphabeticallyOnName, stringLocaleToEnum, stringToLowercaseNoSpaces, styleButton, textValidator, translateDatePeriod, uploadBase64Image, uploadBase64ImageWithRetry, uploadFileToS3, uploadFileToS3WithProgress, uploadJsonToS3, validEmail, wait, waitUntil };
|
|
19310
20491
|
//# sourceMappingURL=smarterplan-ngx-smarterplan-core.mjs.map
|