@webspatial/platform-visionos 1.0.4 → 1.0.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.
Files changed (74) hide show
  1. package/package.json +2 -2
  2. package/web-spatial/EventEmitter.swift +56 -0
  3. package/web-spatial/JSBCommand.swift +348 -0
  4. package/web-spatial/SpatialObject.swift +108 -0
  5. package/web-spatial/WebMsgCommand.swift +119 -0
  6. package/web-spatial/WebSpatialApp.swift +111 -0
  7. package/web-spatial/{libs/uiKitDelegate/Window.swift → Window.swift} +1 -0
  8. package/web-spatial/manager/Dynamic3DManager.swift +114 -0
  9. package/web-spatial/manager/JSBManager.swift +148 -0
  10. package/web-spatial/manager/WKWebViewManager.swift +39 -0
  11. package/web-spatial/{libs/webView/manifest.swift → manifest.swift} +16 -5
  12. package/web-spatial/model/SpatialApp.swift +190 -0
  13. package/web-spatial/model/SpatialScene.swift +1042 -0
  14. package/web-spatial/model/Spatialized2DElement.swift +128 -0
  15. package/web-spatial/model/SpatializedDynamic3DElement.swift +34 -0
  16. package/web-spatial/model/SpatializedElement.swift +95 -0
  17. package/web-spatial/model/SpatializedStatic3DElement.swift +19 -0
  18. package/web-spatial/model/dynamic3d/Geometry.swift +95 -0
  19. package/web-spatial/model/dynamic3d/SpatialComponent.swift +72 -0
  20. package/web-spatial/model/dynamic3d/SpatialEntity.swift +211 -0
  21. package/web-spatial/model/dynamic3d/SpatialMaterial.swift +39 -0
  22. package/web-spatial/model/dynamic3d/SpatialModelEntity.swift +39 -0
  23. package/web-spatial/model/dynamic3d/SpatialModelResource.swift +38 -0
  24. package/web-spatial/model/dynamic3d/SpatialTextureResource.swift +18 -0
  25. package/web-spatial/protocol/ScrollAbleSpatialElementContainer.swift +1 -0
  26. package/web-spatial/protocol/SpatialScrollAble.swift +8 -0
  27. package/web-spatial/protocol/SpatializedElementContainer.swift +8 -0
  28. package/web-spatial/protocol/WebMsgSender.swift +5 -0
  29. package/web-spatial/types/Vec2.swift +12 -0
  30. package/web-spatial/types/Vec3.swift +7 -0
  31. package/web-spatial/view/SceneHandlerUIView.swift +92 -0
  32. package/web-spatial/{views/ui/NavView.swift → view/SpatialNavView.swift} +149 -77
  33. package/web-spatial/view/SpatialSceneContentView.swift +218 -0
  34. package/web-spatial/view/SpatialSceneView.swift +53 -0
  35. package/web-spatial/view/Spatialized2DElementView.swift +96 -0
  36. package/web-spatial/view/SpatializedDynamic3DView.swift +104 -0
  37. package/web-spatial/view/SpatializedElementView.swift +178 -0
  38. package/web-spatial/view/SpatializedStatic3DView.swift +70 -0
  39. package/web-spatial/{views → view/view-modifier}/MaterialWithBorderCornerModifier.swift +28 -8
  40. package/web-spatial/webview/SpatialWebController.swift +300 -0
  41. package/web-spatial/webview/SpatialWebView.swift +34 -0
  42. package/web-spatial/webview/SpatialWebViewModel.swift +307 -0
  43. package/web-spatial.xcodeproj/project.pbxproj +126 -207
  44. package/web-spatial/libs/EventEmitter.swift +0 -25
  45. package/web-spatial/libs/SpatialComponent.swift +0 -24
  46. package/web-spatial/libs/SpatialEntity.swift +0 -172
  47. package/web-spatial/libs/SpatialInputComponent.swift +0 -19
  48. package/web-spatial/libs/SpatialMeshResource.swift +0 -12
  49. package/web-spatial/libs/SpatialModel3DComponent.swift +0 -51
  50. package/web-spatial/libs/SpatialModelComponent.swift +0 -25
  51. package/web-spatial/libs/SpatialObject.swift +0 -140
  52. package/web-spatial/libs/SpatialPhysicallyBasedMaterial.swift +0 -19
  53. package/web-spatial/libs/SpatialViewComponent.swift +0 -8
  54. package/web-spatial/libs/SpatialWindowComponent.swift +0 -454
  55. package/web-spatial/libs/SpatialWindowContainer.swift +0 -153
  56. package/web-spatial/libs/Utils/CommandManager.swift +0 -823
  57. package/web-spatial/libs/Utils/PerfClock.swift +0 -43
  58. package/web-spatial/libs/Utils/SceneManager.swift +0 -101
  59. package/web-spatial/libs/Utils/WindowContainerMgr.swift +0 -122
  60. package/web-spatial/libs/json/JsonParser.swift +0 -45
  61. package/web-spatial/libs/webView/UpdateSystem.swift +0 -26
  62. package/web-spatial/libs/webView/backend/NativeWebView.swift +0 -350
  63. package/web-spatial/views/ImmersiveView.swift +0 -17
  64. package/web-spatial/views/OpenDismissHandlerUI.swift +0 -45
  65. package/web-spatial/views/PlainWindowContainerView.swift +0 -132
  66. package/web-spatial/views/SpatialModel3DView.swift +0 -187
  67. package/web-spatial/views/SpatialViewUI.swift +0 -168
  68. package/web-spatial/views/SpatialWebViewUI.swift +0 -179
  69. package/web-spatial/views/VolumetricWindowContainerView.swift +0 -30
  70. package/web-spatial/web_spatialApp.swift +0 -141
  71. /package/web-spatial/{libs/Utils → Utils}/ColorExtension.swift +0 -0
  72. /package/web-spatial/{libs/Utils → Utils}/Logger.swift +0 -0
  73. /package/web-spatial/{views → view}/LoadingView.swift +0 -0
  74. /package/web-spatial/{views → view/view-modifier}/HideViewModifier.swift +0 -0
@@ -0,0 +1,111 @@
1
+ import typealias RealityKit.Attachment
2
+ import typealias RealityKit.Entity
3
+ import typealias RealityKit.MeshResource
4
+ import typealias RealityKit.Model3D
5
+ import typealias RealityKit.ModelEntity
6
+ import typealias RealityKit.RealityView
7
+ import typealias RealityKit.SimpleMaterial
8
+ import SwiftUI
9
+
10
+ func getCGFloat(_ val: Double?) -> CGFloat? {
11
+ if let v = val {
12
+ return CGFloat(v)
13
+ } else {
14
+ return nil
15
+ }
16
+ }
17
+
18
+ @main
19
+ struct WebSpatialApp: App {
20
+ @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
21
+
22
+ @State var app = SpatialApp.Instance
23
+
24
+ func getDefaultSize() -> CGSize {
25
+ let ans = CGSize(
26
+ width: app
27
+ .getSceneOptions().defaultSize!.width,
28
+ height: app
29
+ .getSceneOptions().defaultSize!.height
30
+ )
31
+
32
+ return ans
33
+ }
34
+
35
+ func getDefaultSize3D() -> Size3D {
36
+ let ans = Size3D(
37
+ width: app
38
+ .getSceneOptions().defaultSize!.width,
39
+ height: app
40
+ .getSceneOptions().defaultSize!.height,
41
+ depth: app.getSceneOptions().defaultSize!.depth ?? 0
42
+ )
43
+
44
+ return ans
45
+ }
46
+
47
+ var body: some Scene {
48
+ WindowGroup(
49
+ id: SpatialScene.WindowStyle.window.rawValue,
50
+ for: String.self
51
+ ) { $windowData in
52
+ let spatialScene = SpatialApp.Instance.getScene(windowData)
53
+ SpatialSceneView(spatialScene: spatialScene!)
54
+ }
55
+ defaultValue: {
56
+ let scene = SpatialApp.Instance.createScene(
57
+ startURL,
58
+ .window,
59
+ .visible,
60
+ app.getSceneOptions()
61
+ )
62
+
63
+ return scene.id
64
+ }
65
+ .windowStyle(.plain)
66
+ .defaultSize(
67
+ getDefaultSize()
68
+ ).windowResizability(
69
+ app.getSceneOptions().windowResizability!
70
+ )
71
+
72
+ WindowGroup(id: SpatialScene.WindowStyle.volume.rawValue, for: String.self) { $windowData in
73
+ let spatialScene = SpatialApp.Instance.getScene(windowData)
74
+ SpatialSceneView(spatialScene: spatialScene!)
75
+ .frame(
76
+ minWidth: getCGFloat(
77
+ app.getSceneOptions(windowData)?.resizeRange?.minWidth),
78
+ maxWidth: getCGFloat(
79
+ app.getSceneOptions(windowData)?.resizeRange?.maxWidth),
80
+ minHeight: getCGFloat(
81
+ app.getSceneOptions(windowData)?.resizeRange?.minHeight),
82
+ maxHeight: getCGFloat(
83
+ app.getSceneOptions(windowData)?.resizeRange?.maxHeight)
84
+ )
85
+ }
86
+ defaultValue: {
87
+ let scene = SpatialApp.Instance.createScene(
88
+ startURL,
89
+ .volume,
90
+ .visible,
91
+ app.getSceneOptions()
92
+ )
93
+
94
+ return scene.id
95
+ }
96
+ .windowStyle(.volumetric)
97
+ .defaultSize(
98
+ getDefaultSize3D(),
99
+ in: .meters
100
+ ).windowResizability(
101
+ app.getSceneOptions().windowResizability!
102
+ )
103
+ .defaultWorldScaling(app.getSceneOptions().worldScaling)
104
+ .volumeWorldAlignment(app.getSceneOptions().worldAlignment)
105
+
106
+ WindowGroup(id: "loading", for: String.self) { _ in
107
+ LoadingView()
108
+ }
109
+ .defaultSize(CGSize(width: 400, height: 400))
110
+ }
111
+ }
@@ -8,6 +8,7 @@ class SceneDelegate: NSObject, ObservableObject, UIWindowSceneDelegate {
8
8
  func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
9
9
  guard let windowScene = scene as? UIWindowScene else { return }
10
10
  window = windowScene.keyWindow // << store !!!
11
+ window?.overrideUserInterfaceStyle = .light
11
12
  }
12
13
 
13
14
  // do memory cleanup after scene removed, otherwise windowContainer cannot destroy content after being dismissed
@@ -0,0 +1,114 @@
1
+ import Foundation
2
+ import RealityKit
3
+
4
+ enum GeometryCreationError: LocalizedError {
5
+ case invalidType(String)
6
+ case missingFields(String, [String])
7
+
8
+ var errorDescription: String? {
9
+ switch self {
10
+ case let .invalidType(t):
11
+ return "invalid geometry type: \(t)"
12
+ case let .missingFields(type, fields):
13
+ return "missing required fields for \(type): " + fields.joined(separator: ", ")
14
+ }
15
+ }
16
+ }
17
+
18
+ class Dynamic3DManager {
19
+ static func createEntity(_ props: CreateSpatialEntity) -> SpatialEntity {
20
+ let entity = SpatialEntity()
21
+ entity.name = props.name ?? ""
22
+ return entity
23
+ }
24
+
25
+ static func createModelComponent(mesh: Geometry, mats: [SpatialMaterial]) -> SpatialModelComponent {
26
+ return SpatialModelComponent(mesh: mesh, mats: mats)
27
+ }
28
+
29
+ static func createGeometry(_ props: CreateGeometryProperties) throws -> Geometry {
30
+ guard let type = GeometryType(rawValue: props.type) else {
31
+ throw GeometryCreationError.invalidType(props.type)
32
+ }
33
+ switch type {
34
+ case .BoxGeometry:
35
+ var missing: [String] = []
36
+ if props.width == nil { missing.append("width") }
37
+ if props.height == nil { missing.append("height") }
38
+ if props.depth == nil { missing.append("depth") }
39
+ if !missing.isEmpty { throw GeometryCreationError.missingFields("BoxGeometry", missing) }
40
+ return BoxGeometry(width: props.width!, height: props.height!, depth: props.depth!, cornerRadius: props.cornerRadius ?? 0, splitFaces: props.splitFaces ?? false)
41
+ case .PlaneGeometry:
42
+ var missing: [String] = []
43
+ if props.width == nil { missing.append("width") }
44
+ if props.height == nil { missing.append("height") }
45
+ if !missing.isEmpty { throw GeometryCreationError.missingFields("PlaneGeometry", missing) }
46
+ return PlaneGeometry(width: props.width!, height: props.height!, cornerRadius: props.cornerRadius ?? 0)
47
+ case .SphereGeometry:
48
+ var missing: [String] = []
49
+ if props.radius == nil { missing.append("radius") }
50
+ if !missing.isEmpty { throw GeometryCreationError.missingFields("SphereGeometry", missing) }
51
+ return SphereGeometry(radius: props.radius!)
52
+ case .ConeGeometry:
53
+ var missing: [String] = []
54
+ if props.radius == nil { missing.append("radius") }
55
+ if props.height == nil { missing.append("height") }
56
+ if !missing.isEmpty { throw GeometryCreationError.missingFields("ConeGeometry", missing) }
57
+ return ConeGeometry(radius: props.radius!, height: props.height!)
58
+ case .CylinderGeometry:
59
+ var missing: [String] = []
60
+ if props.radius == nil { missing.append("radius") }
61
+ if props.height == nil { missing.append("height") }
62
+ if !missing.isEmpty { throw GeometryCreationError.missingFields("CylinderGeometry", missing) }
63
+ return CylinderGeometry(radius: props.radius!, height: props.height!)
64
+ }
65
+ }
66
+
67
+ // Error messages are thrown from createGeometry using GeometryCreationError
68
+
69
+ static func createUnlitMaterial(_ props: CreateUnlitMaterial, _ tex: TextureResource? = nil) -> SpatialUnlitMaterial {
70
+ return SpatialUnlitMaterial(props.color ?? "#FFFFFF", tex, props.transparent ?? true, props.opacity ?? 1)
71
+ }
72
+
73
+ static func loadResourceToLocal(_ urlString: String, loadComplete: @escaping (Result<URL, Error>) -> Void) {
74
+ guard let url = URL(string: urlString) else {
75
+ loadComplete(.failure(NSError(domain: "Invalid URL", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to create URL from string: \(urlString)"])))
76
+ return
77
+ }
78
+ var documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
79
+ documentsUrl.appendPathComponent(url.lastPathComponent)
80
+ let session = URLSession(configuration: URLSessionConfiguration.default)
81
+ var request = URLRequest(url: url)
82
+ request.httpMethod = "GET"
83
+ print("start load")
84
+ let task = session.downloadTask(with: request, completionHandler: { location, response, error in
85
+ if let error = error {
86
+ loadComplete(.failure(error))
87
+ return
88
+ }
89
+ if let httpResponse = response as? HTTPURLResponse, !(200 ... 299).contains(httpResponse.statusCode) {
90
+ let error = NSError(domain: "HTTP Error", code: httpResponse.statusCode, userInfo: [NSLocalizedDescriptionKey: "HTTP Error \(httpResponse.statusCode)"])
91
+ loadComplete(.failure(error))
92
+ return
93
+ }
94
+ guard let location = location else {
95
+ loadComplete(.failure(NSError(domain: "Download Error", code: 0, userInfo: [NSLocalizedDescriptionKey: "Download location is nil"])))
96
+ return
97
+ }
98
+ let fileManager = FileManager.default
99
+ do {
100
+ if fileManager.fileExists(atPath: documentsUrl.path) {
101
+ try fileManager.removeItem(atPath: documentsUrl.path)
102
+ }
103
+ try fileManager.moveItem(at: location, to: documentsUrl)
104
+ print("load complete")
105
+ loadComplete(.success(documentsUrl))
106
+ } catch {
107
+ print("File operation error: \(error)")
108
+ loadComplete(.failure(error))
109
+ }
110
+
111
+ })
112
+ task.resume()
113
+ }
114
+ }
@@ -0,0 +1,148 @@
1
+ import Foundation
2
+
3
+ protocol CommandDataProtocol: Decodable {
4
+ static var commandType: String { get }
5
+ }
6
+
7
+ protocol ReplyDataProtocol: Encodable {
8
+ static var dataType: String { get }
9
+ }
10
+
11
+ struct JsbErrorData: Encodable {
12
+ var code: ReplyCode?
13
+ var message: String?
14
+ }
15
+
16
+ enum ReplyCode: Encodable {
17
+ case TypeError
18
+ case CommandError
19
+ case InvalidSpatialObject
20
+ case InvalidMatrix
21
+ }
22
+
23
+ struct JsbError: Error, Encodable {
24
+ let code: ReplyCode
25
+ let message: String
26
+ }
27
+
28
+ class JSBManager {
29
+ typealias ResolveHandler<T> = (Result<T?, JsbError>) -> Void
30
+
31
+ private var typeMap = [String: CommandDataProtocol.Type]()
32
+ private var actionWithDataMap: [String: (_ data: CommandDataProtocol, _ event: @escaping ResolveHandler<Encodable>) -> Void] = [:]
33
+ private var actionWithoutDataMap: [String: (@escaping ResolveHandler<Encodable>) -> Void] = [:]
34
+
35
+ private let encoder = JSONEncoder()
36
+
37
+ func register<T: CommandDataProtocol>(_ type: T.Type) {
38
+ typeMap[T.commandType] = type
39
+ }
40
+
41
+ func register<T: CommandDataProtocol>(_ type: T.Type, _ event: @escaping (T, @escaping ResolveHandler<Encodable>) -> Void) {
42
+ typeMap[T.commandType] = type
43
+ actionWithDataMap[T.commandType] = { data, result in
44
+ event(data as! T, result)
45
+ }
46
+ }
47
+
48
+ func register<T: CommandDataProtocol>(_ type: T.Type, _ event: @escaping (@escaping ResolveHandler<Encodable>) -> Void) {
49
+ typeMap[T.commandType] = type
50
+ actionWithoutDataMap[T.commandType] = event
51
+ }
52
+
53
+ func remove<T: CommandDataProtocol>(_ type: T.Type) {
54
+ typeMap.removeValue(forKey: T.commandType)
55
+ actionWithDataMap.removeValue(forKey: T.commandType)
56
+ actionWithoutDataMap.removeValue(forKey: T.commandType)
57
+ }
58
+
59
+ func clear() {
60
+ typeMap = [String: CommandDataProtocol.Type]()
61
+ actionWithDataMap = [:]
62
+ actionWithoutDataMap = [:]
63
+ }
64
+
65
+ func handlerMessage(_ message: String, _ replyHandler: ((Any?, String?) -> Void)? = nil) {
66
+ do {
67
+ let jsbInfo = message.components(separatedBy: "::")
68
+ let actionKey = jsbInfo[0]
69
+ let hasData = jsbInfo.count == 2 && jsbInfo[1] != ""
70
+
71
+ if hasData {
72
+ let data = try deserialize(cmdType: actionKey, cmdContent: jsbInfo[1])
73
+ if let action = actionWithDataMap[actionKey] {
74
+ handleAction(action: { callback in
75
+ action(data!, callback)
76
+ }, replyHandler: replyHandler)
77
+ } else {
78
+ print("Invalid JSB!!!", message)
79
+ replyHandler?(nil, "Invalid JSB!!! \(message)")
80
+ }
81
+ } else {
82
+ if let action = actionWithoutDataMap[actionKey] {
83
+ handleAction(action: action, replyHandler: replyHandler)
84
+ } else {
85
+ print("Invalid JSB!!!", message)
86
+ replyHandler?(nil, "Invalid JSB!!! \(message)")
87
+ }
88
+ }
89
+ } catch {}
90
+ }
91
+
92
+ private func handleAction(action: @escaping (@escaping ResolveHandler<Encodable>) -> Void,
93
+ replyHandler: ((Any?, String?) -> Void)?)
94
+ {
95
+ Task { @MainActor in
96
+ action { result in
97
+ switch result {
98
+ case let .success(data):
99
+ if data == nil {
100
+ replyHandler?("", nil)
101
+ } else {
102
+ replyHandler?(try? data?.toDictionary() ?? "", nil)
103
+ }
104
+
105
+ case let .failure(error):
106
+ let resultString = self.parseData(JsbErrorData(
107
+ code: error.code,
108
+ message: error.message
109
+ ))
110
+ replyHandler?(nil, resultString)
111
+ }
112
+ }
113
+ }
114
+ }
115
+
116
+ private func deserialize(cmdType: String, cmdContent: String?) throws -> CommandDataProtocol? {
117
+ let decoder = JSONDecoder()
118
+
119
+ guard let type = typeof(for: cmdType) else {
120
+ print("unknownType")
121
+ return nil
122
+ }
123
+ if cmdContent == nil {
124
+ return nil
125
+ }
126
+ let concreteData = try decoder.decode(type.self, from: cmdContent!.data(using: .utf8)!)
127
+ return concreteData
128
+ }
129
+
130
+ private func typeof(for key: String) -> CommandDataProtocol.Type? {
131
+ return typeMap[key]
132
+ }
133
+
134
+ private func parseData(_ data: Encodable) -> String? {
135
+ if let jsonData = try? encoder.encode(data) {
136
+ let jsonString = String(data: jsonData, encoding: .utf8)
137
+ return jsonString!
138
+ }
139
+ return nil
140
+ }
141
+ }
142
+
143
+ extension Encodable {
144
+ func toDictionary() throws -> [String: Any] {
145
+ let data = try JSONEncoder().encode(self)
146
+ return try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String: Any]
147
+ }
148
+ }
@@ -0,0 +1,39 @@
1
+ import SwiftUI
2
+ @preconcurrency import WebKit
3
+
4
+ class WKWebViewManager {
5
+ static let Instance = WKWebViewManager()
6
+
7
+ private init() {}
8
+
9
+ func create(controller: SpatialWebController, configuration: WKWebViewConfiguration? = nil, spatialId: String? = "") -> WKWebView {
10
+ let userContentController = WKUserContentController()
11
+ // TODO: get native api instead of PACKAGE_VERSION
12
+ let userScript = WKUserScript(source: "window.WebSpatailEnabled = true; window.WebSpatailNativeVersion = 'PACKAGE_VERSION';", injectionTime: .atDocumentStart, forMainFrameOnly: false)
13
+ userContentController.addUserScript(userScript)
14
+ // userContentController.add(controller, name: "bridge")
15
+ userContentController.addScriptMessageHandler(controller, contentWorld: .page, name: "bridge")
16
+ let myConfig = (configuration != nil) ? configuration! : WKWebViewConfiguration()
17
+ myConfig.userContentController = userContentController
18
+ myConfig.preferences.javaScriptCanOpenWindowsAutomatically = true
19
+ myConfig.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs")
20
+ if myConfig.urlSchemeHandler(forURLScheme: "file") == nil {
21
+ myConfig.setURLSchemeHandler(controller, forURLScheme: "file")
22
+ }
23
+ controller.webview = WKWebView(frame: .zero, configuration: myConfig)
24
+ let configUA = myConfig.applicationNameForUserAgent ?? ""
25
+ // change webview ua
26
+ let ua = controller.webview!.value(forKey: "userAgent") as? String ?? ""
27
+ let webviewVersion = ua.split(separator: configUA)[0].split(separator: "AppleWebKit")[1]
28
+ // TODO: get native api instead of PACKAGE_VERSION
29
+ controller.webview!.customUserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7; wv) AppleWebKit\(webviewVersion)WebSpatial/\("PACKAGE_VERSION") SpatialID/\(spatialId!)"
30
+ controller.webview!.uiDelegate = controller
31
+ controller.webview!.allowsBackForwardNavigationGestures = true
32
+ controller.webview!.isInspectable = true
33
+ controller.webview!.allowsLinkPreview = true
34
+ controller.webview!.navigationDelegate = controller
35
+ controller.webview!.scrollView.delegate = controller
36
+ controller.startObserving()
37
+ return controller.webview!
38
+ }
39
+ }
@@ -5,7 +5,8 @@ var pwaManager = PWAManager()
5
5
 
6
6
  struct PWAManager: Codable {
7
7
  var isLocal: Bool = false
8
- var start_url: String = "http://localhost:5173/"
8
+ var start_url: String = "http://localhost:5173/src/reality/gestures"
9
+
9
10
  var scope: String = ""
10
11
  var id: String = "com.webspatial.pico"
11
12
 
@@ -16,12 +17,22 @@ struct PWAManager: Codable {
16
17
  var display: PWADisplayMode = .minimal
17
18
  var display_override: [PWADisplayMode] = []
18
19
  var protocol_handlers: [PWAProtocol] = [PWAProtocol(protocolValue: "web+spatial", url: "./?cmd=%s")]
19
- var mainScene: WindowContainerOptions = .init(
20
+ var mainScene: XSceneOptionsJSB = .init(
20
21
  defaultSize: .init(
21
- width: 1280,
22
- height: 720
22
+ width: 1024,
23
+ height: 768,
24
+ depth: 0.1
25
+ ),
26
+ type: nil,
27
+ resizability: ResizeRange(
28
+ minWidth: 0.5 * 1360,
29
+ minHeight: 0.5 * 1360,
30
+ maxWidth: 2 * 1360,
31
+ maxHeight: 2 * 1360
23
32
  ),
24
- resizability: nil
33
+ worldScaling: nil,
34
+ worldAlignment: nil,
35
+ baseplateVisibility: nil
25
36
  )
26
37
  var useMainScene: Bool = true
27
38
  private var version: String = "PACKAGE_VERSION"
@@ -0,0 +1,190 @@
1
+ import Foundation
2
+ import SwiftUI
3
+
4
+ let logger = Logger()
5
+
6
+ // To load a local path, remove http:// eg. "static-web/"
7
+ let nativeAPIVersion = pwaManager.getVersion()
8
+
9
+ // start URL
10
+ let startURL = pwaManager.start_url
11
+
12
+ let DefaultPlainWindowContainerSize = CGSize(width: 1280, height: 720)
13
+
14
+ enum XLoadingMethod: String, Decodable, Encodable, Hashable {
15
+ case show
16
+ case hide
17
+ }
18
+
19
+ struct XLoadingViewData: Decodable, Hashable, Encodable {
20
+ let sceneID: String
21
+ let method: XLoadingMethod
22
+ let windowStyle: String?
23
+ }
24
+
25
+ struct SceneOptions {
26
+ var defaultSize: Size?
27
+ var windowResizability: WindowResizability?
28
+ var resizeRange: ResizeRange?
29
+ var worldScaling: WorldScalingBehavior
30
+ var worldAlignment: WorldAlignmentBehavior
31
+ var baseplateVisibility: Visibility
32
+ }
33
+
34
+ struct Size: Codable {
35
+ var width: Double
36
+ var height: Double
37
+ var depth: Double?
38
+ }
39
+
40
+
41
+ extension SceneOptions {
42
+ init(_ options: XSceneOptionsJSB) {
43
+ defaultSize = Size(
44
+ width: options.defaultSize?.width ?? DefaultPlainWindowContainerSize.width,
45
+ height: options.defaultSize?.height ?? DefaultPlainWindowContainerSize.height,
46
+ depth: options.defaultSize?.depth ?? 0
47
+ )
48
+ windowResizability = decodeWindowResizability(nil)
49
+ resizeRange = options.resizability
50
+ /// volume only
51
+ worldScaling = options.worldScaling?.toSDK ?? .automatic
52
+ worldAlignment = options.worldAlignment?.toSDK ?? .automatic
53
+ baseplateVisibility = options.baseplateVisibility?.toSDK ?? .automatic
54
+ }
55
+ }
56
+
57
+ func decodeWindowResizability(_ windowResizability: String?) -> WindowResizability {
58
+ switch windowResizability {
59
+ case "automatic":
60
+ return .automatic
61
+ case "contentSize":
62
+ return .contentSize
63
+ case "contentMinSize":
64
+ return .contentMinSize
65
+ default:
66
+ return .automatic
67
+ }
68
+ }
69
+
70
+ @Observable
71
+ class SpatialApp {
72
+ private var scenes = [String: SpatialScene]()
73
+
74
+ // delegate properties to pwaManager
75
+ var name: String { pwaManager.name }
76
+ var scope: String { pwaManager.scope }
77
+ var displayMode: PWADisplayMode { pwaManager.display }
78
+ var version: String { pwaManager.getVersion() }
79
+ var startURL: String { pwaManager.start_url }
80
+
81
+ // used to cache scene config
82
+ private var sceneOptions: SceneOptions
83
+
84
+
85
+ static let Instance: SpatialApp = .init()
86
+
87
+ init() {
88
+ // init pwa manager
89
+ pwaManager._init()
90
+
91
+ Logger.initLogger()
92
+
93
+ sceneOptions = SceneOptions(pwaManager.mainScene);
94
+
95
+ print("plainSceneOptions",sceneOptions)
96
+
97
+ logger.debug("WebSpatial App Started -------- rootURL: " + startURL)
98
+ }
99
+
100
+
101
+
102
+ func createScene(_ url: String, _ style: SpatialScene.WindowStyle, _ state: SpatialScene.SceneStateKind, _ sceneOptions: SceneOptions? = nil) -> SpatialScene {
103
+ var scene = SpatialScene(url, style, state, sceneOptions)
104
+ scenes[scene.id] = scene
105
+ scene
106
+ .on(event: SpatialObject.Events.Destroyed.rawValue, listener: onSceneDestroyed)
107
+
108
+
109
+ return scene
110
+ }
111
+
112
+ private func onSceneDestroyed(_ object: Any, _ data: Any) {
113
+ var spatialObject = object as! SpatialObject
114
+ spatialObject
115
+ .off(event: SpatialObject.Events.Destroyed.rawValue, listener: onSceneDestroyed)
116
+
117
+ scenes.removeValue(forKey: spatialObject.id)
118
+ }
119
+
120
+ func getScene(_ id: String) -> SpatialScene? {
121
+ return scenes[id]
122
+ }
123
+
124
+
125
+ func getSceneOptions() -> SceneOptions {
126
+ return sceneOptions
127
+ }
128
+
129
+ func getSceneOptions(_ sceneId:String) -> SceneOptions? {
130
+ let spatialScene = getScene(sceneId)
131
+ return spatialScene?.sceneConfig
132
+ }
133
+
134
+
135
+ // used form window.open logic
136
+ public func openWindowGroup(
137
+ _ targetSpatialScene: SpatialScene,
138
+ _ sceneData: SceneOptions
139
+ ) {
140
+ if let activeScene = firstActiveScene {
141
+ // cache scene config
142
+ sceneOptions = sceneData
143
+
144
+ DispatchQueue.main.async() {
145
+ activeScene.openWindowData.send(targetSpatialScene.id)
146
+ }
147
+
148
+ }
149
+ }
150
+
151
+ public func closeWindowGroup(_ targetSpatialScene: SpatialScene) {
152
+ if let activeScene = firstActiveScene {
153
+ activeScene.closeWindowData
154
+ .send(targetSpatialScene.id)
155
+ }
156
+ }
157
+
158
+ // used form window.open logic with loading ui
159
+ public func openLoadingUI(_ targetSpatialScene: SpatialScene,_ open: Bool) {
160
+ let lwgdata = XLoadingViewData(
161
+ sceneID: targetSpatialScene.id,
162
+ method: open ? .show : .hide,
163
+ windowStyle: nil
164
+ )
165
+
166
+ if let activeScene = firstActiveScene {
167
+ activeScene.setLoadingWindowData.send(lwgdata)
168
+ }
169
+ }
170
+
171
+ private var firstActiveScene: SpatialScene? {
172
+ get {
173
+ let activeKV = scenes.first() { kv in
174
+ kv.value.state == .visible
175
+ }
176
+ return (activeKV?.value)
177
+ }
178
+ }
179
+
180
+ public func focusScene(_ targetSpatialScene: SpatialScene) {
181
+ // only work when fully visible
182
+ if targetSpatialScene.state != .visible {
183
+ return
184
+ }
185
+
186
+ if let activeScene = firstActiveScene {
187
+ activeScene.openWindowData.send(targetSpatialScene.id)
188
+ }
189
+ }
190
+ }