@shopware-ag/dive 1.18.5 → 1.19.1-beta.0

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 (66) hide show
  1. package/README.md +8 -0
  2. package/build/dive.cjs +220 -215
  3. package/build/dive.cjs.map +1 -1
  4. package/build/dive.js +233 -21102
  5. package/build/dive.js.map +1 -1
  6. package/build/dive.mjs +26111 -0
  7. package/build/dive.mjs.map +1 -0
  8. package/build/src/ar/AR.d.ts +37 -14
  9. package/build/src/ar/arquicklook/ARQuickLook.d.ts +5 -6
  10. package/build/src/ar/sceneviewer/SceneViewer.d.ts +42 -6
  11. package/build/src/com/actions/index.d.ts +2 -0
  12. package/build/src/com/actions/renderer/startrender.d.ts +5 -0
  13. package/build/src/com/actions/scene/launchar.d.ts +5 -2
  14. package/build/src/converter/Converter.d.ts +21 -0
  15. package/build/src/dive.d.ts +3 -2
  16. package/build/src/exporter/Exporter.d.ts +11 -0
  17. package/build/src/helper/applyMixins/applyMixins.d.ts +22 -6
  18. package/build/src/info/Info.d.ts +37 -13
  19. package/build/src/interface/Movable.d.ts +5 -5
  20. package/build/src/interface/Selectable.d.ts +4 -4
  21. package/build/src/loader/Loader.d.ts +11 -0
  22. package/build/src/model/Model.d.ts +2 -2
  23. package/build/src/node/Node.d.ts +3 -3
  24. package/build/src/scene/root/Root.d.ts +1 -1
  25. package/build/src/types/ExporterOptions.d.ts +15 -0
  26. package/build/src/types/FileTypes.d.ts +27 -0
  27. package/build/src/types/index.d.ts +4 -0
  28. package/build/src/types/info/index.d.ts +66 -0
  29. package/package.json +16 -1
  30. package/src/ar/AR.ts +72 -69
  31. package/src/ar/__test__/AR.test.ts +194 -105
  32. package/src/ar/arquicklook/ARQuickLook.ts +32 -72
  33. package/src/ar/arquicklook/__test__/ARQuickLook.test.ts +89 -38
  34. package/src/ar/sceneviewer/SceneViewer.ts +96 -51
  35. package/src/ar/sceneviewer/__test__/SceneViewer.test.ts +144 -47
  36. package/src/ar/webxr/WebXR.ts +5 -4
  37. package/src/com/Communication.ts +10 -7
  38. package/src/com/__test__/Communication.test.ts +16 -3
  39. package/src/com/actions/index.ts +2 -0
  40. package/src/com/actions/renderer/startrender.ts +5 -0
  41. package/src/com/actions/scene/launchar.ts +2 -2
  42. package/src/converter/Converter.ts +117 -0
  43. package/src/dive.ts +10 -6
  44. package/src/exporter/Exporter.ts +75 -0
  45. package/src/helper/applyMixins/applyMixins.ts +59 -7
  46. package/src/info/Info.ts +99 -75
  47. package/src/info/__test__/Info.test.ts +162 -154
  48. package/src/interface/Movable.ts +5 -5
  49. package/src/interface/Selectable.ts +4 -4
  50. package/src/loader/Loader.ts +48 -0
  51. package/src/model/Model.ts +10 -5
  52. package/src/model/__test__/Model.test.ts +4 -11
  53. package/src/node/Node.ts +7 -5
  54. package/src/scene/root/Root.ts +4 -4
  55. package/src/scene/root/__test__/Root.test.ts +4 -4
  56. package/src/toolbox/Toolbox.ts +1 -3
  57. package/src/types/ExporterOptions.ts +14 -0
  58. package/src/types/FileTypes.ts +37 -0
  59. package/src/types/index.ts +26 -0
  60. package/src/types/info/index.ts +76 -0
  61. package/build/src/exporters/usdz/USDZExporter.d.ts +0 -15
  62. package/build/src/loadingmanager/LoadingManager.d.ts +0 -14
  63. package/src/exporters/usdz/USDZExporter.ts +0 -21
  64. package/src/exporters/usdz/__test__/USDZExporter.test.ts +0 -57
  65. package/src/loadingmanager/LoadingManager.ts +0 -50
  66. package/src/loadingmanager/__test__/LoadingManager.test.ts +0 -27
@@ -1,4 +1,5 @@
1
- import { DIVEInfo, WebXRUnsupportedReason } from '../Info';
1
+ import { SystemInfo } from '../Info';
2
+ import { ESystem, EWebXRUnsupportedReason } from '../../types/info';
2
3
 
3
4
  const mockNavigator = (navigator: any) => {
4
5
  Object.defineProperty(global, 'navigator', {
@@ -9,54 +10,62 @@ const mockNavigator = (navigator: any) => {
9
10
 
10
11
  describe('dive/info/DIVEInfo', () => {
11
12
  beforeEach(() => {
12
- DIVEInfo['_supportsWebXR'] = null;
13
+ SystemInfo['_supportsWebXR'] = false;
13
14
  jest.clearAllMocks();
14
15
  });
15
16
 
16
17
  it('should get system: Windows', () => {
17
18
  mockNavigator({
18
- platform: 'Win64',
19
+ userAgent:
20
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.153 Safari/537.36',
19
21
  });
20
- expect(DIVEInfo.GetSystem()).toBe('Windows');
22
+ expect(SystemInfo.GetSystem()).toBe(ESystem.WINDOWS);
21
23
  });
22
24
 
23
25
  it('should get system: MacOS', () => {
24
26
  mockNavigator({
25
- platform: 'MacIntel',
27
+ userAgent:
28
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.153 Safari/537.36',
26
29
  });
27
- expect(DIVEInfo.GetSystem()).toBe('MacOS');
30
+ expect(SystemInfo.GetSystem()).toBe(ESystem.MACOS);
28
31
  });
29
32
 
30
33
  it('should get system: Linux', () => {
31
34
  mockNavigator({
32
- platform: 'Linux',
35
+ userAgent:
36
+ 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.153 Safari/537.36',
33
37
  });
34
- expect(DIVEInfo.GetSystem()).toBe('Linux');
38
+ expect(SystemInfo.GetSystem()).toBe(ESystem.LINUX);
35
39
  });
36
40
 
37
41
  it('should get system: Android', () => {
38
42
  mockNavigator({
39
43
  userAgent:
40
- 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3',
41
- platform: 'Linux',
44
+ 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.153 Mobile Safari/537.36',
42
45
  });
43
- expect(DIVEInfo.GetSystem()).toBe('Android');
46
+ expect(SystemInfo.GetSystem()).toBe(ESystem.ANDROID);
44
47
  });
45
48
 
46
49
  it('should get system: iOS', () => {
47
50
  mockNavigator({
48
51
  userAgent:
49
- 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0_1 like Mac OS X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3',
50
- platform: 'iPhone',
52
+ 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
51
53
  });
52
- expect(DIVEInfo.GetSystem()).toBe('iOS');
54
+ expect(SystemInfo.GetSystem()).toBe(ESystem.IOS);
53
55
  });
54
56
 
55
57
  it('should get system: Unknown', () => {
56
58
  mockNavigator({
57
- platform: 'Unknown',
59
+ userAgent: 'Unknown Browser',
58
60
  });
59
- expect(DIVEInfo.GetSystem()).toBe('Unknown');
61
+ expect(SystemInfo.GetSystem()).toBe(ESystem.UNKNOWN);
62
+ });
63
+
64
+ it('should get system: Unknown when window is undefined', () => {
65
+ const originalNavigator = window.navigator;
66
+ window.navigator = undefined as any;
67
+ expect(SystemInfo.GetSystem()).toBe(ESystem.UNKNOWN);
68
+ window.navigator = originalNavigator;
60
69
  });
61
70
 
62
71
  it('should support webXR', async () => {
@@ -65,19 +74,29 @@ describe('dive/info/DIVEInfo', () => {
65
74
  isSessionSupported: jest.fn().mockResolvedValue(true),
66
75
  },
67
76
  });
68
- const supports = await DIVEInfo.GetSupportsWebXR();
77
+ const restoreSecureContext = window.isSecureContext;
78
+ window.isSecureContext = true;
79
+
80
+ const supports = await SystemInfo.GetSupportsWebXR();
69
81
  expect(supports).toBe(true);
82
+
83
+ window.isSecureContext = restoreSecureContext;
70
84
  });
71
85
 
72
86
  it('should not support webXR (xr undefined)', async () => {
73
87
  mockNavigator({
74
88
  xr: undefined,
75
89
  });
76
- const supports = await DIVEInfo.GetSupportsWebXR();
90
+ const restoreSecureContext = window.isSecureContext;
91
+ window.isSecureContext = true;
92
+
93
+ const supports = await SystemInfo.GetSupportsWebXR();
77
94
  expect(supports).toBe(false);
78
95
 
79
- const reason = DIVEInfo.GetWebXRUnsupportedReason();
80
- expect(reason).toBe(WebXRUnsupportedReason.UNKNWON_ERROR);
96
+ const reason = SystemInfo.GetWebXRUnsupportedReason();
97
+ expect(reason).toBe(EWebXRUnsupportedReason.NO_WEBXR_API);
98
+
99
+ window.isSecureContext = restoreSecureContext;
81
100
  });
82
101
 
83
102
  it('should not support webXR (xr undefined & isSecureContext false)', async () => {
@@ -85,11 +104,11 @@ describe('dive/info/DIVEInfo', () => {
85
104
  mockNavigator({
86
105
  xr: undefined,
87
106
  });
88
- const supports = await DIVEInfo.GetSupportsWebXR();
107
+ const supports = await SystemInfo.GetSupportsWebXR();
89
108
  expect(supports).toBe(false);
90
109
 
91
- const reason = DIVEInfo.GetWebXRUnsupportedReason();
92
- expect(reason).toBe(WebXRUnsupportedReason.NO_HTTPS);
110
+ const reason = SystemInfo.GetWebXRUnsupportedReason();
111
+ expect(reason).toBe(EWebXRUnsupportedReason.NO_HTTPS);
93
112
  });
94
113
 
95
114
  it('should get empty reason (not checked)', async () => {
@@ -98,9 +117,15 @@ describe('dive/info/DIVEInfo', () => {
98
117
  isSessionSupported: jest.fn().mockResolvedValue(true),
99
118
  },
100
119
  });
120
+ const restoreSecureContext = window.isSecureContext;
121
+ window.isSecureContext = true;
122
+
123
+ await SystemInfo.GetSupportsWebXR();
101
124
  console.log = jest.fn();
102
- const reason = DIVEInfo.GetWebXRUnsupportedReason();
125
+ const reason = SystemInfo.GetWebXRUnsupportedReason();
103
126
  expect(reason).toBe(null);
127
+
128
+ window.isSecureContext = restoreSecureContext;
104
129
  });
105
130
 
106
131
  it('should get empty reason (webXR supported)', async () => {
@@ -109,8 +134,14 @@ describe('dive/info/DIVEInfo', () => {
109
134
  isSessionSupported: jest.fn().mockResolvedValue(true),
110
135
  },
111
136
  });
112
- const reason = DIVEInfo.GetWebXRUnsupportedReason();
137
+ const restoreSecureContext = window.isSecureContext;
138
+ window.isSecureContext = true;
139
+
140
+ await SystemInfo.GetSupportsWebXR();
141
+ const reason = SystemInfo.GetWebXRUnsupportedReason();
113
142
  expect(reason).toBe(null);
143
+
144
+ window.isSecureContext = restoreSecureContext;
114
145
  });
115
146
 
116
147
  it('should not support webXR', async () => {
@@ -119,13 +150,18 @@ describe('dive/info/DIVEInfo', () => {
119
150
  isSessionSupported: jest.fn().mockResolvedValue(false),
120
151
  },
121
152
  });
122
- const supports = await DIVEInfo.GetSupportsWebXR();
153
+ const restoreSecureContext = window.isSecureContext;
154
+ window.isSecureContext = true;
155
+
156
+ const supports = await SystemInfo.GetSupportsWebXR();
123
157
  expect(supports).toBe(false);
124
158
 
125
- const reason = DIVEInfo.GetWebXRUnsupportedReason();
159
+ const reason = SystemInfo.GetWebXRUnsupportedReason();
126
160
  expect(reason).toBe(
127
- WebXRUnsupportedReason.IMMERSIVE_AR_NOT_SUPPORTED_BY_DEVICE,
161
+ EWebXRUnsupportedReason.IMMERSIVE_AR_NOT_SUPPORTED_BY_DEVICE,
128
162
  );
163
+
164
+ window.isSecureContext = restoreSecureContext;
129
165
  });
130
166
 
131
167
  it('should not support webXR on error', async () => {
@@ -134,205 +170,177 @@ describe('dive/info/DIVEInfo', () => {
134
170
  isSessionSupported: jest.fn().mockRejectedValue('error'),
135
171
  },
136
172
  });
137
- const supports = await DIVEInfo.GetSupportsWebXR();
173
+ const restoreSecureContext = window.isSecureContext;
174
+ window.isSecureContext = true;
175
+
176
+ const supports = await SystemInfo.GetSupportsWebXR();
138
177
  expect(supports).toBe(false);
139
178
 
140
- const reason = DIVEInfo.GetWebXRUnsupportedReason();
141
- expect(reason).toBe(WebXRUnsupportedReason.AR_SESSION_NOT_ALLOWED);
179
+ const reason = SystemInfo.GetWebXRUnsupportedReason();
180
+ expect(reason).toBe(EWebXRUnsupportedReason.AR_PERMISSION_DENIED);
181
+
182
+ window.isSecureContext = restoreSecureContext;
142
183
  });
143
184
 
144
185
  it('should return cached value', async () => {
145
- DIVEInfo['_supportsWebXR'] = true;
186
+ SystemInfo['_supportsWebXR'] = true;
146
187
  mockNavigator({
147
188
  xr: {
148
189
  isSessionSupported: jest.fn().mockRejectedValue('error'),
149
190
  },
150
191
  });
151
- const supports = await DIVEInfo.GetSupportsWebXR();
192
+ const supports = await SystemInfo.GetSupportsWebXR();
152
193
  expect(supports).toBe(true);
153
194
  });
154
195
 
155
196
  it('should return cached value (false)', async () => {
156
- DIVEInfo['_supportsWebXR'] = false;
197
+ SystemInfo['_supportsWebXR'] = false;
157
198
  mockNavigator({
158
199
  xr: {
159
200
  isSessionSupported: jest.fn().mockRejectedValue('error'),
160
201
  },
161
202
  });
162
- const supports = await DIVEInfo.GetSupportsWebXR();
203
+ const supports = await SystemInfo.GetSupportsWebXR();
163
204
  expect(supports).toBe(false);
164
205
  });
165
206
 
166
- it('should support ARQuickLook with feature detection', () => {
167
- mockNavigator({
168
- userAgent:
169
- 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Mobile/15E148 Safari/604.',
170
- });
207
+ it('should support ARQuickLook when relList supports AR', () => {
171
208
  jest.spyOn(document, 'createElement').mockReturnValue({
172
209
  relList: { supports: () => true },
173
210
  } as unknown as HTMLAnchorElement);
174
- const supports = DIVEInfo.GetSupportsARQuickLook();
211
+ const supports = SystemInfo.GetSupportsARQuickLook();
175
212
  expect(supports).toBe(true);
176
213
  });
177
214
 
178
- it('should support ARQuickLook (iPhone, iOS 15, Safari)', () => {
179
- mockNavigator({
180
- userAgent:
181
- 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Mobile/15E148 Safari/604.',
182
- });
215
+ it('should not support ARQuickLook when relList does not support AR', () => {
183
216
  jest.spyOn(document, 'createElement').mockReturnValue({
184
217
  relList: { supports: () => false },
185
218
  } as unknown as HTMLAnchorElement);
186
- const supports = DIVEInfo.GetSupportsARQuickLook();
187
- expect(supports).toBe(true);
219
+ const supports = SystemInfo.GetSupportsARQuickLook();
220
+ expect(supports).toBe(false);
188
221
  });
189
222
 
190
- it('should support ARQuickLook (iPhone, iOS 17, Google)', () => {
191
- mockNavigator({
192
- userAgent:
193
- 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) GSA/277.0.555192628 Mobile/15E148 Safari/604.',
194
- });
195
- jest.spyOn(document, 'createElement').mockReturnValue({
196
- relList: { supports: () => false },
197
- } as unknown as HTMLAnchorElement);
198
- const supports = DIVEInfo.GetSupportsARQuickLook();
199
- expect(supports).toBe(true);
223
+ it('should be mobile (iOS)', () => {
224
+ jest.spyOn(SystemInfo, 'GetSystem').mockReturnValue(ESystem.IOS);
225
+ expect(SystemInfo.isMobile).toBe(true);
226
+ expect(SystemInfo.isDesktop).toBe(false);
200
227
  });
201
228
 
202
- it('should support ARQuickLook (iPhone, iOS 17, Chrome)', () => {
203
- mockNavigator({
204
- userAgent:
205
- 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/126.0.6478.153 Mobile/15E148 Safari/604.',
206
- });
207
- jest.spyOn(document, 'createElement').mockReturnValue({
208
- relList: { supports: () => false },
209
- } as unknown as HTMLAnchorElement);
210
- const supports = DIVEInfo.GetSupportsARQuickLook();
211
- expect(supports).toBe(true);
229
+ it('should be mobile (Android)', () => {
230
+ jest.spyOn(SystemInfo, 'GetSystem').mockReturnValue(ESystem.ANDROID);
231
+ expect(SystemInfo.isMobile).toBe(true);
232
+ expect(SystemInfo.isDesktop).toBe(false);
233
+ });
234
+
235
+ it('should be desktop (Windows)', () => {
236
+ jest.spyOn(SystemInfo, 'GetSystem').mockReturnValue(ESystem.WINDOWS);
237
+ expect(SystemInfo.isMobile).toBe(false);
238
+ expect(SystemInfo.isDesktop).toBe(true);
239
+ });
240
+
241
+ it('should be desktop (MacOS)', () => {
242
+ jest.spyOn(SystemInfo, 'GetSystem').mockReturnValue(ESystem.MACOS);
243
+ expect(SystemInfo.isMobile).toBe(false);
244
+ expect(SystemInfo.isDesktop).toBe(true);
245
+ });
246
+
247
+ it('should be desktop (Linux)', () => {
248
+ jest.spyOn(SystemInfo, 'GetSystem').mockReturnValue(ESystem.LINUX);
249
+ expect(SystemInfo.isMobile).toBe(false);
250
+ expect(SystemInfo.isDesktop).toBe(true);
251
+ });
252
+
253
+ it('should be desktop (Unknown)', () => {
254
+ jest.spyOn(SystemInfo, 'GetSystem').mockReturnValue(ESystem.UNKNOWN);
255
+ expect(SystemInfo.isMobile).toBe(false);
256
+ expect(SystemInfo.isDesktop).toBe(true);
212
257
  });
213
258
 
214
- it('should support ARQuickLook (iPhone, iOS 17, Chrome)', () => {
259
+ it('should be capable of AR (ARQuickLook)', async () => {
260
+ jest.spyOn(SystemInfo, 'GetSupportsARQuickLook').mockReturnValue(true);
261
+ jest.spyOn(SystemInfo, 'GetSupportsWebXR').mockResolvedValue(false);
262
+ expect(await SystemInfo.GetIsARCapable()).toBe(true);
263
+ });
264
+
265
+ it('should be capable of AR (WebXR)', async () => {
266
+ jest.spyOn(SystemInfo, 'GetSupportsARQuickLook').mockReturnValue(false);
267
+ jest.spyOn(SystemInfo, 'GetSupportsWebXR').mockResolvedValue(true);
268
+ expect(await SystemInfo.GetIsARCapable()).toBe(true);
269
+ });
270
+
271
+ it('should not be capable of AR', async () => {
272
+ jest.spyOn(SystemInfo, 'GetSupportsARQuickLook').mockReturnValue(false);
273
+ jest.spyOn(SystemInfo, 'GetSupportsWebXR').mockResolvedValue(false);
274
+ expect(await SystemInfo.GetIsARCapable()).toBe(false);
275
+ });
276
+
277
+ it('should support SceneViewer (Android, Chrome 89+)', () => {
215
278
  mockNavigator({
216
279
  userAgent:
217
- 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_1 like Mac OS X) AppleWebKit/605.1.11 (KHTML, like Gecko) Version/11.1 Mobile/11E148 CriOS/604.',
280
+ 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 Mobile Safari/537.36',
218
281
  });
219
- jest.spyOn(document, 'createElement').mockReturnValue({
220
- relList: { supports: () => false },
221
- } as unknown as HTMLAnchorElement);
222
- const supports = DIVEInfo.GetSupportsARQuickLook();
282
+ const supports = SystemInfo.GetSupportsSceneViewer();
223
283
  expect(supports).toBe(true);
224
284
  });
225
285
 
226
- it('should support ARQuickLook (iPhone, iOS 17, Firefox)', () => {
286
+ it('should support SceneViewer (Android, Chrome 126+)', () => {
227
287
  mockNavigator({
228
288
  userAgent:
229
- 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_1 like Mac OS X) AppleWebKit/605.1.11 (KHTML, like Gecko) Version/11.1 Mobile/11E148 FxiOS/604.',
289
+ 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.153 Mobile Safari/537.36',
230
290
  });
231
- jest.spyOn(document, 'createElement').mockReturnValue({
232
- relList: { supports: () => false },
233
- } as unknown as HTMLAnchorElement);
234
- const supports = DIVEInfo.GetSupportsARQuickLook();
291
+ const supports = SystemInfo.GetSupportsSceneViewer();
235
292
  expect(supports).toBe(true);
236
293
  });
237
294
 
238
- it('should not support ARQuickLook (Android)', () => {
295
+ it('should not support SceneViewer (Android, Chrome <89)', () => {
239
296
  mockNavigator({
240
297
  userAgent:
241
- 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Mobile Safari/537.3',
298
+ 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4389.72 Mobile Safari/537.36',
242
299
  });
243
- jest.spyOn(document, 'createElement').mockReturnValue({
244
- relList: { supports: () => false },
245
- } as unknown as HTMLAnchorElement);
246
- const supports = DIVEInfo.GetSupportsARQuickLook();
300
+ const supports = SystemInfo.GetSupportsSceneViewer();
247
301
  expect(supports).toBe(false);
248
302
  });
249
303
 
250
- it('should not support ARQuickLook (iPhone, no iOS version)', () => {
304
+ it('should not support SceneViewer (Android, no Chrome)', () => {
251
305
  mockNavigator({
252
306
  userAgent:
253
- 'Mozilla/5.0 (iPhone; CPU iPhone OS like Mac OS X) AppleWebKit/605.1.11 (KHTML, like Gecko) Version/11.1 Mobile/11E148 Safari/604.',
307
+ 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Mobile Safari/537.36',
254
308
  });
255
- jest.spyOn(document, 'createElement').mockReturnValue({
256
- relList: { supports: () => false },
257
- } as unknown as HTMLAnchorElement);
258
- const supports = DIVEInfo.GetSupportsARQuickLook();
309
+ const supports = SystemInfo.GetSupportsSceneViewer();
259
310
  expect(supports).toBe(false);
260
311
  });
261
312
 
262
- it('should not support ARQuickLook (iPhone, iOS 17, no browser)', () => {
313
+ it('should not support SceneViewer (iOS)', () => {
263
314
  mockNavigator({
264
- userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_1 like Mac OS X)',
315
+ userAgent:
316
+ 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
265
317
  });
266
- jest.spyOn(document, 'createElement').mockReturnValue({
267
- relList: { supports: () => false },
268
- } as unknown as HTMLAnchorElement);
269
- const supports = DIVEInfo.GetSupportsARQuickLook();
318
+ const supports = SystemInfo.GetSupportsSceneViewer();
270
319
  expect(supports).toBe(false);
271
320
  });
272
321
 
273
- it('should not support ARQuickLook (iPhone, iOS <12, Safari)', () => {
322
+ it('should not support SceneViewer (Windows)', () => {
274
323
  mockNavigator({
275
324
  userAgent:
276
- 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_1 like Mac OS X) AppleWebKit/605.1.11 (KHTML, like Gecko) Version/11.1 Mobile/11E148 Safari/604.',
325
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.153 Safari/537.36',
277
326
  });
278
- jest.spyOn(document, 'createElement').mockReturnValue({
279
- relList: { supports: () => false },
280
- } as unknown as HTMLAnchorElement);
281
- const supports = DIVEInfo.GetSupportsARQuickLook();
327
+ const supports = SystemInfo.GetSupportsSceneViewer();
282
328
  expect(supports).toBe(false);
283
329
  });
284
330
 
285
- it('should be mobile (iOS)', () => {
286
- jest.spyOn(DIVEInfo, 'GetSystem').mockReturnValue('iOS');
287
- expect(DIVEInfo.isMobile).toBe(true);
288
- expect(DIVEInfo.isDesktop).toBe(false);
289
- });
290
-
291
- it('should be mobile (Android)', () => {
292
- jest.spyOn(DIVEInfo, 'GetSystem').mockReturnValue('Android');
293
- expect(DIVEInfo.isMobile).toBe(true);
294
- expect(DIVEInfo.isDesktop).toBe(false);
295
- });
296
-
297
- it('should be desktop (Windows)', () => {
298
- jest.spyOn(DIVEInfo, 'GetSystem').mockReturnValue('Windows');
299
- expect(DIVEInfo.isMobile).toBe(false);
300
- expect(DIVEInfo.isDesktop).toBe(true);
301
- });
302
-
303
- it('should be desktop (MacOS)', () => {
304
- jest.spyOn(DIVEInfo, 'GetSystem').mockReturnValue('MacOS');
305
- expect(DIVEInfo.isMobile).toBe(false);
306
- expect(DIVEInfo.isDesktop).toBe(true);
307
- });
308
-
309
- it('should be desktop (Linux)', () => {
310
- jest.spyOn(DIVEInfo, 'GetSystem').mockReturnValue('Linux');
311
- expect(DIVEInfo.isMobile).toBe(false);
312
- expect(DIVEInfo.isDesktop).toBe(true);
313
- });
314
-
315
- it('should be desktop (Unknown)', () => {
316
- jest.spyOn(DIVEInfo, 'GetSystem').mockReturnValue('Unknown');
317
- expect(DIVEInfo.isMobile).toBe(false);
318
- expect(DIVEInfo.isDesktop).toBe(true);
319
- });
320
-
321
- it('should be capable of AR (ARQuickLook)', async () => {
322
- jest.spyOn(DIVEInfo, 'GetSupportsARQuickLook').mockReturnValue(true);
323
- jest.spyOn(DIVEInfo, 'GetSupportsWebXR').mockResolvedValue(false);
324
- expect(await DIVEInfo.GetIsARCapable()).toBe(true);
325
- });
326
-
327
- it('should be capable of AR (WebXR)', async () => {
328
- jest.spyOn(DIVEInfo, 'GetSupportsARQuickLook').mockReturnValue(false);
329
- jest.spyOn(DIVEInfo, 'GetSupportsWebXR').mockResolvedValue(true);
330
- expect(await DIVEInfo.GetIsARCapable()).toBe(true);
331
+ it('should not support SceneViewer (no window)', () => {
332
+ const originalWindow = global.window;
333
+ global.window = undefined as any;
334
+ const supports = SystemInfo.GetSupportsSceneViewer();
335
+ expect(supports).toBe(false);
336
+ global.window = originalWindow;
331
337
  });
332
338
 
333
- it('should not be capable of AR', async () => {
334
- jest.spyOn(DIVEInfo, 'GetSupportsARQuickLook').mockReturnValue(false);
335
- jest.spyOn(DIVEInfo, 'GetSupportsWebXR').mockResolvedValue(false);
336
- expect(await DIVEInfo.GetIsARCapable()).toBe(false);
339
+ it('should not support SceneViewer (no navigator)', () => {
340
+ const originalNavigator = global.navigator;
341
+ global.navigator = undefined as any;
342
+ const supports = SystemInfo.GetSupportsSceneViewer();
343
+ expect(supports).toBe(false);
344
+ global.navigator = originalNavigator;
337
345
  });
338
346
  });
@@ -4,9 +4,9 @@
4
4
  * @module
5
5
  */
6
6
 
7
- export interface DIVEMovable {
8
- isMovable: true;
9
- onMoveStart?: () => void;
10
- onMove?: () => void;
11
- onMoveEnd?: () => void;
7
+ export class DIVEMovable {
8
+ readonly isMovable: true = true;
9
+ public onMoveStart?(): void;
10
+ public onMove?(): void;
11
+ public onMoveEnd?(): void;
12
12
  }
@@ -4,8 +4,8 @@
4
4
  * @module
5
5
  */
6
6
 
7
- export interface DIVESelectable {
8
- isSelectable: true;
9
- onSelect?: () => void;
10
- onDeselect?: () => void;
7
+ export class DIVESelectable {
8
+ readonly isSelectable: true = true;
9
+ public onSelect?(): void;
10
+ public onDeselect?(): void;
11
11
  }
@@ -0,0 +1,48 @@
1
+ import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
2
+ import { USDZLoader } from 'three/examples/jsm/loaders/USDZLoader';
3
+ import { Object3D } from 'three';
4
+ import { type FileType, SUPPORTED_FILE_TYPES } from '../types';
5
+
6
+ export class Loader {
7
+ private _gltfLoader: GLTFLoader;
8
+ private _usdzLoader: USDZLoader;
9
+
10
+ constructor() {
11
+ this._gltfLoader = new GLTFLoader();
12
+ this._usdzLoader = new USDZLoader();
13
+ }
14
+
15
+ public async load(uri: string): Promise<Object3D> {
16
+ const type = this._getFileTypeFromUri(uri);
17
+ return this._load(uri, type);
18
+ }
19
+
20
+ private _getFileTypeFromUri(uri: string): FileType {
21
+ const extension = uri.split('.').pop()?.toLowerCase() as FileType;
22
+ if (!extension || !SUPPORTED_FILE_TYPES.includes(extension)) {
23
+ throw new Error(`Unsupported file type: ${extension}`);
24
+ }
25
+ return extension;
26
+ }
27
+
28
+ private async _load(uri: string, type: FileType): Promise<Object3D> {
29
+ switch (type) {
30
+ case 'glb':
31
+ case 'gltf': {
32
+ return this._loadGltf(uri);
33
+ }
34
+ case 'usdz': {
35
+ return this._loadUsdz(uri);
36
+ }
37
+ }
38
+ }
39
+
40
+ private async _loadGltf(uri: string): Promise<Object3D> {
41
+ const gltf = await this._gltfLoader.loadAsync(uri);
42
+ return gltf.scene;
43
+ }
44
+
45
+ private async _loadUsdz(uri: string): Promise<Object3D> {
46
+ return this._usdzLoader.loadAsync(uri);
47
+ }
48
+ }
@@ -1,6 +1,11 @@
1
- import { Mesh, MeshStandardMaterial, Raycaster, Vector3 } from 'three';
1
+ import {
2
+ Mesh,
3
+ MeshStandardMaterial,
4
+ type Object3D,
5
+ Raycaster,
6
+ Vector3,
7
+ } from 'three';
2
8
  import { PRODUCT_LAYER_MASK } from '../constant/VisibilityLayerMask';
3
- import type { GLTF } from 'three/examples/jsm/loaders/GLTFLoader';
4
9
  import { findSceneRecursive } from '../helper/findSceneRecursive/findSceneRecursive';
5
10
  import { type COMMaterial } from '../com/types';
6
11
  import { DIVENode } from '../node/Node';
@@ -22,11 +27,11 @@ export class DIVEModel extends DIVENode {
22
27
  private _mesh: Mesh | null = null;
23
28
  private _material: MeshStandardMaterial | null = null;
24
29
 
25
- public SetModel(gltf: GLTF): void {
30
+ public SetModel(gltf: Object3D): void {
26
31
  this.clear();
27
32
  this._boundingBox.makeEmpty();
28
33
 
29
- gltf.scene.traverse((child) => {
34
+ gltf.traverse((child) => {
30
35
  child.castShadow = true;
31
36
  child.receiveShadow = true;
32
37
 
@@ -47,7 +52,7 @@ export class DIVEModel extends DIVENode {
47
52
  }
48
53
  });
49
54
 
50
- this.add(gltf.scene);
55
+ this.add(gltf);
51
56
  }
52
57
 
53
58
  public SetMaterial(material: Partial<COMMaterial>): void {
@@ -2,7 +2,6 @@ import { RaycasterIntersectObjectMock } from '../../../__mocks__/three';
2
2
 
3
3
  import { DIVEModel } from '../Model';
4
4
  import { DIVECommunication } from '../../com/Communication';
5
- import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader';
6
5
  import { DIVEScene } from '../../scene/Scene';
7
6
  import {
8
7
  Vector3,
@@ -29,12 +28,6 @@ jest.mock('../../com/Communication.ts', () => {
29
28
  const object = new Object3D();
30
29
  object.children.push(new Mesh());
31
30
 
32
- const gltf = {
33
- scene: {
34
- ...object,
35
- },
36
- } as unknown as GLTF;
37
-
38
31
  jest.spyOn(DIVECommunication, 'get').mockReturnValue({
39
32
  PerformAction: jest.fn(),
40
33
  } as unknown as DIVECommunication);
@@ -57,11 +50,11 @@ describe('dive/model/DIVEModel', () => {
57
50
  });
58
51
 
59
52
  it('should set model', () => {
60
- expect(() => model.SetModel(gltf)).not.toThrow();
53
+ expect(() => model.SetModel(object)).not.toThrow();
61
54
  });
62
55
 
63
56
  it('should place on floor', () => {
64
- model.SetModel(gltf);
57
+ model.SetModel(object);
65
58
 
66
59
  const com = DIVECommunication.get('id')!;
67
60
  const spyPerformAction = jest.spyOn(com, 'PerformAction');
@@ -204,14 +197,14 @@ describe('dive/model/DIVEModel', () => {
204
197
 
205
198
  it('should set model material when material already set before', () => {
206
199
  model.SetMaterial({ roughness: 0.5 } as COMMaterial);
207
- expect(() => model.SetModel(gltf)).not.toThrow();
200
+ expect(() => model.SetModel(object)).not.toThrow();
208
201
  expect(
209
202
  (model['_mesh']?.material as MeshStandardMaterial).roughness,
210
203
  ).toBe(0.5);
211
204
  });
212
205
 
213
206
  it('should set material to model when model already set before', () => {
214
- model.SetModel(gltf);
207
+ model.SetModel(object);
215
208
  expect(() =>
216
209
  model.SetMaterial({ roughness: 0.5 } as COMMaterial),
217
210
  ).not.toThrow();