@webspatial/platform-visionos 1.2.0 → 1.3.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 (29) hide show
  1. package/package.json +1 -1
  2. package/web-spatial/EventEmitter.swift +11 -11
  3. package/web-spatial/JSBCommand.swift +15 -3
  4. package/web-spatial/WebMsgCommand.swift +7 -3
  5. package/web-spatial/WebSpatialApp.swift +10 -10
  6. package/web-spatial/Window.swift +2 -2
  7. package/web-spatial/manager/AttachmentManager.swift +81 -0
  8. package/web-spatial/manager/JSBManager.swift +1 -2
  9. package/web-spatial/manifest.swift +1 -1
  10. package/web-spatial/model/SpatialApp.swift +59 -55
  11. package/web-spatial/model/SpatialScene.swift +97 -14
  12. package/web-spatial/model/Spatialized2DElement.swift +4 -5
  13. package/web-spatial/model/SpatializedStatic3DElement.swift +1 -1
  14. package/web-spatial/model/dynamic3d/SpatialComponent.swift +27 -27
  15. package/web-spatial/model/dynamic3d/SpatialEntity.swift +2 -2
  16. package/web-spatial/model/dynamic3d/SpatialMaterial.swift +15 -15
  17. package/web-spatial/model/dynamic3d/SpatialModelEntity.swift +10 -10
  18. package/web-spatial/model/dynamic3d/SpatialModelResource.swift +1 -1
  19. package/web-spatial/model/dynamic3d/SpatialTextureResource.swift +8 -8
  20. package/web-spatial/view/SpatialNavView.swift +52 -47
  21. package/web-spatial/view/SpatializedDynamic3DView.swift +68 -4
  22. package/web-spatial/view/SpatializedElementView.swift +28 -13
  23. package/web-spatial/view/SpatializedStatic3DView.swift +4 -6
  24. package/web-spatial/view/view-modifier/HideViewModifier.swift +2 -2
  25. package/web-spatial/webview/SpatialWebController.swift +27 -24
  26. package/web-spatial/webview/SpatialWebView.swift +5 -1
  27. package/web-spatial/webview/SpatialWebViewModel.swift +13 -7
  28. package/web-spatial.xcodeproj/project.pbxproj +13 -0
  29. package/web-spatialTests/NavigationCleanupTests.swift +33 -0
@@ -38,7 +38,9 @@ let defaultSceneConfig = SceneOptions(
38
38
  class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSender {
39
39
  var parent: (any ScrollAbleSpatialElementContainer)?
40
40
 
41
- // Enum
41
+ var attachmentManager = AttachmentManager()
42
+
43
+ /// Enum
42
44
  enum WindowStyle: String, Codable, CaseIterable {
43
45
  case window
44
46
  case volume
@@ -66,15 +68,15 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
66
68
  }
67
69
 
68
70
  enum SceneStateKind: String {
69
- // default value
71
+ /// default value
70
72
  case idle
71
- // when SpatialScene is loading
73
+ /// when SpatialScene is loading
72
74
  case pending
73
- // when SpatialScen will visible after some time
75
+ /// when SpatialScen will visible after some time
74
76
  case willVisible
75
- // when SpatialScen load Succesfully
77
+ /// when SpatialScen load Succesfully
76
78
  case visible
77
- // when SpatialScen Failed to load
79
+ /// when SpatialScen Failed to load
78
80
  case fail
79
81
  }
80
82
 
@@ -101,7 +103,7 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
101
103
  moveToState(state, sceneOptions)
102
104
  }
103
105
 
104
- // used to send message to spatial root webview
106
+ /// used to send message to spatial root webview
105
107
  func sendWebMsg(_ id: String, _ msg: Encodable) {
106
108
  spatialWebViewModel.sendWebEvent(id, msg)
107
109
  }
@@ -299,6 +301,8 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
299
301
  spatialWebViewModel.addJSBListener(ConvertFromEntityToScene.self, onConvertFromEntityToScene)
300
302
  spatialWebViewModel.addJSBListener(ConvertFromSceneToEntity.self, onConvertFromSceneToEntity)
301
303
 
304
+ spatialWebViewModel.addJSBListener(UpdateAttachmentEntityCommand.self, onUpdateAttachmentEntity)
305
+
302
306
  spatialWebViewModel.addOpenWindowListener(protocal: "webspatial", onOpenWindowHandler)
303
307
 
304
308
  spatialWebViewModel
@@ -380,6 +384,8 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
380
384
  let host = url.host ?? ""
381
385
  if host == "createSpatialScene" {
382
386
  return handleWindowOpenCustom(url)
387
+ } else if host == "createAttachment" {
388
+ return handleCreateAttachment(url)
383
389
  } else {
384
390
  let spatialized2DElement: Spatialized2DElement = createSpatializedElement(
385
391
  .Spatialized2DElement
@@ -388,15 +394,66 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
388
394
  }
389
395
  }
390
396
 
397
+ private func handleCreateAttachment(_ url: URL) -> WebViewElementInfo? {
398
+ guard let components = URLComponents(string: url.absoluteString),
399
+ let queryItems = components.queryItems
400
+ else {
401
+ print("❌ fail to parse attachment URL")
402
+ return nil
403
+ }
404
+
405
+ guard let parentEntityId = queryItems.first(where: { $0.name == "parentEntityId" })?.value else {
406
+ print("❌ missing parentEntityId for attachment")
407
+ return nil
408
+ }
409
+
410
+ // Parse position (JSON array like [0,0.1,0])
411
+ var position = SIMD3<Float>(0, 0, 0)
412
+ if let positionStr = queryItems.first(where: { $0.name == "position" })?.value?.removingPercentEncoding,
413
+ let positionData = positionStr.data(using: .utf8),
414
+ let positionArray = try? JSONDecoder().decode([Float].self, from: positionData),
415
+ positionArray.count >= 3
416
+ {
417
+ position = SIMD3<Float>(positionArray[0], positionArray[1], positionArray[2])
418
+ }
419
+
420
+ // Parse size (JSON object like {"width":100,"height":100})
421
+ var size = CGSize(width: 100, height: 100)
422
+ if let sizeStr = queryItems.first(where: { $0.name == "size" })?.value?.removingPercentEncoding,
423
+ let sizeData = sizeStr.data(using: .utf8),
424
+ let sizeObj = try? JSONDecoder().decode(AttachmentSize.self, from: sizeData)
425
+ {
426
+ size = CGSize(width: sizeObj.width, height: sizeObj.height)
427
+ }
428
+
429
+ let info = attachmentManager.create(
430
+ id: UUID().uuidString,
431
+ parentEntityId: parentEntityId,
432
+ position: position,
433
+ size: size
434
+ )
435
+
436
+ return WebViewElementInfo(id: info.id, element: info.webViewModel)
437
+ }
438
+
391
439
  private func onPageStartLoad() {
392
440
  // destroy all SpatialObject asset
393
441
  let spatialObjectArray = spatialObjects.map { $0.value }
394
442
  for spatialObject in spatialObjectArray {
395
443
  spatialObject.destroy()
396
444
  }
445
+ // destroy all attachments
446
+ attachmentManager.destroyAll()
397
447
  backgroundMaterial = .None
398
448
  }
399
449
 
450
+ /// Some SPA navigations (history back/forward) do not trigger a full WKNavigation
451
+ /// lifecycle. SpatialNavView calls this before navigation actions to ensure
452
+ /// previously-created spatial objects are cleaned up.
453
+ func resetForNavigation() {
454
+ onPageStartLoad()
455
+ }
456
+
400
457
  private func onGetSpatialSceneState(
401
458
  command: GetSpatialSceneStateCommand,
402
459
  resolve: @escaping JSBManager.ResolveHandler<Encodable>
@@ -424,6 +481,12 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
424
481
  }
425
482
 
426
483
  private func onDestroySpatialObjectCommand(command: DestroyCommand, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
484
+ // Check if it's an attachment first
485
+ if attachmentManager.get(id: command.id) != nil {
486
+ attachmentManager.remove(id: command.id)
487
+ resolve(.success(nil))
488
+ return
489
+ }
427
490
  if let spatialObject: SpatialObject = findSpatialObject(command.id) {
428
491
  spatialObject.destroy()
429
492
  resolve(.success(nil))
@@ -698,21 +761,21 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
698
761
  * Begin Implement SpatializedElementContainer Protocol
699
762
  */
700
763
 
701
- // SpatialScene can hold a collection of SpatializedElement children
764
+ /// SpatialScene can hold a collection of SpatializedElement children
702
765
  private var children = [String: SpatializedElement]()
703
766
 
704
- // Called by SpatializedElement.setParent
767
+ /// Called by SpatializedElement.setParent
705
768
  func addChild(_ spatializedElement: SpatializedElement) {
706
769
  children[spatializedElement.id] = spatializedElement
707
770
  }
708
771
 
709
- // Called by SpatializedElement.setParent
772
+ /// Called by SpatializedElement.setParent
710
773
  func removeChild(_ spatializedElement: SpatializedElement) {
711
774
  children.removeValue(forKey: spatializedElement.id)
712
775
  }
713
776
 
714
777
  func getChildrenOfType(_ type: SpatializedElementType) -> [String: SpatializedElement] {
715
- let typedChildren = children.filter {
778
+ return children.filter {
716
779
  switch type {
717
780
  case .Spatialized2DElement:
718
781
  return $0.value is Spatialized2DElement
@@ -722,7 +785,6 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
722
785
  return $0.value is SpatializedDynamic3DElement
723
786
  }
724
787
  }
725
- return typedChildren
726
788
  }
727
789
 
728
790
  func getChildren() -> [String: SpatializedElement] {
@@ -733,7 +795,7 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
733
795
  * End Implement SpatializedElementContainer Protocol
734
796
  */
735
797
 
736
- /*
798
+ /**
737
799
  * Begin Implement SpatialScrollAble Protocol
738
800
  */
739
801
  let scrollPageEnabled: Bool = true
@@ -789,7 +851,7 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
789
851
  * Begin SpatialObjects management
790
852
  */
791
853
 
792
- // Resources that will be destroyed when this webpage is destoryed or if it is navigated away from
854
+ /// Resources that will be destroyed when this webpage is destoryed or if it is navigated away from
793
855
  private var spatialObjects = [String: any SpatialObjectProtocol]()
794
856
 
795
857
  func createSpatializedElement<T: SpatializedElement>(_ type: SpatializedElementType) -> T {
@@ -1006,6 +1068,26 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
1006
1068
  resolve(.success(ConvertReply(id: command.entityId, position: point)))
1007
1069
  }
1008
1070
 
1071
+ private func onUpdateAttachmentEntity(command: UpdateAttachmentEntityCommand, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
1072
+ guard attachmentManager.get(id: command.id) != nil else {
1073
+ resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "Attachment \(command.id) not found")))
1074
+ return
1075
+ }
1076
+
1077
+ var newPosition: SIMD3<Float>? = nil
1078
+ if let posArray = command.position, posArray.count >= 3 {
1079
+ newPosition = SIMD3<Float>(posArray[0], posArray[1], posArray[2])
1080
+ }
1081
+
1082
+ var newSize: CGSize? = nil
1083
+ if let sizeObj = command.size {
1084
+ newSize = CGSize(width: sizeObj.width, height: sizeObj.height)
1085
+ }
1086
+
1087
+ attachmentManager.update(id: command.id, position: newPosition, size: newSize)
1088
+ resolve(.success(baseReplyData))
1089
+ }
1090
+
1009
1091
  private func addSpatialObject(_ object: any SpatialObjectProtocol) {
1010
1092
  var spatialObject = object
1011
1093
  spatialObjects[spatialObject.spatialId] = spatialObject
@@ -1042,6 +1124,7 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
1042
1124
  for spatialObject in spatialObjectArray {
1043
1125
  spatialObject.destroy()
1044
1126
  }
1127
+ attachmentManager.destroyAll()
1045
1128
  spatialWebViewModel.destroy()
1046
1129
  }
1047
1130
 
@@ -57,15 +57,15 @@ class Spatialized2DElement: SpatializedElement, ScrollAbleSpatialElementContaine
57
57
  defaultAlignment = .center
58
58
  }
59
59
 
60
- // Spatialized2DElement can hold a collection of SpatializedElement children
60
+ /// Spatialized2DElement can hold a collection of SpatializedElement children
61
61
  private var children = [String: SpatializedElement]()
62
62
 
63
- // Called by SpatializedElement.setParent
63
+ /// Called by SpatializedElement.setParent
64
64
  func addChild(_ spatializedElement: SpatializedElement) {
65
65
  children[spatializedElement.id] = spatializedElement
66
66
  }
67
67
 
68
- // Called by SpatializedElement.setParent
68
+ /// Called by SpatializedElement.setParent
69
69
  func removeChild(_ spatializedElement: SpatializedElement) {
70
70
  children.removeValue(forKey: spatializedElement.id)
71
71
  }
@@ -75,7 +75,7 @@ class Spatialized2DElement: SpatializedElement, ScrollAbleSpatialElementContaine
75
75
  }
76
76
 
77
77
  func getChildrenOfType(_ type: SpatializedElementType) -> [String: SpatializedElement] {
78
- let typedChildren = children.filter {
78
+ return children.filter {
79
79
  switch type {
80
80
  case .Spatialized2DElement:
81
81
  return $0.value is Spatialized2DElement
@@ -85,7 +85,6 @@ class Spatialized2DElement: SpatializedElement, ScrollAbleSpatialElementContaine
85
85
  return $0.value is SpatializedDynamic3DElement
86
86
  }
87
87
  }
88
- return typedChildren
89
88
  }
90
89
 
91
90
  func loadHtml(_ html: String) {
@@ -4,7 +4,7 @@ import SwiftUI
4
4
  @Observable
5
5
  class SpatializedStatic3DElement: SpatializedElement {
6
6
  var modelURL: String = ""
7
- var modelTransform: AffineTransform3D = AffineTransform3D.identity
7
+ var modelTransform: AffineTransform3D = .identity
8
8
 
9
9
  enum CodingKeys: String, CodingKey {
10
10
  case modelURL, type
@@ -1,40 +1,40 @@
1
- import SwiftUI
2
1
  import RealityKit
2
+ import SwiftUI
3
3
 
4
4
  @Observable
5
5
  class SpatialComponent: SpatialObject {
6
6
  let type: SpatialComponentType
7
-
8
-
9
- internal var _resource:Component? = nil
10
- var resource:Component? {
7
+
8
+ var _resource: Component?
9
+ var resource: Component? {
11
10
  _resource
12
11
  }
13
-
14
- internal var _entity:SpatialEntity? = nil
15
- var entity:SpatialEntity? {
12
+
13
+ var _entity: SpatialEntity?
14
+ var entity: SpatialEntity? {
16
15
  _entity
17
16
  }
18
-
19
- init(_ _type:SpatialComponentType){
17
+
18
+ init(_ _type: SpatialComponentType) {
20
19
  type = _type
21
20
  super.init()
22
21
  }
23
-
24
- func addToEntity(entity:SpatialEntity){
22
+
23
+ func addToEntity(entity: SpatialEntity) {
25
24
  if _entity != nil {
26
25
  print("This component has already been added to another entity")
27
26
  return
28
27
  }
29
- if let component = resource{
28
+ if let component = resource {
30
29
  _entity = entity
31
30
  entity.components.set(component)
32
31
  }
33
32
  }
34
-
35
- func removeFromEntity(entity:SpatialEntity){
33
+
34
+ func removeFromEntity(entity: SpatialEntity) {
36
35
  if let component = resource,
37
- self.entity == entity{
36
+ self.entity == entity
37
+ {
38
38
  entity.components.remove(Swift.type(of: component))
39
39
  _entity = nil
40
40
  }
@@ -43,30 +43,30 @@ class SpatialComponent: SpatialObject {
43
43
 
44
44
  @Observable
45
45
  class SpatialModelComponent: SpatialComponent {
46
- init(mesh:Geometry, mats:[SpatialMaterial]){
46
+ init(mesh: Geometry, mats: [SpatialMaterial]) {
47
47
  super.init(.ModelComponent)
48
- var materials:[any RealityKit.Material] = []
49
- mats.forEach{ item in
48
+ var materials: [any RealityKit.Material] = []
49
+ for item in mats {
50
50
  materials.append(item.resource!)
51
51
  }
52
52
  _resource = ModelComponent(mesh: mesh.resource!, materials: materials)
53
53
  }
54
-
55
- override func addToEntity(entity:SpatialEntity){
54
+
55
+ override func addToEntity(entity: SpatialEntity) {
56
56
  super.addToEntity(entity: entity)
57
57
  entity.generateCollisionShapes(recursive: true)
58
58
  }
59
-
60
- override func removeFromEntity(entity:SpatialEntity){
59
+
60
+ override func removeFromEntity(entity: SpatialEntity) {
61
61
  super.removeFromEntity(entity: entity)
62
62
  entity.generateCollisionShapes(recursive: true)
63
63
  }
64
-
65
- override internal func onDestroy() {
64
+
65
+ override func onDestroy() {
66
66
  _resource = nil
67
67
  }
68
68
  }
69
69
 
70
- enum SpatialComponentType:String {
71
- case ModelComponent = "ModelComponent"
70
+ enum SpatialComponentType: String {
71
+ case ModelComponent
72
72
  }
@@ -159,7 +159,7 @@ class SpatialEntity: Entity, SpatialObjectProtocol {
159
159
  transform.rotation = simd_quatf(ix: Float(rotation.imag.x), iy: Float(rotation.imag.y), iz: Float(rotation.imag.z), r: Float(rotation.real))
160
160
  }
161
161
 
162
- // Encodable
162
+ /// Encodable
163
163
  enum CodingKeys: String, CodingKey {
164
164
  case id, name, isDestroyed, children, components
165
165
  }
@@ -173,7 +173,7 @@ class SpatialEntity: Entity, SpatialObjectProtocol {
173
173
  try container.encode(spatialComponents, forKey: .components)
174
174
  }
175
175
 
176
- // Equatable
176
+ /// Equatable
177
177
  static func == (lhs: SpatialEntity, rhs: SpatialEntity) -> Bool {
178
178
  return lhs.spatialId == rhs.spatialId
179
179
  }
@@ -1,39 +1,39 @@
1
- import SwiftUI
2
1
  import RealityKit
2
+ import SwiftUI
3
3
 
4
4
  @Observable
5
5
  class SpatialMaterial: SpatialObject {
6
6
  let type: SpatialMaterialType
7
-
8
- internal var _resource:RealityKit.Material? = nil
9
- var resource:RealityKit.Material? {
7
+
8
+ var _resource: RealityKit.Material?
9
+ var resource: RealityKit.Material? {
10
10
  _resource
11
11
  }
12
-
13
- init(_ _type:SpatialMaterialType){
12
+
13
+ init(_ _type: SpatialMaterialType) {
14
14
  type = _type
15
15
  super.init()
16
16
  }
17
-
17
+
18
18
  override func onDestroy() {
19
19
  _resource = nil
20
20
  }
21
21
  }
22
22
 
23
23
  @Observable
24
- class SpatialUnlitMaterial: SpatialMaterial{
25
- let color:UIColor
26
-
27
- init(_ color:String, _ texture:TextureResource? = nil, _ transparent:Bool = true, _ opacity:Float = 1){
28
- self.color = UIColor.init(Color(hex: color))
24
+ class SpatialUnlitMaterial: SpatialMaterial {
25
+ let color: UIColor
26
+
27
+ init(_ color: String, _ texture: TextureResource? = nil, _ transparent: Bool = true, _ opacity: Float = 1) {
28
+ self.color = UIColor(Color(hex: color))
29
29
  super.init(.UnlitMaterial)
30
30
  var mat = UnlitMaterial()
31
- mat.color = .init(tint:UIColor(Color.init(hex: color)), texture: texture != nil ? .init(texture!) : nil)
31
+ mat.color = .init(tint: UIColor(Color(hex: color)), texture: texture != nil ? .init(texture!) : nil)
32
32
  mat.blending = transparent ? .transparent(opacity: .init(scale: opacity)) : .opaque
33
33
  _resource = mat
34
34
  }
35
35
  }
36
36
 
37
- enum SpatialMaterialType: String{
38
- case UnlitMaterial = "UnlitMaterial"
37
+ enum SpatialMaterialType: String {
38
+ case UnlitMaterial
39
39
  }
@@ -1,32 +1,32 @@
1
- import SwiftUI
2
1
  import RealityKit
2
+ import SwiftUI
3
3
 
4
4
  @Observable
5
- class SpatialModelEntity: SpatialEntity{
6
- private var modelEntity:Entity? = nil
7
- required init(_ modelResource:SpatialModelResource, _ _name:String = ""){
5
+ class SpatialModelEntity: SpatialEntity {
6
+ private var modelEntity: Entity?
7
+ required init(_ modelResource: SpatialModelResource, _ _name: String = "") {
8
8
  super.init(_name)
9
9
  modelEntity = modelResource.resource
10
10
  addChild(modelEntity!)
11
11
  generateCollisionShapes(recursive: true)
12
12
  }
13
-
13
+
14
14
  required init() {
15
15
  super.init()
16
16
  }
17
-
18
- override internal func onDestroy(){
17
+
18
+ override func onDestroy() {
19
19
  super.onDestroy()
20
- if let modelEntity = self.modelEntity{
20
+ if let modelEntity = modelEntity {
21
21
  removeChild(modelEntity)
22
22
  }
23
23
  modelEntity = nil
24
24
  }
25
-
25
+
26
26
  enum CodingKeys: String, CodingKey {
27
27
  case id, name, isDestroyed, children, components, model
28
28
  }
29
-
29
+
30
30
  override func encode(to encoder: any Encoder) throws {
31
31
  var container = encoder.container(keyedBy: CodingKeys.self)
32
32
  try container.encode(spatialId, forKey: .id)
@@ -3,7 +3,7 @@ import SwiftUI
3
3
 
4
4
  @Observable
5
5
  class SpatialModelResource: SpatialObject {
6
- var _resource: Entity? = nil
6
+ var _resource: Entity?
7
7
  var resource: Entity? {
8
8
  _resource
9
9
  }
@@ -1,18 +1,18 @@
1
- import SwiftUI
2
1
  import RealityKit
2
+ import SwiftUI
3
3
 
4
4
  @Observable
5
- class SpatialTextureResource:SpatialObject {
6
- internal var _resource:TextureResource? = nil
7
- var resource:TextureResource? {
5
+ class SpatialTextureResource: SpatialObject {
6
+ var _resource: TextureResource?
7
+ var resource: TextureResource? {
8
8
  _resource
9
9
  }
10
-
11
- override init(_ url:String){
10
+
11
+ override init(_ url: String) {
12
12
  super.init()
13
13
  }
14
-
15
- override internal func onDestroy() {
14
+
15
+ override func onDestroy() {
16
16
  _resource = nil
17
17
  }
18
18
  }