1
1
import type { IConceptJS , INodeJS } from "@modelix/ts-model-api" ;
2
- import { customRef , markRaw } from "vue" ;
2
+ import type { Ref } from "vue" ;
3
+ import { markRaw , shallowRef } from "vue" ;
3
4
import type { Cache } from "./Cache" ;
5
+ import type { Nullable } from "@modelix/model-client" ;
4
6
5
7
export function toReactiveINodeJS (
6
8
node : INodeJS ,
@@ -9,12 +11,12 @@ export function toReactiveINodeJS(
9
11
return cache . memoize ( node , ( ) => new ReactiveINodeJS ( node , cache ) ) ;
10
12
}
11
13
12
- // This declaration specifies the types of the implemenation in `unwrapReactiveINodeJS` further.
14
+ // This declaration specifies the types of the implementation in `unwrapReactiveINodeJS` further.
13
15
// It declares, that when
14
16
// * an `INodeJS` is given an `INodeJS` is returned
15
17
// * an `undefined` is given an `undefined` is returned
16
18
// * an `null` is given an `null` is returned
17
- // This so called conditional types help avoid unneed assertsion about types on the usage site.
19
+ // This so called conditional types help avoid unneeded assertions about types on the usage site.
18
20
// See. https://www.typescriptlang.org/docs/handbook/2/conditional-types.html
19
21
function unwrapReactiveINodeJS < T extends INodeJS | null | undefined > (
20
22
maybeReactive : T ,
@@ -32,20 +34,24 @@ function unwrapReactiveINodeJS(
32
34
}
33
35
}
34
36
35
- // `any` is currectly provided as the type for references from `@modelix/ts-model-api`,
37
+ // `any` is correctly provided as the type for references from `@modelix/ts-model-api`,
36
38
// so we have to work with it for now.
37
39
// eslint-disable-next-line @typescript-eslint/no-explicit-any
38
40
type INodeReferenceJS = any ;
39
41
40
- interface TrackAndTrigger {
41
- track : ( ) => void ;
42
- trigger : ( ) => void ;
43
- }
44
-
45
42
export class ReactiveINodeJS implements INodeJS {
46
- private byRoleTrackAndTrigger : Map < string | undefined , TrackAndTrigger > =
43
+ private byRoleRefToProperty : Map < string , Ref < string | undefined > > = new Map ( ) ;
44
+ private byRoleRefToReferenceTargetNode : Map <
45
+ string ,
46
+ Ref < INodeJS | undefined >
47
+ > = new Map ( ) ;
48
+ private byRoleRefToReferenceTargetRef : Map <
49
+ string ,
50
+ Ref < INodeReferenceJS | undefined >
51
+ > = new Map ( ) ;
52
+ private byRoleRefToChildren : Map < string | undefined , Ref < INodeJS [ ] > > =
47
53
new Map ( ) ;
48
- private trackAndTriggerForAllChildren : TrackAndTrigger | undefined ;
54
+ private refForAllChildren : Ref < INodeJS [ ] > | undefined = undefined ;
49
55
50
56
constructor (
51
57
public readonly unreactiveNode : INodeJS ,
@@ -56,6 +62,30 @@ export class ReactiveINodeJS implements INodeJS {
56
62
markRaw ( this ) ;
57
63
}
58
64
65
+ private propertyGetter = ( role : string ) =>
66
+ this . unreactiveNode . getPropertyValue ( role ) ;
67
+
68
+ private referenceTargetRefGetter = ( role : string ) =>
69
+ this . unreactiveNode . getReferenceTargetRef ( role ) ;
70
+
71
+ private referenceTargetNodeGetter = ( role : string ) => {
72
+ const unreactiveTargetNode =
73
+ this . unreactiveNode . getReferenceTargetNode ( role ) ;
74
+ return unreactiveTargetNode
75
+ ? toReactiveINodeJS ( unreactiveTargetNode , this . cache )
76
+ : unreactiveTargetNode ;
77
+ } ;
78
+
79
+ private childrenGetter = ( role : string | undefined ) =>
80
+ this . unreactiveNode
81
+ . getChildren ( role )
82
+ . map ( ( node ) => toReactiveINodeJS ( node , this . cache ) ) ;
83
+
84
+ private allChildrenGetter = ( ) =>
85
+ this . unreactiveNode
86
+ . getAllChildren ( )
87
+ . map ( ( node ) => toReactiveINodeJS ( node , this . cache ) ) ;
88
+
59
89
getConcept ( ) : IConceptJS | undefined {
60
90
return this . unreactiveNode . getConcept ( ) ;
61
91
}
@@ -78,30 +108,30 @@ export class ReactiveINodeJS implements INodeJS {
78
108
79
109
getParent ( ) : INodeJS | undefined {
80
110
// This does not need to be made reacitve for the same reason as getRoleInParent
81
- const unreacitveNode = this . unreactiveNode . getParent ( ) ;
82
- return unreacitveNode
83
- ? toReactiveINodeJS ( unreacitveNode , this . cache )
84
- : unreacitveNode ;
111
+ const unreactiveNode = this . unreactiveNode . getParent ( ) ;
112
+ return unreactiveNode
113
+ ? toReactiveINodeJS ( unreactiveNode , this . cache )
114
+ : unreactiveNode ;
85
115
}
86
116
87
117
remove ( ) : void {
88
118
this . unreactiveNode . remove ( ) ;
89
119
}
90
120
91
121
getChildren ( role : string | undefined ) : INodeJS [ ] {
92
- const { track } = this . getOrCreateTrackAndTriggerForRole ( role ) ;
93
- track ( ) ;
94
- return this . unreactiveNode
95
- . getChildren ( role )
96
- . map ( ( node ) => toReactiveINodeJS ( node , this . cache ) ) ;
122
+ const ref = this . getOrCreateRefForRole (
123
+ this . byRoleRefToChildren ,
124
+ role ,
125
+ this . childrenGetter ,
126
+ ) ;
127
+ return ref . value ;
97
128
}
98
129
99
130
getAllChildren ( ) : INodeJS [ ] {
100
- const { track } = this . getOrCreateTrackAndTriggerForAllChildren ( ) ;
101
- track ( ) ;
102
- return this . unreactiveNode
103
- . getAllChildren ( )
104
- . map ( ( node ) => toReactiveINodeJS ( node , this . cache ) ) ;
131
+ if ( this . refForAllChildren == undefined ) {
132
+ this . refForAllChildren = shallowRef ( this . allChildrenGetter ( ) ) ;
133
+ }
134
+ return this . refForAllChildren ! . value ;
105
135
}
106
136
107
137
moveChild ( role : string | undefined , index : number , child : INodeJS ) : void {
@@ -113,14 +143,14 @@ export class ReactiveINodeJS implements INodeJS {
113
143
index : number ,
114
144
concept : IConceptJS | undefined ,
115
145
) : INodeJS {
116
- const unreacitveNode = this . unreactiveNode . addNewChild (
146
+ // The related Vue-`Ref` does not need to be triggered.
147
+ // It will be updated through the changed listener of the branch.
148
+ const unreactiveNode = this . unreactiveNode . addNewChild (
117
149
role ,
118
150
index ,
119
151
concept ,
120
152
) ;
121
- return unreacitveNode
122
- ? toReactiveINodeJS ( unreacitveNode , this . cache )
123
- : unreacitveNode ;
153
+ return toReactiveINodeJS ( unreactiveNode , this . cache ) ;
124
154
}
125
155
126
156
removeChild ( child : INodeJS ) : void {
@@ -134,25 +164,26 @@ export class ReactiveINodeJS implements INodeJS {
134
164
}
135
165
136
166
getReferenceTargetNode ( role : string ) : INodeJS | undefined {
137
- const { track } = this . getOrCreateTrackAndTriggerForRole ( role ) ;
138
- track ( ) ;
139
- const unreacitveNode = this . unreactiveNode . getReferenceTargetNode ( role ) ;
140
- return unreacitveNode
141
- ? toReactiveINodeJS ( unreacitveNode , this . cache )
142
- : unreacitveNode ;
167
+ const ref = this . getOrCreateRefForRole (
168
+ this . byRoleRefToReferenceTargetNode ,
169
+ role ,
170
+ this . referenceTargetNodeGetter ,
171
+ ) ;
172
+ return ref . value ;
143
173
}
144
174
145
175
getReferenceTargetRef ( role : string ) : INodeReferenceJS | undefined {
146
- const { track } = this . getOrCreateTrackAndTriggerForRole ( role ) ;
147
- track ( ) ;
148
- return this . unreactiveNode . getReferenceTargetRef ( role ) ;
176
+ const ref = this . getOrCreateRefForRole (
177
+ this . byRoleRefToReferenceTargetRef ,
178
+ role ,
179
+ this . referenceTargetRefGetter ,
180
+ ) ;
181
+ return ref . value ;
149
182
}
150
183
151
184
setReferenceTargetNode ( role : string , target : INodeJS | undefined ) : void {
152
- // Do not call `this.unreactiveNode.setReferenceTargetNode` directly,
153
- // because the target is declared as `INodeJS`, but actuall an `NodeAdapterJS` is expected.
154
- // Just using the reference is cleaner then unwrapping
155
- // then checking for ReactiveINodeJS and eventuall unwrapping it
185
+ // The related Vue-`Ref` does not need to be triggered.
186
+ // It will be updated through the changed listener of the branch.
156
187
return this . unreactiveNode . setReferenceTargetNode (
157
188
role ,
158
189
unwrapReactiveINodeJS ( target ) ,
@@ -163,6 +194,8 @@ export class ReactiveINodeJS implements INodeJS {
163
194
role : string ,
164
195
target : INodeReferenceJS | undefined ,
165
196
) : void {
197
+ // The related Vue-`Ref` does not need to be triggered.
198
+ // It will be updated through the changed listener of the branch.
166
199
this . unreactiveNode . setReferenceTargetRef ( role , target ) ;
167
200
}
168
201
@@ -173,69 +206,63 @@ export class ReactiveINodeJS implements INodeJS {
173
206
}
174
207
175
208
getPropertyValue ( role : string ) : string | undefined {
176
- const { track } = this . getOrCreateTrackAndTriggerForRole ( role ) ;
177
- track ( ) ;
178
- return this . unreactiveNode . getPropertyValue ( role ) ;
209
+ const ref = this . getOrCreateRefForRole (
210
+ this . byRoleRefToProperty ,
211
+ role ,
212
+ this . propertyGetter ,
213
+ ) ;
214
+ return ref . value ;
179
215
}
180
216
181
217
setPropertyValue ( role : string , value : string | undefined ) : void {
218
+ // The related Vue-`Ref` does not need to be triggered.
219
+ // It will be updated through the changed listener of the branch.
182
220
this . unreactiveNode . setPropertyValue ( role , value ) ;
183
221
}
184
222
185
- private getOrCreateTrackAndTriggerForAllChildren ( ) : TrackAndTrigger {
186
- if ( this . trackAndTriggerForAllChildren === undefined ) {
187
- customRef ( ( track , trigger ) => {
188
- this . trackAndTriggerForAllChildren = {
189
- track ,
190
- trigger ,
191
- } ;
192
- return {
193
- // Getter and setter ar empty for the same reason as in `getOrCreateTrackAndTriggerForRole`
194
- get ( ) { } ,
195
- set ( ) { } ,
196
- } ;
197
- } ) ;
223
+ getOrCreateRefForRole < RoleT , ValueT > (
224
+ byRoleRefs : Map < RoleT , Ref < ValueT > > ,
225
+ role : RoleT ,
226
+ getValue : ( role : RoleT ) => ValueT ,
227
+ ) : Ref < ValueT > {
228
+ const maybeCreatedShallowRef = byRoleRefs . get ( role ) ;
229
+
230
+ if ( maybeCreatedShallowRef != undefined ) {
231
+ return maybeCreatedShallowRef ;
232
+ } else {
233
+ const newRef = shallowRef ( getValue ( role ) ) ;
234
+ byRoleRefs . set ( role , newRef ) ;
235
+ return newRef ;
198
236
}
199
- // `this.trackAndTriggerForAllChildren` will always be set, because the `factory`
200
- // argument of `customRef` will be evaluated immedialty.
201
- return this . trackAndTriggerForAllChildren ! ;
202
237
}
203
238
204
- private getOrCreateTrackAndTriggerForRole (
205
- role : string | undefined ,
206
- ) : TrackAndTrigger {
207
- const existing = this . byRoleTrackAndTrigger . get ( role ) ;
208
- if ( existing !== undefined ) {
209
- return existing ;
239
+ triggerChangeInChild ( role : Nullable < string > ) {
240
+ if ( this . refForAllChildren ) {
241
+ this . refForAllChildren . value = this . allChildrenGetter ( ) ;
242
+ }
243
+
244
+ const normalizedRole = role ?? undefined ;
245
+ const maybeRef = this . byRoleRefToChildren . get ( normalizedRole ) ;
246
+ if ( maybeRef ) {
247
+ maybeRef . value = this . childrenGetter ( normalizedRole ) ;
248
+ }
249
+ }
250
+
251
+ triggerChangeInReference ( role : string ) {
252
+ const maybeTargetNodeRef = this . byRoleRefToReferenceTargetNode . get ( role ) ;
253
+ if ( maybeTargetNodeRef ) {
254
+ maybeTargetNodeRef . value = this . referenceTargetNodeGetter ( role ) ;
255
+ }
256
+ const maybeTargetRefRef = this . byRoleRefToReferenceTargetRef . get ( role ) ;
257
+ if ( maybeTargetRefRef ) {
258
+ maybeTargetRefRef . value = this . referenceTargetRefGetter ( role ) ;
259
+ }
260
+ }
261
+
262
+ triggerChangeInProperty ( role : string ) {
263
+ const maybeRef = this . byRoleRefToProperty . get ( role ) ;
264
+ if ( maybeRef ) {
265
+ maybeRef . value = this . propertyGetter ( role ) ;
210
266
}
211
- let created ;
212
- customRef ( ( track , trigger ) => {
213
- created = {
214
- track,
215
- trigger,
216
- } ;
217
- this . byRoleTrackAndTrigger . set ( role , created ) ;
218
- return {
219
- // The getters and setters will never be called directly
220
- // and therefore the are empty.
221
- // We use `customRef` to get access to a pair of `trigger` and `track`
222
- // to call them directly from outside.
223
- get ( ) { } ,
224
- set ( ) { } ,
225
- } ;
226
- } ) ;
227
- // `created` will always be set, because the `factory`
228
- // argument of `customRef` will be evaluated immedialty.
229
- return created ! ;
230
- }
231
-
232
- triggerChangeInRole ( role : string | undefined | null ) {
233
- const normalizedRole =
234
- role !== undefined && role !== null ? role : undefined ;
235
- this . byRoleTrackAndTrigger . get ( normalizedRole ) ?. trigger ( ) ;
236
- }
237
-
238
- triggerChangeInAllChildren ( ) {
239
- this . trackAndTriggerForAllChildren ?. trigger ( ) ;
240
267
}
241
268
}
0 commit comments