@shopware-ag/dive 1.18.5-beta.3 → 1.18.5

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.
@@ -34,16 +34,13 @@ export declare class DIVECommunication {
34
34
  private controller;
35
35
  private toolbox;
36
36
  private _mediaGenerator;
37
- private get mediaGenerator();
38
37
  private _io;
39
- private get io();
40
38
  private _ar;
41
- private get ar();
42
39
  private registered;
43
40
  private listeners;
44
41
  constructor(renderer: DIVERenderer, scene: DIVEScene, controls: DIVEOrbitControls, toolbox: DIVEToolbox);
45
42
  DestroyInstance(): boolean;
46
- PerformAction<Action extends keyof Actions>(action: Action, payload: Actions[Action]['PAYLOAD']): Actions[Action]['RETURN'];
43
+ PerformAction<Action extends keyof Actions>(action: Action, payload?: Actions[Action]['PAYLOAD']): Actions[Action]['RETURN'];
47
44
  Subscribe<Action extends keyof Actions>(type: Action, listener: EventListener<Action>): Unsubscribe;
48
45
  private dispatch;
49
46
  private getAllSceneData;
@@ -0,0 +1,8 @@
1
+ export declare class DIVEModule<T> {
2
+ private _path;
3
+ private _ctor;
4
+ constructor(_path: string, _ctor: string);
5
+ private _promise;
6
+ private _instance;
7
+ get(): Promise<T>;
8
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shopware-ag/dive",
3
- "version": "1.18.5-beta.3",
3
+ "version": "1.18.5",
4
4
  "description": "Shopware Spatial Framework",
5
5
  "type": "module",
6
6
  "main": "build/dive.cjs",
@@ -51,7 +51,7 @@
51
51
  "ts-jest": "^29.1.2",
52
52
  "ts-node": "^10.9.2",
53
53
  "tsc": "^2.0.4",
54
- "typescript": "^5.4.5",
54
+ "typescript": "^5.8.2",
55
55
  "typescript-eslint": "^7.7.1",
56
56
  "vite": "^6.2.1",
57
57
  "vite-plugin-dts": "^4.5.3"
@@ -2,6 +2,7 @@ import { Actions } from './actions/index.ts';
2
2
  import { generateUUID } from 'three/src/math/MathUtils';
3
3
  import { isSelectTool } from '../toolbox/select/SelectTool.ts';
4
4
  import { merge } from 'lodash';
5
+ import { DIVEModule } from '../module/Module.ts';
5
6
 
6
7
  // type imports
7
8
  import { type Color, type MeshStandardMaterial } from 'three';
@@ -74,80 +75,13 @@ export class DIVECommunication {
74
75
  private controller: DIVEOrbitControls;
75
76
  private toolbox: DIVEToolbox;
76
77
 
77
- private _mediaGenerator: DIVEMediaCreator | null;
78
- private get mediaGenerator(): Promise<DIVEMediaCreator> {
79
- if (!this._mediaGenerator) {
80
- return new Promise((resolve, reject) => {
81
- import('../mediacreator/MediaCreator.ts')
82
- .then((module) => {
83
- const DIVEMediaCreator = module.DIVEMediaCreator;
84
- this._mediaGenerator = new DIVEMediaCreator(
85
- this.renderer,
86
- this.scene,
87
- this.controller,
88
- );
89
- resolve(this._mediaGenerator);
90
- })
91
- .catch((error) => {
92
- console.error(
93
- 'DIVE: Error while lazy-loading IO module:',
94
- error,
95
- );
96
- reject(error);
97
- });
98
- });
99
- }
100
- return Promise.resolve(this._mediaGenerator);
101
- }
102
-
103
- private _io: DIVEIO | null;
104
- private get io(): Promise<DIVEIO> {
105
- if (!this._io) {
106
- return new Promise((resolve, reject) => {
107
- import('../io/IO.ts')
108
- .then((module) => {
109
- const DIVEIO = module.DIVEIO;
110
- this._io = new DIVEIO(this.scene);
111
- resolve(this._io);
112
- })
113
- .catch((error) => {
114
- console.error(
115
- 'DIVE: Error while lazy-loading IO module:',
116
- error,
117
- );
118
- reject(error);
119
- });
120
- });
121
- }
122
- return Promise.resolve(this._io);
123
- }
124
-
125
- private _ar: DIVEAR | null;
126
- private get ar(): Promise<DIVEAR> {
127
- if (!this._ar) {
128
- return new Promise((resolve, reject) => {
129
- import('../ar/AR.ts')
130
- .then((module) => {
131
- const DIVEAR = module.DIVEAR;
132
- this._ar = new DIVEAR(
133
- this.renderer,
134
- this.scene,
135
- this.controller,
136
- );
137
- resolve(this._ar);
138
- })
139
- .catch((error) => {
140
- console.error(
141
- 'DIVE: Error while lazy-loading AR module:',
142
- error,
143
- );
144
- reject(error);
145
- });
146
- });
147
- }
78
+ private _mediaGenerator: DIVEModule<DIVEMediaCreator> = new DIVEModule(
79
+ '../mediacreator/MediaCreator.ts',
80
+ 'DIVEMediaCreator',
81
+ );
82
+ private _io: DIVEModule<DIVEIO> = new DIVEModule('../io/IO.ts', 'DIVEIO');
148
83
 
149
- return Promise.resolve(this._ar);
150
- }
84
+ private _ar: DIVEModule<DIVEAR> = new DIVEModule('../ar/AR.ts', 'DIVEAR');
151
85
 
152
86
  private registered: Map<string, COMEntity> = new Map();
153
87
 
@@ -166,9 +100,6 @@ export class DIVECommunication {
166
100
  this.scene = scene;
167
101
  this.controller = controls;
168
102
  this.toolbox = toolbox;
169
- this._mediaGenerator = null;
170
- this._io = null;
171
- this._ar = null;
172
103
 
173
104
  DIVECommunication.__instances.push(this);
174
105
  }
@@ -184,7 +115,7 @@ export class DIVECommunication {
184
115
 
185
116
  public PerformAction<Action extends keyof Actions>(
186
117
  action: Action,
187
- payload: Actions[Action]['PAYLOAD'],
118
+ payload?: Actions[Action]['PAYLOAD'],
188
119
  ): Actions[Action]['RETURN'] {
189
120
  let returnValue: Actions[Action]['RETURN'] = false;
190
121
 
@@ -353,10 +284,11 @@ export class DIVECommunication {
353
284
  }
354
285
  case 'LAUNCH_AR': {
355
286
  returnValue = new Promise<void>((resolve, reject) => {
356
- this.ar
357
- .then((arModule) => {
287
+ this._ar
288
+ .get()
289
+ .then((ar) => {
358
290
  resolve(
359
- arModule.Launch(
291
+ ar.Launch(
360
292
  payload as Actions['LAUNCH_AR']['PAYLOAD'],
361
293
  ),
362
294
  );
@@ -784,7 +716,7 @@ export class DIVECommunication {
784
716
  target = payload.target;
785
717
  }
786
718
 
787
- return this.mediaGenerator.then((module) => {
719
+ return this._mediaGenerator.get().then((module) => {
788
720
  return module.GenerateMedia(
789
721
  position,
790
722
  target,
@@ -858,9 +790,10 @@ export class DIVECommunication {
858
790
  payload: Actions['EXPORT_SCENE']['PAYLOAD'],
859
791
  ): Actions['EXPORT_SCENE']['RETURN'] {
860
792
  return new Promise<string | null>((resolve, reject) => {
861
- this.io
862
- .then((ioModule) => {
863
- resolve(ioModule.Export(payload.type));
793
+ this._io
794
+ .get()
795
+ .then((io) => {
796
+ resolve(io.Export(payload.type));
864
797
  })
865
798
  .catch(reject);
866
799
  });
@@ -32,36 +32,47 @@ import {
32
32
  type COMLight,
33
33
  type COMModel,
34
34
  type COMPov,
35
- type COMPrimitive,
36
35
  } from '../types';
37
36
  import { type DIVESceneObject } from '../../types';
38
37
 
39
- jest.mock('../../mediacreator/MediaCreator', () => {
38
+ const mockModule: Record<string, any> = {
39
+ get: jest.fn().mockReturnValue(Promise.resolve({})),
40
+ };
41
+ jest.mock('../../module/Module', () => {
40
42
  return {
41
- DIVEMediaCreator: jest.fn(function () {
42
- this.GenerateMedia = jest.fn();
43
+ DIVEModule: jest.fn().mockImplementation(() => {
44
+ return mockModule;
45
+ }),
46
+ };
47
+ });
43
48
 
44
- return this;
49
+ jest.mock('../../mediacreator/MediaCreator', () => {
50
+ return {
51
+ DIVEMediaCreator: jest.fn().mockImplementation(() => {
52
+ return {
53
+ GenerateMedia: jest.fn(),
54
+ };
45
55
  }),
46
56
  };
47
57
  });
48
58
 
49
59
  jest.mock('../../io/IO', () => {
50
60
  return {
51
- DIVEIO: jest.fn(function () {
52
- this.Import = jest.fn();
53
- this.Export = jest.fn();
54
- return this;
61
+ DIVEIO: jest.fn().mockImplementation(() => {
62
+ return {
63
+ Import: jest.fn(),
64
+ Export: jest.fn(),
65
+ };
55
66
  }),
56
67
  };
57
68
  });
58
69
 
59
70
  jest.mock('../../ar/AR', () => {
60
71
  return {
61
- DIVEAR: jest.fn(function () {
62
- this.Launch = jest.fn();
63
-
64
- return this;
72
+ DIVEAR: jest.fn().mockImplementation(() => {
73
+ return {
74
+ Launch: jest.fn(),
75
+ };
65
76
  }),
66
77
  };
67
78
  });
@@ -194,12 +205,14 @@ const mockToolBox = {
194
205
  }),
195
206
  SetGizmoMode: jest.fn(),
196
207
  SetGizmoVisibility: jest.fn(),
208
+ SetGizmoScaleLinked: jest.fn(),
197
209
  } as unknown as DIVEToolbox;
198
210
 
199
211
  let testCom: DIVECommunication;
200
212
 
201
213
  describe('dive/communication/DIVECommunication', () => {
202
214
  beforeEach(() => {
215
+ jest.clearAllMocks();
203
216
  testCom = new DIVECommunication(
204
217
  mockRenderer,
205
218
  mockScene,
@@ -797,6 +810,11 @@ describe('dive/communication/DIVECommunication', () => {
797
810
  expect(visibility).toBe(false);
798
811
  });
799
812
 
813
+ it('should perform action SET_GIZMO_SCALE_LINKED', () => {
814
+ const success = testCom.PerformAction('SET_GIZMO_SCALE_LINKED', true);
815
+ expect(success).toBe(true);
816
+ });
817
+
800
818
  it('should perform action USE_TOOL', () => {
801
819
  let success = testCom.PerformAction('USE_TOOL', { tool: 'select' });
802
820
  expect(success).toBe(true);
@@ -844,7 +862,10 @@ describe('dive/communication/DIVECommunication', () => {
844
862
 
845
863
  it('should perform action GENERATE_MEDIA', async () => {
846
864
  const blobUri = 'blob:http://localhost:3000/1234';
847
- const mediaGeneratorModule = await testCom['mediaGenerator'];
865
+ jest.spyOn(mockModule, 'get').mockResolvedValue({
866
+ GenerateMedia: jest.fn(),
867
+ });
868
+ const mediaGeneratorModule = await testCom['_mediaGenerator'].get();
848
869
 
849
870
  jest.spyOn(mediaGeneratorModule, 'GenerateMedia').mockReturnValue(
850
871
  blobUri,
@@ -962,7 +983,10 @@ describe('dive/communication/DIVECommunication', () => {
962
983
 
963
984
  it('should perform action EXPORT_SCENE', async () => {
964
985
  const url = 'https://example.com';
965
- const ioModule = await testCom['io'];
986
+ jest.spyOn(mockModule, 'get').mockResolvedValue({
987
+ Export: jest.fn(),
988
+ });
989
+ const ioModule = await testCom['_io'].get();
966
990
 
967
991
  jest.spyOn(ioModule, 'Export').mockResolvedValueOnce(url);
968
992
 
@@ -973,7 +997,10 @@ describe('dive/communication/DIVECommunication', () => {
973
997
  });
974
998
 
975
999
  it('should perform action LAUNCH_AR', async () => {
976
- const arModule = await testCom['ar'];
1000
+ jest.spyOn(mockModule, 'get').mockResolvedValue({
1001
+ Launch: jest.fn(),
1002
+ });
1003
+ const arModule = await testCom['_ar'].get();
977
1004
  const arLaunchSpy = jest
978
1005
  .spyOn(arModule, 'Launch')
979
1006
  .mockResolvedValueOnce();
@@ -0,0 +1,45 @@
1
+ export class DIVEModule<T> {
2
+ constructor(
3
+ private _path: string,
4
+ private _ctor: string,
5
+ ) {}
6
+
7
+ private _promise: Promise<T> | null = null;
8
+ private _instance: T | null = null;
9
+
10
+ public async get(): Promise<T> {
11
+ // if we already have an instance, return it
12
+ if (this._instance) {
13
+ return Promise.resolve(this._instance);
14
+ }
15
+
16
+ // if we already have a loading process ongoing, return the already created promise
17
+ if (this._promise) {
18
+ return this._promise;
19
+ }
20
+
21
+ // if we don't have a promise yet, it means that we are not already loading the module
22
+ this._promise = (async () => {
23
+ const module = await import(this._path);
24
+
25
+ const ModuleConstructor = module[this._ctor];
26
+
27
+ if (!ModuleConstructor) {
28
+ throw new Error(
29
+ `DIVE: Module class ${this._ctor} not found in ${this._path}`,
30
+ );
31
+ }
32
+
33
+ if (typeof ModuleConstructor !== 'function') {
34
+ throw new Error(
35
+ `DIVE: Module at ${this._path} does not export a valid constructor (${this._ctor} wanted)`,
36
+ );
37
+ }
38
+
39
+ this._instance = new ModuleConstructor() as T;
40
+ return this._instance;
41
+ })();
42
+
43
+ return this._promise;
44
+ }
45
+ }
@@ -0,0 +1,54 @@
1
+ import { DIVEModule } from '../Module';
2
+
3
+ jest.mock('/mock/path', () => mockModule, { virtual: true });
4
+
5
+ const mockModule: Record<string, any> = {};
6
+
7
+ describe('dive/module/DIVEModule', () => {
8
+ beforeEach(() => {
9
+ // Reset the mock module before each test
10
+ Object.keys(mockModule).forEach((key) => delete mockModule[key]);
11
+ });
12
+
13
+ it('should return the same instance on multiple calls to get()', async () => {
14
+ mockModule.TestClass = class TestClass {};
15
+ const diveModule = new DIVEModule('/mock/path', 'TestClass');
16
+
17
+ const instance1 = await diveModule.get();
18
+ const instance2 = await diveModule.get();
19
+
20
+ expect(instance1).toBe(instance2);
21
+ });
22
+
23
+ it('should throw an error if the constructor is not found', async () => {
24
+ const diveModule = new DIVEModule('/mock/path', 'NonExistentClass');
25
+
26
+ await expect(diveModule.get()).rejects.toThrow(
27
+ 'DIVE: Module class NonExistentClass not found in /mock/path',
28
+ );
29
+ });
30
+
31
+ it('should throw an error if the constructor is not a function', async () => {
32
+ mockModule.InvalidClass = {};
33
+ const diveModule = new DIVEModule('/mock/path', 'InvalidClass');
34
+
35
+ await expect(diveModule.get()).rejects.toThrow(
36
+ 'DIVE: Module at /mock/path does not export a valid constructor (InvalidClass wanted)',
37
+ );
38
+ });
39
+
40
+ it('should only load the module once even with concurrent calls to get()', async () => {
41
+ mockModule.TestClass = class TestClass {};
42
+ const diveModule = new DIVEModule('/mock/path', 'TestClass');
43
+
44
+ const [
45
+ instance1,
46
+ instance2,
47
+ ] = await Promise.all([
48
+ diveModule.get(),
49
+ diveModule.get(),
50
+ ]);
51
+
52
+ expect(instance1).toBe(instance2);
53
+ });
54
+ });