@shopware-ag/dive 1.19.0 → 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 (60) hide show
  1. package/build/dive.cjs +224 -219
  2. package/build/dive.cjs.map +1 -1
  3. package/build/dive.js +233 -21947
  4. package/build/dive.js.map +1 -1
  5. package/build/dive.mjs +26111 -0
  6. package/build/dive.mjs.map +1 -0
  7. package/build/src/ar/AR.d.ts +37 -14
  8. package/build/src/ar/arquicklook/ARQuickLook.d.ts +5 -6
  9. package/build/src/ar/sceneviewer/SceneViewer.d.ts +42 -6
  10. package/build/src/com/actions/scene/launchar.d.ts +5 -2
  11. package/build/src/converter/Converter.d.ts +21 -0
  12. package/build/src/dive.d.ts +2 -2
  13. package/build/src/exporter/Exporter.d.ts +11 -0
  14. package/build/src/helper/applyMixins/applyMixins.d.ts +22 -6
  15. package/build/src/info/Info.d.ts +37 -13
  16. package/build/src/interface/Movable.d.ts +5 -5
  17. package/build/src/interface/Selectable.d.ts +4 -4
  18. package/build/src/loader/Loader.d.ts +11 -0
  19. package/build/src/model/Model.d.ts +2 -2
  20. package/build/src/node/Node.d.ts +3 -3
  21. package/build/src/scene/root/Root.d.ts +1 -1
  22. package/build/src/types/ExporterOptions.d.ts +15 -0
  23. package/build/src/types/FileTypes.d.ts +27 -0
  24. package/build/src/types/index.d.ts +4 -0
  25. package/build/src/types/info/index.d.ts +66 -0
  26. package/package.json +16 -1
  27. package/src/ar/AR.ts +72 -69
  28. package/src/ar/__test__/AR.test.ts +194 -105
  29. package/src/ar/arquicklook/ARQuickLook.ts +32 -72
  30. package/src/ar/arquicklook/__test__/ARQuickLook.test.ts +89 -38
  31. package/src/ar/sceneviewer/SceneViewer.ts +96 -51
  32. package/src/ar/sceneviewer/__test__/SceneViewer.test.ts +144 -47
  33. package/src/ar/webxr/WebXR.ts +5 -4
  34. package/src/com/Communication.ts +5 -7
  35. package/src/com/__test__/Communication.test.ts +9 -3
  36. package/src/com/actions/scene/launchar.ts +2 -2
  37. package/src/converter/Converter.ts +117 -0
  38. package/src/dive.ts +3 -3
  39. package/src/exporter/Exporter.ts +75 -0
  40. package/src/helper/applyMixins/applyMixins.ts +59 -7
  41. package/src/info/Info.ts +99 -75
  42. package/src/info/__test__/Info.test.ts +162 -154
  43. package/src/interface/Movable.ts +5 -5
  44. package/src/interface/Selectable.ts +4 -4
  45. package/src/loader/Loader.ts +48 -0
  46. package/src/model/Model.ts +10 -5
  47. package/src/model/__test__/Model.test.ts +4 -11
  48. package/src/node/Node.ts +7 -5
  49. package/src/scene/root/Root.ts +4 -4
  50. package/src/scene/root/__test__/Root.test.ts +4 -4
  51. package/src/types/ExporterOptions.ts +14 -0
  52. package/src/types/FileTypes.ts +37 -0
  53. package/src/types/index.ts +26 -0
  54. package/src/types/info/index.ts +76 -0
  55. package/build/src/exporters/usdz/USDZExporter.d.ts +0 -15
  56. package/build/src/loadingmanager/LoadingManager.d.ts +0 -14
  57. package/src/exporters/usdz/USDZExporter.ts +0 -21
  58. package/src/exporters/usdz/__test__/USDZExporter.test.ts +0 -57
  59. package/src/loadingmanager/LoadingManager.ts +0 -50
  60. package/src/loadingmanager/__test__/LoadingManager.test.ts +0 -27
@@ -0,0 +1,117 @@
1
+ import { Loader } from '../loader/Loader';
2
+ import { Exporter } from '../exporter/Exporter';
3
+ import {
4
+ type FileType,
5
+ SUPPORTED_FILE_TYPES,
6
+ type ExportOptions,
7
+ } from '../types';
8
+
9
+ export class ConversionError extends Error {
10
+ constructor(
11
+ message: string,
12
+ public readonly cause?: unknown,
13
+ ) {
14
+ super(message);
15
+ this.name = 'ConversionError';
16
+ }
17
+ }
18
+
19
+ export class FileTypeError extends ConversionError {
20
+ constructor(extension: string) {
21
+ super(
22
+ `Unsupported file type: ${extension}. Supported types are: ${SUPPORTED_FILE_TYPES.join(', ')}`,
23
+ );
24
+ this.name = 'FileTypeError';
25
+ }
26
+ }
27
+
28
+ export class NetworkError extends ConversionError {
29
+ constructor(uri: string, cause?: unknown) {
30
+ super(`Failed to fetch file from ${uri}`, cause);
31
+ this.name = 'NetworkError';
32
+ }
33
+ }
34
+
35
+ export class Converter {
36
+ private _loader: Loader;
37
+ private _exporter: Exporter;
38
+
39
+ constructor(private readonly _uri: string) {
40
+ this._loader = new Loader();
41
+ this._exporter = new Exporter();
42
+ }
43
+
44
+ public static convert(uri: string): Converter {
45
+ return new Converter(uri);
46
+ }
47
+
48
+ public async to<T extends FileType>(
49
+ type: T,
50
+ options?: ExportOptions<T>,
51
+ ): Promise<ArrayBuffer> {
52
+ try {
53
+ const sourceType = this._getFileTypeFromUri();
54
+
55
+ // If source and target types match, just return the file content
56
+ if (sourceType === type) {
57
+ return await this._loadFile();
58
+ }
59
+
60
+ // Otherwise, convert through Object3D
61
+ const object3D = await this._loader.load(this._uri);
62
+ return await this._exporter.export(object3D, type, options);
63
+ } catch (error) {
64
+ if (error instanceof ConversionError) {
65
+ throw error;
66
+ }
67
+ throw new ConversionError('Failed to convert file', error);
68
+ }
69
+ }
70
+
71
+ private _getFileTypeFromUri(): FileType {
72
+ const extension = this._uri.split('.').pop()?.toLowerCase();
73
+ if (!extension) {
74
+ throw new FileTypeError('no extension');
75
+ }
76
+ if (!SUPPORTED_FILE_TYPES.includes(extension as FileType)) {
77
+ throw new FileTypeError(extension);
78
+ }
79
+ return extension as FileType;
80
+ }
81
+
82
+ private async _loadFile(): Promise<ArrayBuffer> {
83
+ try {
84
+ const response = await fetch(this._uri);
85
+ if (!response.ok) {
86
+ throw new NetworkError(
87
+ this._uri,
88
+ `HTTP error! status: ${response.status}`,
89
+ );
90
+ }
91
+ return response.arrayBuffer();
92
+ } catch (error) {
93
+ if (error instanceof NetworkError) {
94
+ throw error;
95
+ }
96
+ throw new NetworkError(this._uri, error);
97
+ }
98
+ }
99
+ }
100
+
101
+ // Example usage:
102
+ // Converter.convert('https://example.com/model.glb').to('usdz', {
103
+ // usdz: {
104
+ // ar: {
105
+ // anchoring: { type: 'plane' },
106
+ // planeAnchoring: { alignment: 'horizontal' }
107
+ // }
108
+ // }
109
+ // });
110
+ //
111
+ // Converter.convert('https://example.com/model.usdz').to('gltf', {
112
+ // gltf: {
113
+ // onlyVisible: true,
114
+ // maxTextureSize: 2048,
115
+ // includeCustomExtensions: true
116
+ // }
117
+ // });
package/src/dive.ts CHANGED
@@ -18,7 +18,7 @@ import { DIVEAnimationSystem } from './animation/AnimationSystem.ts';
18
18
  import DIVEAxisCamera from './axiscamera/AxisCamera.ts';
19
19
  import { getObjectDelta } from './helper/getObjectDelta/getObjectDelta.ts';
20
20
  import { MathUtils } from 'three';
21
- import { DIVEInfo } from './info/Info.ts';
21
+ import { SystemInfo } from './info/Info.ts';
22
22
  import pkgjson from '../package.json';
23
23
 
24
24
  export type DIVESettings = {
@@ -158,8 +158,8 @@ export default class DIVE {
158
158
  return this.renderer.domElement;
159
159
  }
160
160
 
161
- public get Info(): DIVEInfo {
162
- return DIVEInfo;
161
+ public get Info(): SystemInfo {
162
+ return SystemInfo;
163
163
  }
164
164
 
165
165
  // setters
@@ -0,0 +1,75 @@
1
+ import { Object3D } from 'three';
2
+ import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter';
3
+ import { USDZExporter } from 'three/examples/jsm/exporters/USDZExporter';
4
+ import {
5
+ type FileType,
6
+ type GLTFExporterOptions,
7
+ type USDZExporterOptions,
8
+ type ExportOptions,
9
+ } from '../types';
10
+
11
+ export class Exporter {
12
+ private _gltfExporter: GLTFExporter;
13
+ private _usdzExporter: USDZExporter;
14
+
15
+ constructor() {
16
+ this._gltfExporter = new GLTFExporter();
17
+ this._usdzExporter = new USDZExporter();
18
+ }
19
+
20
+ public async export<T extends FileType>(
21
+ object: Object3D,
22
+ type: T,
23
+ options?: ExportOptions<T>,
24
+ ): Promise<ArrayBuffer> {
25
+ switch (type) {
26
+ case 'glb': {
27
+ return this._exportGlb(object, options);
28
+ }
29
+ case 'gltf': {
30
+ return this._exportGltf(object, options);
31
+ }
32
+ case 'usdz': {
33
+ return this._exportUsdz(object, options);
34
+ }
35
+ default:
36
+ throw new Error(`Unsupported file type: ${type}`);
37
+ }
38
+ }
39
+
40
+ private async _exportGlb(
41
+ object: Object3D,
42
+ options?: GLTFExporterOptions,
43
+ ): Promise<ArrayBuffer> {
44
+ const result = await this._gltfExporter.parseAsync(object, {
45
+ ...options,
46
+ binary: true,
47
+ });
48
+ if (result instanceof ArrayBuffer) {
49
+ return result;
50
+ }
51
+ throw new Error('Failed to export GLB: expected ArrayBuffer');
52
+ }
53
+
54
+ private async _exportGltf(
55
+ object: Object3D,
56
+ options?: GLTFExporterOptions,
57
+ ): Promise<ArrayBuffer> {
58
+ const json = await this._gltfExporter.parseAsync(object, {
59
+ ...options,
60
+ binary: false,
61
+ });
62
+ const text = JSON.stringify(json);
63
+ const encoder = new TextEncoder();
64
+ const bytes = encoder.encode(text);
65
+ return bytes.buffer as ArrayBuffer;
66
+ }
67
+
68
+ private async _exportUsdz(
69
+ object: Object3D,
70
+ options?: USDZExporterOptions,
71
+ ): Promise<ArrayBuffer> {
72
+ const result = await this._usdzExporter.parse(object, options);
73
+ return result.buffer as ArrayBuffer;
74
+ }
75
+ }
@@ -1,11 +1,62 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /* eslint-disable @typescript-eslint/ban-types */
3
+ // A generic constructor type.
4
+ type Constructor<T = {}> = new (...args: any[]) => T;
5
+
6
+ // Converts a union of types to an intersection of types.
7
+ type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
8
+ k: infer I,
9
+ ) => void
10
+ ? I
11
+ : never;
12
+
13
+ // Merges the instance types of the base class and all mixin classes.
14
+ type MixedInstance<
15
+ T extends Constructor,
16
+ K extends readonly Constructor[],
17
+ > = InstanceType<T> & UnionToIntersection<InstanceType<K[number]>>;
18
+
19
+ // Recursively flatten the constructor parameter lists for a tuple of constructors.
20
+ type FlattenConstructorParams<T extends readonly Constructor[]> =
21
+ T extends readonly [infer First, ...infer Rest]
22
+ ? First extends Constructor
23
+ ? Rest extends readonly Constructor[]
24
+ ? [
25
+ ...ConstructorParameters<First>,
26
+ ...FlattenConstructorParams<Rest>,
27
+ ]
28
+ : ConstructorParameters<First>
29
+ : []
30
+ : [];
31
+
32
+ // Constructs the mixed constructor type.
33
+ // It accepts the parameters of T followed by all the parameters of K and
34
+ // produces an instance that is the intersection of the instance types.
35
+ type MixedConstructor<
36
+ T extends Constructor,
37
+ K extends readonly Constructor[],
38
+ > = new (
39
+ ...args: [...ConstructorParameters<T>, ...FlattenConstructorParams<K>]
40
+ ) => MixedInstance<T, K>;
41
+
1
42
  /**
2
- * Merges two class prototypes to a new one.
43
+ * Applies mixins to a base class.
44
+ *
45
+ * @param derivedCtor Base class constructor
46
+ * @param constructors Additional constructors that get mixed into the base class
47
+ * @returns A mixed constructor with the instance type of the base class and all mixin classes
48
+ * @example
49
+ * ```
50
+ * const SelectableMovableObject3D = applyMixins(Object3D, [DIVESelectable, DIVEMovable]);
51
+ * const instance = new SelectableMovableObject3D();
52
+ * instance.onMove();
53
+ * instance.onSelect();
54
+ * ```
3
55
  */
4
-
5
- export const applyMixins = (
6
- derivedCtor: { prototype: object },
7
- constructors: { prototype: object }[],
8
- ): void => {
56
+ export function applyMixins<
57
+ T extends Constructor,
58
+ K extends readonly Constructor[],
59
+ >(derivedCtor: T, constructors: K): MixedConstructor<T, K> {
9
60
  constructors.forEach((baseCtor) => {
10
61
  Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
11
62
  Object.defineProperty(
@@ -15,4 +66,5 @@ export const applyMixins = (
15
66
  );
16
67
  });
17
68
  });
18
- };
69
+ return derivedCtor as unknown as MixedConstructor<T, K>;
70
+ }
package/src/info/Info.ts CHANGED
@@ -1,137 +1,161 @@
1
- export enum WebXRUnsupportedReason {
2
- 'UNKNWON_ERROR' = 0,
3
- 'NO_HTTPS' = 1,
4
- 'IMMERSIVE_AR_NOT_SUPPORTED_BY_DEVICE' = 2,
5
- 'AR_SESSION_NOT_ALLOWED' = 3,
6
- }
1
+ import { ESystem, EWebXRUnsupportedReason } from '../types/info';
7
2
 
8
- export class DIVEInfo {
9
- private static _supportsWebXR: boolean | null = null;
10
- private static _webXRUnsupportedReason: WebXRUnsupportedReason | null =
3
+ export class SystemInfo {
4
+ private static _supportsWebXR: boolean = false;
5
+ private static _webXRUnsupportedReason: EWebXRUnsupportedReason | null =
11
6
  null;
12
7
 
13
8
  /**
14
- *
15
- * @returns The system the user is using. Possible values are "Android", "iOS", "Windows", "MacOS", "Linux" or "Unknown".
9
+ * Gets the current system (iOS, Android, Windows, etc.)
10
+ * @returns DIVESystem The current system
16
11
  */
17
- public static GetSystem(): string {
18
- const platform = navigator.platform;
19
- if (/Android/.test(navigator.userAgent)) {
20
- return 'Android';
21
- } else if (/iPhone|iPad|iPod/.test(navigator.userAgent)) {
22
- return 'iOS';
23
- } else if (platform.startsWith('Win')) {
24
- return 'Windows';
25
- } else if (platform.startsWith('Mac')) {
26
- return 'MacOS';
27
- } else if (platform.startsWith('Linux')) {
28
- return 'Linux';
29
- } else {
30
- return 'Unknown';
12
+ public static GetSystem(): ESystem {
13
+ if (typeof window === 'undefined' || !window.navigator) {
14
+ return ESystem.UNKNOWN;
15
+ }
16
+
17
+ const userAgent = window.navigator.userAgent.toLowerCase();
18
+ if (userAgent.includes('iphone') || userAgent.includes('ipad')) {
19
+ return ESystem.IOS;
20
+ }
21
+ if (userAgent.includes('android')) {
22
+ return ESystem.ANDROID;
31
23
  }
24
+ if (userAgent.includes('windows')) {
25
+ return ESystem.WINDOWS;
26
+ }
27
+ if (userAgent.includes('macintosh')) {
28
+ return ESystem.MACOS;
29
+ }
30
+ if (userAgent.includes('linux')) {
31
+ return ESystem.LINUX;
32
+ }
33
+ return ESystem.UNKNOWN;
32
34
  }
33
35
 
34
36
  /**
35
37
  * @returns A promise that resolves to a boolean indicating whether the user's device supports WebXR.
36
38
  */
37
39
  public static async GetSupportsWebXR(): Promise<boolean> {
38
- if (this._supportsWebXR !== null) {
40
+ if (this._supportsWebXR !== false) {
39
41
  return this._supportsWebXR;
40
42
  }
41
43
 
42
- // check if XRSystem is available && if https enabled
43
- if (!navigator.xr) {
44
+ // Check if we're in a secure context (HTTPS)
45
+ if (!window.isSecureContext) {
44
46
  this._supportsWebXR = false;
47
+ this._webXRUnsupportedReason = EWebXRUnsupportedReason.NO_HTTPS;
48
+ return this._supportsWebXR;
49
+ }
45
50
 
46
- if (window.isSecureContext === false) {
47
- this._webXRUnsupportedReason = WebXRUnsupportedReason.NO_HTTPS;
48
- } else {
49
- this._webXRUnsupportedReason =
50
- WebXRUnsupportedReason.UNKNWON_ERROR;
51
- }
52
-
51
+ // Check if XRSystem is available
52
+ if (!navigator.xr) {
53
+ this._supportsWebXR = false;
54
+ this._webXRUnsupportedReason = EWebXRUnsupportedReason.NO_WEBXR_API;
53
55
  return this._supportsWebXR;
54
56
  }
55
57
 
56
- // Check if immersive-vr session mode is supported
57
58
  try {
58
- const supported =
59
- await navigator.xr!.isSessionSupported('immersive-ar');
60
- if (!supported) {
59
+ // Check specifically for immersive-ar support
60
+ const arSupported =
61
+ await navigator.xr.isSessionSupported('immersive-ar');
62
+ this._supportsWebXR = arSupported;
63
+
64
+ if (!this._supportsWebXR) {
61
65
  this._webXRUnsupportedReason =
62
- WebXRUnsupportedReason.IMMERSIVE_AR_NOT_SUPPORTED_BY_DEVICE;
66
+ EWebXRUnsupportedReason.IMMERSIVE_AR_NOT_SUPPORTED_BY_DEVICE;
63
67
  }
64
- this._supportsWebXR = supported;
65
68
  } catch (error) {
66
69
  this._supportsWebXR = false;
67
70
  this._webXRUnsupportedReason =
68
- WebXRUnsupportedReason.AR_SESSION_NOT_ALLOWED;
71
+ EWebXRUnsupportedReason.AR_PERMISSION_DENIED;
69
72
  }
73
+
70
74
  return this._supportsWebXR;
71
75
  }
72
76
 
73
77
  /**
74
- * @returns The reason why WebXR is not supported on the user's device. Returns null if WebXR is supported nor not has been checked yet.
78
+ * @returns The reason why WebXR is not supported on the user's device. Returns null if WebXR is supported.
75
79
  */
76
- public static GetWebXRUnsupportedReason(): WebXRUnsupportedReason | null {
77
- if (this._supportsWebXR === null) {
78
- console.log('WebXR support has not been checked yet.');
80
+ public static GetWebXRUnsupportedReason(): EWebXRUnsupportedReason | null {
81
+ if (this._supportsWebXR) {
82
+ console.log('WebXR is supported.');
79
83
  return null;
80
84
  }
81
85
  return this._webXRUnsupportedReason;
82
86
  }
83
87
 
84
88
  /**
85
- * @returns A boolean indicating whether the user's device supports AR Quick Look.
89
+ * Checks if ARQuickLook is supported on the current device
90
+ * This checks for:
91
+ * 1. AR support via relList
92
+ *
93
+ * Requirements:
94
+ * - iOS 13.0 or later
95
+ * - Safari browser (ARQuickLook is only supported in Safari)
96
+ * - Device with AR capabilities (iPhone/iPad with LiDAR scanner or ARKit support)
97
+ *
98
+ * Note: ARQuickLook is only available in Safari on iOS. Other browsers
99
+ * (Chrome, Firefox, etc.) do not support ARQuickLook, even on iOS.
100
+ *
101
+ * @returns boolean indicating if ARQuickLook is supported
86
102
  */
87
103
  public static GetSupportsARQuickLook(): boolean {
88
104
  const a = document.createElement('a');
89
- if (a.relList.supports('ar')) {
90
- return true;
91
- }
92
-
93
- // fallback check
94
- const userAgent = navigator.userAgent;
105
+ return a.relList.supports('ar');
106
+ }
95
107
 
96
- // Check if the device is running iOS
97
- const isIOS =
98
- /iPad|iPhone|iPod/.test(userAgent) &&
99
- !(window as unknown as Window & { MSStream?: string }).MSStream;
100
- if (!isIOS) {
108
+ /**
109
+ * Checks if SceneViewer is supported on the current device
110
+ * This checks for:
111
+ * 1. Android device
112
+ * 2. Chrome browser (version 89 or later)
113
+ *
114
+ * Requirements:
115
+ * - Android 7.0 (API level 24) or later
116
+ * - Chrome for Android 89 or later
117
+ *
118
+ * Note: According to Google's documentation, if these requirements are met,
119
+ * SceneViewer will be available. If ARCore is not installed, SceneViewer will
120
+ * fall back to showing the model in 3D.
121
+ *
122
+ * @returns boolean indicating if SceneViewer is supported
123
+ */
124
+ public static GetSupportsSceneViewer(): boolean {
125
+ // Check if we're in a browser environment
126
+ if (typeof window === 'undefined' || !window.navigator) {
101
127
  return false;
102
128
  }
103
129
 
104
- // Extract iOS version
105
- const match = userAgent.match(/OS (\d+)_/);
106
- if (!match || match.length < 2) {
130
+ const userAgent = window.navigator.userAgent.toLowerCase();
131
+
132
+ // Check if we're on Android
133
+ if (!userAgent.includes('android')) {
107
134
  return false;
108
135
  }
109
- const iOSVersion = parseInt(match[1], 10);
110
-
111
- // Minimum iOS version for QuickLook support
112
- const minQuickLookVersion = 12;
113
136
 
114
- // Check if the iOS version is supported
115
- if (iOSVersion < minQuickLookVersion) {
137
+ // Check if we're using Chrome
138
+ if (!userAgent.includes('chrome')) {
116
139
  return false;
117
140
  }
118
141
 
119
- // Check for supported browser
120
- const isSupportedBrowser =
121
- /^((?!chrome|android).)*safari|CriOS|FxiOS/i.test(userAgent);
122
- if (isSupportedBrowser) {
123
- return true;
142
+ // Check Chrome version (89 or later)
143
+ const chromeVersion = userAgent.match(/chrome\/(\d+)/);
144
+ if (!chromeVersion || parseInt(chromeVersion[1]) < 89) {
145
+ return false;
124
146
  }
125
147
 
126
- // Default to false if none of the conditions are met
127
- return false;
148
+ return true;
128
149
  }
129
150
 
130
151
  /**
131
152
  * @returns A boolean indicating whether the user's device is a mobile device.
132
153
  */
133
154
  public static get isMobile(): boolean {
134
- return this.GetSystem() === 'Android' || this.GetSystem() === 'iOS';
155
+ return (
156
+ this.GetSystem() === ESystem.ANDROID ||
157
+ this.GetSystem() === ESystem.IOS
158
+ );
135
159
  }
136
160
 
137
161
  /**