@smarterplan/ngx-smarterplan-core 1.4.12 → 1.4.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/esm2022/lib/pipes/hashtag-from-id.pipe.mjs +26 -7
  2. package/esm2022/lib/services/matterport-tag-copy.service.mjs +838 -195
  3. package/esm2022/lib/services/matterport-tag.service.mjs +169 -60
  4. package/esm2022/lib/services/matterport.service.mjs +222 -39
  5. package/esm2022/lib/services/models/comment.service.mjs +35 -1
  6. package/esm2022/lib/services/models/equipment.service.mjs +4 -2
  7. package/esm2022/lib/services/models/feature.service.mjs +26 -1
  8. package/esm2022/lib/services/models/hashtag.service.mjs +29 -2
  9. package/esm2022/lib/services/s3.service.mjs +29 -1
  10. package/esm2022/lib/services/utils/matterport-transform.util.mjs +29 -3
  11. package/esm2022/lib/services/viewer.service.mjs +8 -1
  12. package/esm2022/lib/types.service.mjs +9 -1
  13. package/fesm2022/smarterplan-ngx-smarterplan-core.mjs +1407 -298
  14. package/fesm2022/smarterplan-ngx-smarterplan-core.mjs.map +1 -1
  15. package/lib/pipes/hashtag-from-id.pipe.d.ts +9 -0
  16. package/lib/services/matterport-tag-copy.service.d.ts +196 -15
  17. package/lib/services/matterport-tag.service.d.ts +61 -7
  18. package/lib/services/matterport.service.d.ts +56 -1
  19. package/lib/services/models/comment.service.d.ts +16 -0
  20. package/lib/services/models/equipment.service.d.ts +4 -1
  21. package/lib/services/models/feature.service.d.ts +11 -0
  22. package/lib/services/models/hashtag.service.d.ts +6 -0
  23. package/lib/services/s3.service.d.ts +14 -0
  24. package/lib/services/utils/matterport-transform.util.d.ts +16 -1
  25. package/lib/types.service.d.ts +18 -0
  26. package/package.json +1 -1
@@ -4,6 +4,15 @@ import * as i0 from "@angular/core";
4
4
  export declare class HashtagFromIdPipe implements PipeTransform {
5
5
  private hashtagService;
6
6
  constructor(hashtagService: HashtagService);
7
+ /**
8
+ * Resolves a hashtag id to its display name.
9
+ *
10
+ * The cache is consulted for THIS id, and the network answers whenever the id is not
11
+ * in it.
12
+ *
13
+ * Hashtag ids are globally unique, so a cache holding entries from several spaces can
14
+ * only ever produce a correct hit or a miss, never a wrong name.
15
+ */
7
16
  transform(hashtagID: string): Promise<string>;
8
17
  static ɵfac: i0.ɵɵFactoryDeclaration<HashtagFromIdPipe, never>;
9
18
  static ɵpipe: i0.ɵɵPipeDeclaration<HashtagFromIdPipe, "hashtagFromID", false>;
@@ -8,6 +8,8 @@ import { Object3DService } from './models/object3D.service';
8
8
  import { FeatureService } from './models/feature.service';
9
9
  import { MeasurementService } from './models/measurement.service';
10
10
  import { CommentService } from './models/comment.service';
11
+ import { HashtagService } from './models/hashtag.service';
12
+ import { SpaceService } from './models/space.service';
11
13
  import { BaseUserService } from './user.service';
12
14
  import { Vec3 } from './utils/matterport-transform.util';
13
15
  import * as i0 from "@angular/core";
@@ -37,13 +39,37 @@ export declare class MatterportTagCopyService {
37
39
  private featureService;
38
40
  private measurementService;
39
41
  private commentService;
42
+ private hashtagService;
43
+ private spaceService;
40
44
  private translate;
41
45
  copyProgress$: BehaviorSubject<CopyProgress | null>;
42
46
  copyLog$: BehaviorSubject<CopyLogEntry[]>;
43
47
  private spModule;
44
48
  private log;
45
49
  private errorSeq;
46
- constructor(navigationService: NavigationService, userService: BaseUserService, poiService: PoiService, ticketService: TicketsService, equipmentService: EquipmentService, object3DService: Object3DService, featureService: FeatureService, measurementService: MeasurementService, commentService: CommentService, translate: TranslateService);
50
+ /** source element id -> id of its copy in the target space. */
51
+ private elementIds;
52
+ /** source Comment id -> id of its copy. Needed to remap POI.tagImageID, which is a
53
+ * reference to a Comment record (see the schema: "connection to comment.id"). */
54
+ private commentIds;
55
+ /** source Hashtag id -> its name. */
56
+ private sourceHashtagNames;
57
+ /** normalised hashtag name -> its id in the TARGET space (pre-existing or just created). */
58
+ private targetHashtagIdsByName;
59
+ /** ids of the hashtags this run created in the target space. */
60
+ private createdHashtagIds;
61
+ /** ids of the hashtags actually persisted on a successfully created feature. Anything in
62
+ * createdHashtagIds but absent here is an orphan and is deleted at the end of the run. */
63
+ private usedHashtagIds;
64
+ /** source ids of the elements whose POIs were already created by an element phase.
65
+ * Guards the standalone POI phase against creating a second, impoverished copy. */
66
+ private handledElementIds;
67
+ /** true when source and target spaces belong to the same organisation. Domains are
68
+ * organisation-scoped (no spaceID), so domainID survives the copy only in that case. */
69
+ private sameOrganisation;
70
+ /** Target space id of the run in progress — used to re-home copied tag icons. */
71
+ private targetSpaceId;
72
+ constructor(navigationService: NavigationService, userService: BaseUserService, poiService: PoiService, ticketService: TicketsService, equipmentService: EquipmentService, object3DService: Object3DService, featureService: FeatureService, measurementService: MeasurementService, commentService: CommentService, hashtagService: HashtagService, spaceService: SpaceService, translate: TranslateService);
47
73
  copyTagsFromVisit(sourceSpaceId: string, targetSpaceId: string, landmarkSourceA: Vec3, landmarkTargetA: Vec3, landmarkSourceB: Vec3, landmarkTargetB: Vec3): Promise<boolean>;
48
74
  /** Adds or updates a step line (identified by stepId) with new text, then republishes the log. */
49
75
  private setStepLog;
@@ -51,6 +77,10 @@ export declare class MatterportTagCopyService {
51
77
  private appendErrorLog;
52
78
  /** Resets the log for a new run. */
53
79
  private resetLog;
80
+ /** Clears every id map carried over from a previous run. The service is a root-level
81
+ * singleton, so leftover mappings would silently link a new copy to records created
82
+ * during the previous one. */
83
+ private resetRunState;
54
84
  /** Builds and sets a step's log line as "<translated label>: <percent>%", no step numbering. */
55
85
  private setStepProgress;
56
86
  /** Logs a translated, parameterized error line for a single failed item. */
@@ -58,14 +88,152 @@ export declare class MatterportTagCopyService {
58
88
  /** Logs an item-level error, using a specific "missing file / orphaned reference"
59
89
  * translation key when the cause is a MissingS3FileError, and the generic per-type
60
90
  * key otherwise. Keeps the copy log readable: a missing file is not a bug to
61
- * investigate the same way a network/permission error is. */
91
+ * investigate the same way a network/permission error is.
92
+ * `extraParams` carries the placeholders a few keys need on top of {{name}}/{{error}}
93
+ * (e.g. errorFeatureAttachment's {{feature}}, errorPoi's {{sweep}}), which used to be
94
+ * rendered as raw "{{feature}}" text in the log. */
62
95
  private logCopyItemError;
63
96
  /**
64
- * Heuristic duplicate-detection: compares title AND description together instead of
65
- * title alone, to reduce false positives when several source items happen to share
66
- * the same title. Not a perfect guarantee, but meaningfully more robust.
97
+ * Null-safe equivalent of FeatureService.isArchived(): true when the operator has archived
98
+ * this feature on that space. "No mission on the space" answers false (nothing archived)
99
+ * instead of throwing, which is what made a copy from a space the operator is not a member
100
+ * of fail outright.
101
+ */
102
+ private isArchivedForOperator;
103
+ /**
104
+ * True when both spaces belong to the same organisation.
105
+ * Domains (POI/Feature categories) are organisation-scoped records with no spaceID, so
106
+ * a domainID stays meaningful inside one organisation and only becomes a dangling
107
+ * reference across two. A failed lookup answers "no": dropping a category is recoverable,
108
+ * pointing at another organisation's domain is not.
109
+ */
110
+ private spacesShareOrganisation;
111
+ /** Normalised lookup key for a hashtag name, so "Moyen Âge" and "moyen âge " match. */
112
+ private hashtagKey;
113
+ /**
114
+ * READ-ONLY indexing of both spaces' hashtags. Creates nothing.
115
+ *
116
+ * `Feature.hashtags` stores Hashtag *ids*, and a Hashtag is a per-space record, so the
117
+ * ids have to be translated by name. The translation table is built here; the missing
118
+ * keywords are created later, one at a time, only when a POI that actually uses them is
119
+ * about to be written (see resolveHashtagIds()).
120
+ */
121
+ private loadHashtagIndex;
122
+ /**
123
+ * Translates one feature's hashtag ids into target-space ids, creating on the fly the
124
+ * keywords the target space does not have yet.
125
+ *
126
+ * Creation is LAZY on purpose. Doing it up front, for the whole source vocabulary, meant
127
+ * that a copy which failed or was closed halfway left behind keywords no POI referenced —
128
+ * pollution the operator had to clean up by hand. Here a keyword is only ever written
129
+ * when a POI that carries it is about to be created, so an interrupted copy stops
130
+ * creating keywords at the same POI it stops creating content.
131
+ *
132
+ * The residual window (keyword created, then createFeature fails) is closed by
133
+ * markHashtagsUsed() + cleanupUnusedCreatedHashtags().
134
+ *
135
+ * A keyword that can neither be found nor created is dropped rather than left as a
136
+ * dangling id.
137
+ */
138
+ private resolveHashtagIds;
139
+ /** Records that these hashtags are now referenced by a feature that really exists, so
140
+ * the end-of-run cleanup leaves them alone. */
141
+ private markHashtagsUsed;
142
+ /**
143
+ * Deletes the hashtags this run created that no copied POI ended up referencing.
144
+ *
145
+ * Runs on every exit path — success, general failure, or exception — so the target space
146
+ * never keeps keywords that came from an aborted copy. Only ids created by THIS run are
147
+ * ever considered: a pre-existing keyword of the target space is never touched, even if
148
+ * no copied POI uses it.
149
+ */
150
+ private cleanupUnusedCreatedHashtags;
151
+ /**
152
+ * Orders a feature's media set exactly the way the UI will read it back, and repairs
153
+ * the ordering key when the source data cannot express the user's intended order.
154
+ *
155
+ * The viewer sorts each media family on its own (`setImages`, `setVideos`, `setAudio`,
156
+ * `setDocuments`, ... all do `.sort((a, b) => a.index - b.index)`), so `index` only has
157
+ * to be consistent WITHIN a CommentType. The attachment form however defaults `index`
158
+ * to 1 for every new attachment, so a multi-image POI very often holds several images
159
+ * all sharing index 1 — or null. Sorting those is left to DynamoDB's arbitrary scan
160
+ * order, which is why the copy came out in a different order than the original.
161
+ *
162
+ * Strategy: sort deterministically (index, then creation date, then id — i.e. the
163
+ * upload order the user actually saw), and only rewrite the indices of a family when
164
+ * they are ambiguous (null or duplicated). A family whose indices are already unique
165
+ * is copied untouched, so a deliberate ordering is never overwritten.
166
+ */
167
+ private orderCommentsForCopy;
168
+ /** Sort key for a comment's position; a missing index sorts last, not first. */
169
+ private orderingIndexOf;
170
+ /** Creation timestamp of a comment, used as the tie-breaker: it reflects the order in
171
+ * which the user uploaded the media, which is the order they saw. */
172
+ private creationTimeOf;
173
+ /**
174
+ * Copies ONE media item (image, video, audio, document, text, link, embed, iframe...)
175
+ * of a feature, duplicating its file in S3 first when it has one.
176
+ *
177
+ * Every column of the Comment model is carried over. `description` and `metadata` were
178
+ * previously dropped, which emptied the captions of images/videos, the body of TEXT
179
+ * items — where the whole content lives, since a TEXT comment has no file — and any
180
+ * per-media settings stored in metadata.
181
+ */
182
+ private copyCommentForFeature;
183
+ /** Same, for an attachment carried by a ticket rather than by a feature. */
184
+ private copyCommentForTicket;
185
+ /** Shared body of the two above: `owner` carries whichever foreign key links the copy back
186
+ * to its new parent, everything else is identical. */
187
+ private copyComment;
188
+ /**
189
+ * Creates the target-space copy of a source POI, with EVERY column of the POI model.
190
+ *
191
+ * Single entry point for all phases: the element phases (ticket, equipment, 3D object,
192
+ * feature, measurement) used to build their POI inline with only 5 fields — coordinate,
193
+ * type, elementID, metadata, sweep — which is precisely how museum POIs lost their
194
+ * pastille: `tagDescription` (the popup text), `tagIcon` (the custom icon) and
195
+ * `tagImageID` (the pastille's image) never left the source space.
196
+ */
197
+ private createCopiedPoi;
198
+ /**
199
+ * Adapts a POI's `metadata` (see MattertagData.getMetadataString(): { icon, normal, sid })
200
+ * to the target space.
201
+ *
202
+ * - `sid` is the id of the Mattertag inside the SOURCE Matterport model. Carried over,
203
+ * the viewer tries to re-create the target's tag with an id belonging to another
204
+ * model ("already in use"), so the pastille fails to attach. It is dropped: the
205
+ * viewer then allocates a fresh sid on first render.
206
+ * - `normal` is the stem direction. It is a direction, so only the rotation applies
207
+ * (never the translation); without it, a copied pastille points into the wall
208
+ * whenever the two models are not aligned on the same axis.
209
+ * - `icon` and anything else is kept as-is.
210
+ */
211
+ private getAdjustedPoiMetadata;
212
+ /**
213
+ * Resolves a POI's `tagImageID` — which the schema documents as a "connection to
214
+ * comment.id", i.e. the id of the Comment holding the image shown in the pastille.
215
+ *
216
+ * It is NOT an S3 key, so the previous "does it contain a slash?" heuristic never
217
+ * matched and the value travelled unchanged, leaving every copied pastille pointing at
218
+ * a Comment of the source space. It is now translated through the comment id map built
219
+ * while duplicating the media, and dropped when it cannot be resolved (the pastille
220
+ * then falls back to the feature's cover image instead of a broken reference).
221
+ */
222
+ private resolveTagImageID;
223
+ /**
224
+ * Heuristic duplicate-detection: compares several identifying fields together rather than
225
+ * a single label, to reduce false positives when items share one of them.
226
+ *
227
+ * The fields are supplied by the caller because the models do not agree on them. The
228
+ * previous version hard-coded `title`/`description`, which do not exist on Equipment at
229
+ * all: both sides read `undefined`, every equipment therefore "matched" every other one,
230
+ * and a target space holding a single equipment silently caused the whole equipment phase
231
+ * to copy nothing.
67
232
  */
68
233
  private matchesExisting;
234
+ /** Serialises the identifying fields unambiguously, so that ["a", "b"] and ["ab", ""]
235
+ * can never produce the same key. */
236
+ private identityKey;
69
237
  /**
70
238
  * Fetches the position of every sweep in a space and returns it as a Map for O(1) lookup.
71
239
  * Uses allSettled so a single failing sweep lookup doesn't abort the whole map —
@@ -90,6 +258,8 @@ export declare class MatterportTagCopyService {
90
258
  /**
91
259
  * Builds a new S3 key for a copied attachment and sanitizes the filename
92
260
  * to prevent AWS Amplify SignatureDoesNotMatch errors.
261
+ * `folderOverride` re-homes the copy into another prefix — used for tag icons, which
262
+ * are stored per space (`icons-tags/<spaceId>/`) and must follow the target space.
93
263
  */
94
264
  private buildCopiedAttachmentKey;
95
265
  /**
@@ -100,20 +270,31 @@ export declare class MatterportTagCopyService {
100
270
  * - Any other kind of failure (permissions, network, etc.) still throws a generic Error.
101
271
  */
102
272
  private copyS3KeyIfPresent;
273
+ /** True when the failure means the source object simply is not on S3 (orphaned reference). */
274
+ private isMissingS3File;
275
+ /** True when AppSync refused the operation for lack of permission, as opposed to failing it.
276
+ * Nothing can be retried or worked around client-side: the account simply is not allowed
277
+ * to write that model, which is a backend authorisation rule (@auth) to change. */
278
+ private isUnauthorized;
279
+ /** True for the SigV4 mismatch Storage.copy() raises on keys with special characters. */
280
+ private isSignatureMismatch;
103
281
  /**
104
282
  * Duplicates the file referenced by a POI's tagIcon (a JSON string shaped like
105
- * { src, opacity }, per form-feature.component.ts). If the JSON can't be parsed or
106
- * has no `src`, the value is returned unchanged rather than risk corrupting it.
283
+ * { src, opacity }, per form-feature.component.ts).
284
+ *
285
+ * Two behaviours matter here:
286
+ * - the copy is re-homed under the TARGET space's icon folder
287
+ * (`icons-tags/<targetSpaceId>/`, the layout form-feature.component.ts uploads to).
288
+ * Keeping the source folder produced a target space whose icons all lived under
289
+ * another space's prefix, so cleaning up the source space broke the copy's icons.
290
+ * - a missing icon file is NOT fatal. It used to bubble up as a MissingS3FileError and
291
+ * abort the whole POI — losing the pastille, its text and its media because of a
292
+ * decorative file. The POI is now created with the icon reference dropped, which
293
+ * renders the module's default icon, and a log line reports it.
107
294
  */
108
295
  private duplicateTagIcon;
109
- /**
110
- * Duplicates tagImageID if it looks like a reference to a file (contains a path
111
- * separator, whether raw key or pre-signed URL). UNCONFIRMED ASSUMPTION: this
112
- * field's exact semantics weren't verified — if it's actually an opaque reference
113
- * to another DB record rather than a file, this heuristic may incorrectly attempt
114
- * a copy. Confirm the real structure to tighten this if it causes issues.
115
- */
116
- private duplicateTagImageID;
296
+ /** Icon folder of the space being copied into, matching what the feature form uploads to. */
297
+ private buildTargetIconFolder;
117
298
  /**
118
299
  * Extracts and normalizes the raw S3 key from a stored value (which may be a full URL
119
300
  * or already a raw key).
@@ -6,6 +6,8 @@ import { BaseTagService } from './tag.service';
6
6
  import * as i0 from "@angular/core";
7
7
  export declare class MatterportTagService {
8
8
  private navigationService;
9
+ /** Name of the common layer every Smarterplan tag is created in. */
10
+ private static readonly SHARED_TAG_LAYER_NAME;
9
11
  dictionnaryTags: Map<string, MattertagData>;
10
12
  tagAddQueue: Promise<any>;
11
13
  mattertagToFollow: string | null;
@@ -16,10 +18,61 @@ export declare class MatterportTagService {
16
18
  tagService: BaseTagService | null;
17
19
  private tagsCurrentOpacity;
18
20
  private tagsCurrentEnabled;
21
+ /**
22
+ * Single layer holding every Smarterplan tag, added to every view of the space so a
23
+ * tag stays in place — same sid, same content — when the user switches between the
24
+ * furnished and the Defurnished view. Tags added to the global sdk.Tag (or to a
25
+ * view's own layer) belong to that view only, which is what made them pop in and out
26
+ * on every view change.
27
+ *
28
+ * Deliberately NOT created with `{ common: true }`: in SDK 26.7.5 a common layer is
29
+ * pushed into `getOrderedModelViews()` at creation time, and that call defaults to
30
+ * `includeDefurnish: false`, so the Defurnished view never receives it — while
31
+ * `View.addLayer()` refuses to repair it ("Layer is common. No need to add."). A
32
+ * plain layer we attach to each view ourselves reaches every view, Defurnished
33
+ * included. See attachSharedTagLayerToViews().
34
+ */
35
+ private sharedTagLayer;
36
+ private sharedTagLayerPromise;
19
37
  constructor(navigationService: MatterportNavigationService);
20
38
  setTagService(service: BaseTagService): void;
21
39
  /** Opens a specific tag in the Matterport viewer by its ID. */
22
40
  goToTag(sdk: any, sid: string): Promise<any>;
41
+ /**
42
+ * Returns the SDK Tag module to use for an operation addressing an existing tag by ID
43
+ * (icon, opacity, position, attachments, removal...): always the global sdk.Tag.
44
+ *
45
+ * A layer-scoped Tag module only exposes the *replayable* mutations, meant to build
46
+ * the layer's own content; applying them to an already-created tag silently does
47
+ * nothing — which is what left every pin white instead of carrying its icon. Only
48
+ * `add` has to go through the layer, see getTagCreationModule().
49
+ */
50
+ getTagModule(sdk: any, tagId: string): any;
51
+ /**
52
+ * Returns the Tag module every new tag must be created through: the shared layer
53
+ * when the View API is available, the global sdk.Tag otherwise.
54
+ */
55
+ getTagCreationModule(sdk: any): Promise<any>;
56
+ /**
57
+ * Creates (once per SDK session) and returns the shared tag layer.
58
+ * Resolves to null when the View API is unavailable, so callers transparently fall
59
+ * back to the global sdk.Tag.
60
+ */
61
+ ensureSharedTagLayer(sdk: any): Promise<any>;
62
+ /**
63
+ * Makes sure the shared layer is part of every view of the space, Defurnished
64
+ * included. This is what actually shares the tags across views, and the reason the
65
+ * layer must not be a common one: the SDK only performs the attachment for
66
+ * non-common layers. Idempotent, so it can run on every view collection update.
67
+ */
68
+ attachSharedTagLayerToViews(sdk: any, views: Iterable<any>): Promise<void>;
69
+ /**
70
+ * Adds a tag through the shared layer so it exists in every view. Falls back
71
+ * to the global sdk.Tag when the View API is unavailable; that call is raced against
72
+ * a timeout as a safety net, since it never resolves while a Defurnished view is
73
+ * active.
74
+ */
75
+ private addTagToSdk;
23
76
  /** Opens the most recently created tag in the viewer. */
24
77
  goToLastTag(sdk: any): Promise<any>;
25
78
  /**
@@ -38,12 +91,6 @@ export declare class MatterportTagService {
38
91
  * If there are no active rooms (e.g., hallway), falls back to returning true.
39
92
  */
40
93
  isTagInRooms(mattertagData: MattertagData, rooms: any[]): boolean;
41
- /**
42
- * Computes the final context-aware opacity for a single tag.
43
- * @param currentFloorSequence Authoritative current floor sequence from sdk.Floor.current
44
- * (null = single-floor model or unknown).
45
- * @param currentRooms Array of active rooms the camera is in.
46
- */
47
94
  /** Returns the ID of the most recently added tag. */
48
95
  getLastTag(): string | null;
49
96
  /**
@@ -64,6 +111,13 @@ export declare class MatterportTagService {
64
111
  * @param mattertagToFollow ID of the cursor tag (never touched).
65
112
  */
66
113
  updateTagsVisibilityForContext(sdk: any, sweepCollection: any, currentCameraMode: CameraMode, mattertagToFollow: string | null, currentFloorSequence?: number | null, currentRooms?: any[]): Promise<void>;
114
+ /**
115
+ * Drops the memoized opacity/enabled state so the next visibility pass re-applies it
116
+ * to every tag. Needed after a view switch (furnished <-> defurnished): the SDK
117
+ * re-renders the shared layer's tags and the cached values would otherwise make
118
+ * updateTagsVisibilityForContext() skip them, leaving tags stuck invisible.
119
+ */
120
+ resetVisibilityCache(): void;
67
121
  /** Removes a specific tag from the viewer and local dictionaries. */
68
122
  deleteMattertagFromId(sdk: any, mattertagID: string): Promise<void>;
69
123
  /** Removes the last added tag from the viewer. */
@@ -74,7 +128,7 @@ export declare class MatterportTagService {
74
128
  * Creates and configures a Mattertag from a POI, handling coordinates, normals, and sweep ID.
75
129
  * Skips creation if a tag for the object already exists.
76
130
  */
77
- createMattertagFromPOI(sdk: any, tagType: PoiType, object: DbObjectType, poi: POI, poseCamera: any, getTagFromElementId: (id: string) => {
131
+ createMattertagFromPOI(sdk: any, tagType: PoiType, object: DbObjectType, poi: POI, getTagFromElementId: (id: string) => {
78
132
  tag: string;
79
133
  sweep: string;
80
134
  }, setTagIconAndOpacity: (sid: string, mt: MattertagData) => Promise<void>, injectHtmlInTag: (type: PoiType, obj: DbObjectType, sid: string) => Promise<void>): Promise<any>;
@@ -2,7 +2,7 @@ import { NgZone } from '@angular/core';
2
2
  import { ActivatedRoute, Router } from '@angular/router';
3
3
  import { Subject } from 'rxjs';
4
4
  import { MattertagData } from '../mattertagData';
5
- import { DbObjectType, IObject3D, MattertagActionMode, POI, PoiType, SpModule } from '../types.service';
5
+ import { DbObjectType, IObject3D, MattertagActionMode, MatterportView, POI, PoiType, SpModule } from '../types.service';
6
6
  import { BaseTagService } from './tag.service';
7
7
  import { BaseVisibilityService } from './baseVisibility.service';
8
8
  import { Config } from '../config';
@@ -104,6 +104,15 @@ export declare class MatterportService {
104
104
  action: string;
105
105
  data: string;
106
106
  }>;
107
+ /** Views (furnished, defurnished...) exposed by the SDK for the current space. */
108
+ private viewsByType;
109
+ private currentView;
110
+ private viewSubscriptions;
111
+ /** Emits the active view every time the user switches between furnished/defurnished. */
112
+ onViewChanged: Subject<MatterportView | null>;
113
+ /** Delay left to the SDK to rebuild the layer's tags after a view switch. */
114
+ private static readonly VIEW_SWITCH_REFRESH_DELAY_MS;
115
+ private viewSwitchRestoreTimer;
107
116
  get mattertagToFollow(): string | null;
108
117
  set mattertagToFollow(value: string | null);
109
118
  get inTransitionMode(): boolean;
@@ -186,6 +195,11 @@ export declare class MatterportService {
186
195
  setRegistredIconForTag(mattertagID: string, iconName: string): Promise<void>;
187
196
  /** Sets the appropriate icon and opacity for a tag based on its type and POI metadata. */
188
197
  setTagIconAndOpacity(mattertagID: string, mattertagData: MattertagData): Promise<void>;
198
+ /**
199
+ * Applies an icon to a tag. The call is awaited and its rejection reported: a
200
+ * silently dropped promise here leaves the pin with its default white look.
201
+ */
202
+ private editTagIcon;
189
203
  /** Navigates the camera to the most recently created tag. */
190
204
  goToLastTag(): Promise<void>;
191
205
  /** Navigates the camera to a specific Mattertag by its ID. */
@@ -325,6 +339,47 @@ export declare class MatterportService {
325
339
  * @returns MattertagData
326
340
  */
327
341
  getMatterTagDataForMattertag(mattertagID: string): MattertagData | null;
342
+ /**
343
+ * Subscribes to the SDK View API to keep track of the views available for the space
344
+ * and of the active one. Tags live in a shared common layer, so a view change only
345
+ * requires re-applying their visibility, never re-creating them.
346
+ */
347
+ private initViews;
348
+ /**
349
+ * Forces the opacity/enabled state to be pushed again to every tag. The SDK
350
+ * re-renders the shared layer's tags on a view swap, and the memoized values would
351
+ * otherwise make updateTagsVisibilityForContext() skip them, leaving tags invisible.
352
+ */
353
+ private reapplyTagsVisibility;
354
+ /**
355
+ * Puts the tags back in shape in the view that just became active.
356
+ *
357
+ * Activating a view re-materializes the shared layer's tags from the descriptors
358
+ * recorded when they were added, so everything applied to a tag *afterwards* through
359
+ * the global Tag module is dropped along the way: the custom icon (the pin falls back
360
+ * to the descriptor's white) and the sandbox carrying the hover content. They have to
361
+ * be applied again on the freshly rebuilt tags.
362
+ *
363
+ * Visibility is refreshed straight away so the tags show up without waiting, then
364
+ * content is re-applied once the transition is over — re-applying it too early would
365
+ * target tags the SDK has not rebuilt yet.
366
+ */
367
+ private restoreTagsForActiveView;
368
+ /**
369
+ * Re-applies the icon and the injected HTML of every tag we own. Used after a view
370
+ * switch, see restoreTagsForActiveView().
371
+ */
372
+ private refreshTagsContent;
373
+ private clearViewSubscriptions;
374
+ /** True when the space provides a defurnished view. */
375
+ get hasDefurnishView(): boolean;
376
+ /** True when the defurnished view is the active one. */
377
+ get isDefurnishedView(): boolean;
378
+ /**
379
+ * Switches between the defurnished view and the furnished one.
380
+ * @param returnToStart Move the camera back to the view's start location.
381
+ */
382
+ toggleDefurnishView(returnToStart?: boolean): Promise<void>;
328
383
  action_toolbox_floorplan(): Promise<void>;
329
384
  action_toolbox_inside_view(): void;
330
385
  actionShowAllFloors(): void;
@@ -15,6 +15,22 @@ export declare class CommentService {
15
15
  getComment(commentID: string): Promise<Comment>;
16
16
  deleteComment(commentID: string): Promise<Comment>;
17
17
  createComment(comment: Comment): Promise<Comment>;
18
+ /**
19
+ * Returns EVERY comment attached to a feature, following `nextToken` until the
20
+ * connection is exhausted.
21
+ *
22
+ * The `comments` connection nested inside FeaturesBySpace/GetFeature is capped by
23
+ * the AppSync resolver (`$util.defaultIfNull($context.args.limit, 100)`) and exposes
24
+ * a `nextToken` that nobody follows, so a media-rich POI silently loses every
25
+ * attachment past the first page. Anything that must see the complete media set of
26
+ * a feature (duplication, export, audit) has to use this method rather than
27
+ * `feature.comments.items`.
28
+ */
29
+ getAllCommentsForFeature(featureID: string): Promise<Comment[]>;
30
+ /** Same as getAllCommentsForFeature(), for the attachments of a ticket. */
31
+ getAllCommentsForTicket(ticketID: string): Promise<Comment[]>;
32
+ /** Drains a paginated comment query until its nextToken is exhausted. */
33
+ private getAllCommentsPaginated;
18
34
  updateComment(input: any): Promise<Comment>;
19
35
  uploadFileForComment(file: File, id?: string): Promise<string>;
20
36
  /** Create a new comment with an existing Annexe */
@@ -70,7 +70,10 @@ export declare class EquipmentService extends BaseObjectService {
70
70
  * S3 object itself has already been physically copied by the caller beforehand,
71
71
  * so this only needs to record the Note pointing at that new key.
72
72
  */
73
- createNoteWithExistingAttachment(equipmentID: string, attachmentKey: string, type: TypeNote, ownerMissionID: string | null): Promise<Note>;
73
+ createNoteWithExistingAttachment(equipmentID: string, attachmentKey: string, type: TypeNote, ownerMissionID: string | null, dates?: {
74
+ signDate?: number | null;
75
+ expirationDate?: number | null;
76
+ }): Promise<Note>;
74
77
  uploadImage(equipment: Equipment, imageFile: File): Promise<Note>;
75
78
  updateEquipment(equip: any, file?: File, noteType?: TypeNote): Promise<Equipment>;
76
79
  getPrefixDoc(type: TypeNote): any;
@@ -48,6 +48,17 @@ export declare class FeatureService extends BaseObjectService {
48
48
  initFeatures(): Promise<void>;
49
49
  getFeaturesForSpace(spaceID: string, keepArchived?: boolean): Promise<Feature[]>;
50
50
  getFeaturesBySpaceWithoutFiltering(spaceID: string): Promise<Feature[]>;
51
+ /**
52
+ * Returns EVERY feature of a space, following `nextToken` until the index is
53
+ * exhausted, with no user/archive filtering at all.
54
+ *
55
+ * `getFeaturesBySpaceWithoutFiltering()` reads a single page (the AppSync default
56
+ * caps it at 100 items) and calls `isArchived()`, which dereferences the current
57
+ * user's mission ON THAT SPACE — so it both truncates large spaces and throws for a
58
+ * space the operator has no mission on. Duplication has to see the raw, complete
59
+ * list, hence this separate accessor rather than a change of the existing one.
60
+ */
61
+ getAllFeaturesBySpaceRaw(spaceID: string): Promise<Feature[]>;
51
62
  isBookingPlace(feature: Feature): void;
52
63
  setCoverImage(feature: Feature): void;
53
64
  updateFeatureLocation(feature: Feature, tagData: MattertagData, spaceID: string): Promise<POI>;
@@ -6,6 +6,12 @@ export declare class HashtagService {
6
6
  API: any;
7
7
  constructor(apiInjected: any);
8
8
  getHashtag(hashtagID: string): Promise<Hashtag>;
9
+ /** Reads a space's keywords without touching the shared cache. */
10
+ listHashtagsForSpace(spaceID: string): Promise<Hashtag[]>;
11
+ /** Creates a keyword in a space without adding it to the shared cache. */
12
+ createHashtagForSpace(name: string, spaceID: string): Promise<Hashtag>;
13
+ /** Deletes a keyword without touching the shared cache. */
14
+ deleteHashtagById(hashtagID: string): Promise<Hashtag>;
9
15
  createHashtag(hashtag: Hashtag): Promise<Hashtag>;
10
16
  getHashtagsForSpace(spaceID?: string): Promise<Hashtag[]>;
11
17
  static ɵfac: i0.ɵɵFactoryDeclaration<HashtagService, never>;
@@ -20,3 +20,17 @@ export declare const uploadJsonToS3: (path: string, json: string, filename: stri
20
20
  * to distinguish an orphaned DB reference from a genuine copy failure.
21
21
  */
22
22
  export declare const copyFileInS3: (sourceKey: string, destKey: string) => Promise<string>;
23
+ /**
24
+ * Fallback duplication: downloads the object and re-uploads it under the new key.
25
+ *
26
+ * Storage.copy() builds the S3 `x-amz-copy-source` header by plain string concatenation
27
+ * (see @aws-amplify/storage AWSS3Provider.copy: `${bucket}/${srcPrefix}${srcKey}`), with no
28
+ * URL-encoding of the key. A key holding a space, an accent or a parenthesis therefore
29
+ * yields a header that does not match the signed canonical request, and S3 answers
30
+ * "The request signature we calculated does not match the signature you provided".
31
+ *
32
+ * Round-tripping the bytes avoids that header entirely and works for any key, at the cost
33
+ * of transferring the file through the browser — hence its use as a fallback only.
34
+ * IMPORTANT: like copyFileInS3, this must NOT swallow errors.
35
+ */
36
+ export declare const copyFileInS3ViaDownload: (sourceKey: string, destKey: string) => Promise<string>;
@@ -15,7 +15,22 @@ export interface RigidTransform {
15
15
  export declare function computeRigidTransform(sourceA: Vec3, targetA: Vec3, sourceB: Vec3, targetB: Vec3): RigidTransform;
16
16
  /** Applies the rigid transform to a 3D position (x, y, z). */
17
17
  export declare function applyRigidTransformToPoint(p: Vec3, t: RigidTransform): Vec3;
18
- /** Applies the Y rotation (in degrees) to an orientation angle (a pose's yRotation). */
18
+ /**
19
+ * Applies the Y rotation (in degrees) to an orientation angle (a pose's yRotation).
20
+ * A null/undefined/non-numeric angle is returned untouched: "no orientation stored"
21
+ * must stay "no orientation stored", not become the raw rotation delta.
22
+ */
19
23
  export declare function applyRigidTransformToYRotationDeg(yRotationDeg: number, t: RigidTransform): number;
20
24
  /** Euclidean distance between two points, useful to validate landmark quality. */
21
25
  export declare function distance(a: Vec3, b: Vec3): number;
26
+ /**
27
+ * Applies ONLY the rotation part of the transform to a direction vector
28
+ * (a normal, a stem vector, ...).
29
+ *
30
+ * A direction has no origin, so the translation must NOT be applied to it —
31
+ * doing so would turn a unit normal into a point somewhere in the target model.
32
+ * Used for a POI's `metadata.normal`, which drives the orientation of the tag's
33
+ * stem: without this rotation, a pastille copied into a space whose model is
34
+ * rotated relative to the source ends up pointing into the wall it is stuck on.
35
+ */
36
+ export declare function applyRigidTransformToDirection(v: Vec3, t: RigidTransform): Vec3;
@@ -842,3 +842,21 @@ export type Object3D = {
842
842
  items: POI[];
843
843
  };
844
844
  } & AWSType;
845
+ export declare enum MatterportViewType {
846
+ BASE = "viewtype.base",
847
+ DEFURNISH = "viewtype.defurnish",
848
+ LAYERED_BASE = "viewtype.layeredbase",
849
+ OTHER = "viewtype.other",
850
+ USER = "viewtype.user"
851
+ }
852
+ export interface MatterportView {
853
+ id: string;
854
+ name: string;
855
+ active: boolean;
856
+ type: MatterportViewType;
857
+ setActive(returnToStart?: boolean): Promise<void>;
858
+ }
859
+ export type MatterportViewCollection = Record<string, MatterportView>;
860
+ export interface MatterportSubscription {
861
+ cancel(): void;
862
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smarterplan/ngx-smarterplan-core",
3
- "version": "1.4.12",
3
+ "version": "1.4.14",
4
4
  "dependencies": {
5
5
  "tslib": "^2.5.0"
6
6
  },