@textbus/collaborate 2.2.0-alpha.0 → 2.3.0-alpha.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,732 +0,0 @@
1
- import { Inject, Injectable } from '@tanbo/di'
2
- import { delay, filter, map, Observable, Subject, Subscription } from '@tanbo/stream'
3
- import {
4
- ChangeOrigin,
5
- ComponentInstance,
6
- ContentType,
7
- Controller,
8
- Formats,
9
- History,
10
- HISTORY_STACK_SIZE,
11
- makeError,
12
- Registry,
13
- RootComponentRef,
14
- Scheduler,
15
- Selection,
16
- SelectionPaths,
17
- Slot,
18
- Starter,
19
- Translator
20
- } from '@textbus/core'
21
- import {
22
- Array as YArray,
23
- Doc as YDoc,
24
- Map as YMap,
25
- RelativePosition,
26
- Text as YText,
27
- Transaction,
28
- UndoManager,
29
- createAbsolutePositionFromRelativePosition,
30
- createRelativePositionFromTypeIndex
31
- } from 'yjs'
32
-
33
- import { CollaborateCursor, RemoteSelection } from './collaborate-cursor'
34
- import { createUnknownComponent } from './unknown.component'
35
-
36
- const collaborateErrorFn = makeError('Collaborate')
37
-
38
- interface CursorPosition {
39
- anchor: RelativePosition
40
- focus: RelativePosition
41
- }
42
-
43
- class ContentMap {
44
- private slotAndYTextMap = new WeakMap<Slot, YText>()
45
- private yTextAndSLotMap = new WeakMap<YText, Slot>()
46
-
47
- set(key: Slot, value: YText): void
48
- set(key: YText, value: Slot): void
49
- set(key: any, value: any) {
50
- if (key instanceof Slot) {
51
- this.slotAndYTextMap.set(key, value)
52
- this.yTextAndSLotMap.set(value, key)
53
- } else {
54
- this.slotAndYTextMap.set(value, key)
55
- this.yTextAndSLotMap.set(key, value)
56
- }
57
- }
58
-
59
- get(key: Slot): YText | null
60
- get(key: YText): Slot | null
61
- get(key: any) {
62
- if (key instanceof Slot) {
63
- return this.slotAndYTextMap.get(key) || null
64
- }
65
- return this.yTextAndSLotMap.get(key) || null
66
- }
67
-
68
- delete(key: Slot | YText) {
69
- if (key instanceof Slot) {
70
- const v = this.slotAndYTextMap.get(key)
71
- this.slotAndYTextMap.delete(key)
72
- if (v) {
73
- this.yTextAndSLotMap.delete(v)
74
- }
75
- } else {
76
- const v = this.yTextAndSLotMap.get(key)
77
- this.yTextAndSLotMap.delete(key)
78
- if (v) {
79
- this.slotAndYTextMap.delete(v)
80
- }
81
- }
82
- }
83
- }
84
-
85
- interface Update {
86
- record: boolean
87
- actions: Array<() => void>
88
- }
89
-
90
- interface UpdateItem {
91
- record: boolean
92
-
93
- action(): void
94
- }
95
-
96
- @Injectable()
97
- export class Collaborate implements History {
98
- onSelectionChange: Observable<SelectionPaths>
99
- yDoc = new YDoc()
100
- onBack: Observable<void>
101
- onForward: Observable<void>
102
- onChange: Observable<void>
103
- onPush: Observable<void>
104
-
105
- get canBack() {
106
- return this.manager?.canUndo() || false
107
- }
108
-
109
- get canForward() {
110
- return this.manager?.canRedo() || false
111
- }
112
-
113
- private backEvent = new Subject<void>()
114
- private forwardEvent = new Subject<void>()
115
- private changeEvent = new Subject<void>()
116
- private pushEvent = new Subject<void>()
117
-
118
- private manager: UndoManager | null = null
119
-
120
- private subscriptions: Subscription[] = []
121
- private updateFromRemote = false
122
-
123
- private contentSyncCaches = new WeakMap<Slot, () => void>()
124
- private slotStateSyncCaches = new WeakMap<Slot, () => void>()
125
- private slotsSyncCaches = new WeakMap<ComponentInstance, () => void>()
126
- private componentStateSyncCaches = new WeakMap<ComponentInstance, () => void>()
127
-
128
- private selectionChangeEvent = new Subject<SelectionPaths>()
129
- private contentMap = new ContentMap()
130
-
131
- private updateRemoteActions: Array<UpdateItem> = []
132
- private noRecord = {}
133
-
134
- constructor(@Inject(HISTORY_STACK_SIZE) private stackSize: number,
135
- private rootComponentRef: RootComponentRef,
136
- private collaborateCursor: CollaborateCursor,
137
- private controller: Controller,
138
- private scheduler: Scheduler,
139
- private translator: Translator,
140
- private registry: Registry,
141
- private selection: Selection,
142
- private starter: Starter) {
143
- this.onSelectionChange = this.selectionChangeEvent.asObservable().pipe(delay())
144
- this.onBack = this.backEvent.asObservable()
145
- this.onForward = this.forwardEvent.asObservable()
146
- this.onChange = this.changeEvent.asObservable()
147
- this.onPush = this.pushEvent.asObservable()
148
- }
149
-
150
- listen() {
151
- const root = this.yDoc.getMap('RootComponent')
152
- const rootComponent = this.rootComponentRef.component!
153
- this.manager = new UndoManager(root, {
154
- trackedOrigins: new Set<any>([this.yDoc])
155
- })
156
- const cursorKey = 'cursor-position'
157
- this.manager.on('stack-item-added', event => {
158
- event.stackItem.meta.set(cursorKey, this.getRelativeCursorLocation())
159
- if (this.manager!.undoStack.length > this.stackSize) {
160
- this.manager!.undoStack.shift()
161
- }
162
- if (event.origin === this.yDoc) {
163
- this.pushEvent.next()
164
- }
165
- this.changeEvent.next()
166
- })
167
- this.manager.on('stack-item-popped', event => {
168
- const position = event.stackItem.meta.get(cursorKey) as CursorPosition
169
- if (position) {
170
- this.restoreCursorLocation(position)
171
- }
172
- })
173
- this.subscriptions.push(
174
- this.selection.onChange.subscribe(() => {
175
- const paths = this.selection.getPaths()
176
- this.selectionChangeEvent.next(paths)
177
- }),
178
- this.scheduler.onDocChanged.pipe(
179
- map(item => {
180
- return item.filter(i => {
181
- return i.from !== ChangeOrigin.Remote
182
- })
183
- }),
184
- filter(item => {
185
- return item.length
186
- })
187
- ).subscribe(() => {
188
- const updates: Update[] = []
189
-
190
- let update: Update | null = null
191
-
192
- for (const item of this.updateRemoteActions) {
193
- if (!update) {
194
- update = {
195
- record: item.record,
196
- actions: []
197
- }
198
- updates.push(update)
199
- }
200
- if (update.record === item.record) {
201
- update.actions.push(item.action)
202
- } else {
203
- update = {
204
- record: item.record,
205
- actions: [item.action]
206
- }
207
- updates.push(update)
208
- }
209
- }
210
-
211
- this.updateRemoteActions = []
212
-
213
- for (const item of updates) {
214
- this.yDoc.transact(() => {
215
- item.actions.forEach(fn => {
216
- fn()
217
- })
218
- }, item.record ? this.yDoc : this.noRecord)
219
- }
220
- })
221
- )
222
- this.syncRootComponent(root, rootComponent)
223
- }
224
-
225
- updateRemoteSelection(paths: RemoteSelection[]) {
226
- this.collaborateCursor.draw(paths)
227
- }
228
-
229
- back() {
230
- if (this.canBack) {
231
- this.manager?.undo()
232
- this.backEvent.next()
233
- }
234
- }
235
-
236
- forward() {
237
- if (this.canForward) {
238
- this.manager?.redo()
239
- this.forwardEvent.next()
240
- }
241
- }
242
-
243
- clear() {
244
- this.manager?.clear()
245
- this.changeEvent.next()
246
- }
247
-
248
- destroy() {
249
- this.subscriptions.forEach(i => i.unsubscribe())
250
- this.collaborateCursor.destroy()
251
- this.manager?.destroy()
252
- }
253
-
254
- private syncRootComponent(root: YMap<any>, rootComponent: ComponentInstance) {
255
- let slots = root.get('slots') as YArray<YMap<any>>
256
- if (!slots) {
257
- slots = new YArray()
258
- rootComponent.slots.toArray().forEach(i => {
259
- const sharedSlot = this.createSharedSlotBySlot(i)
260
- slots.push([sharedSlot])
261
- })
262
- this.yDoc.transact(() => {
263
- root.set('state', rootComponent.state)
264
- root.set('slots', slots)
265
- })
266
- } else if (slots.length === 0) {
267
- rootComponent.updateState(() => {
268
- return root.get('state')
269
- })
270
- this.yDoc.transact(() => {
271
- rootComponent.slots.toArray().forEach(i => {
272
- const sharedSlot = this.createSharedSlotBySlot(i)
273
- slots.push([sharedSlot])
274
- })
275
- })
276
- } else {
277
- rootComponent.updateState(() => {
278
- return root.get('state')
279
- })
280
- rootComponent.slots.clean()
281
- slots.forEach(sharedSlot => {
282
- const slot = this.createSlotBySharedSlot(sharedSlot)
283
- this.syncContent(sharedSlot.get('content'), slot)
284
- this.syncSlot(sharedSlot, slot)
285
- rootComponent.slots.insert(slot)
286
- })
287
- }
288
- this.syncComponent(root, rootComponent)
289
- this.syncSlots(slots, rootComponent)
290
- }
291
-
292
- private restoreCursorLocation(position: CursorPosition) {
293
- const anchorPosition = createAbsolutePositionFromRelativePosition(position.anchor, this.yDoc)
294
- const focusPosition = createAbsolutePositionFromRelativePosition(position.focus, this.yDoc)
295
- if (anchorPosition && focusPosition) {
296
- const focusSlot = this.contentMap.get(focusPosition.type as YText)
297
- const anchorSlot = this.contentMap.get(anchorPosition.type as YText)
298
- if (focusSlot && anchorSlot) {
299
- this.selection.setBaseAndExtent(anchorSlot, anchorPosition.index, focusSlot, focusPosition.index)
300
- return
301
- }
302
- }
303
- this.selection.unSelect()
304
- }
305
-
306
- private getRelativeCursorLocation(): CursorPosition | null {
307
- const { anchorSlot, anchorOffset, focusSlot, focusOffset } = this.selection
308
- if (anchorSlot) {
309
- const anchorYText = this.contentMap.get(anchorSlot)
310
- if (anchorYText) {
311
- const anchorPosition = createRelativePositionFromTypeIndex(anchorYText, anchorOffset!)
312
- if (focusSlot) {
313
- const focusYText = this.contentMap.get(focusSlot)
314
- if (focusYText) {
315
- const focusPosition = createRelativePositionFromTypeIndex(focusYText, focusOffset!)
316
- return {
317
- focus: focusPosition,
318
- anchor: anchorPosition
319
- }
320
- }
321
- }
322
- }
323
- }
324
- return null
325
- }
326
-
327
- private syncContent(content: YText, slot: Slot) {
328
- this.contentMap.set(slot, content)
329
- const syncRemote = (ev, tr) => {
330
- this.runRemoteUpdate(tr, () => {
331
- slot.retain(0)
332
- ev.delta.forEach(action => {
333
- if (Reflect.has(action, 'retain')) {
334
- if (action.attributes) {
335
- const formats = remoteFormatsToLocal(this.registry, action.attributes)
336
- if (formats.length) {
337
- slot.retain(action.retain!, formats)
338
- }
339
- slot.retain(slot.index + action.retain)
340
- } else {
341
- slot.retain(action.retain)
342
- }
343
- } else if (action.insert) {
344
- const index = slot.index
345
- let length = 1
346
- if (typeof action.insert === 'string') {
347
- length = action.insert.length
348
- slot.insert(action.insert, remoteFormatsToLocal(this.registry, action.attributes))
349
- } else {
350
- const sharedComponent = action.insert as YMap<any>
351
- const canInsertInlineComponent = slot.schema.includes(ContentType.InlineComponent)
352
- const component = this.createComponentBySharedComponent(sharedComponent, canInsertInlineComponent)
353
- this.syncSlots(sharedComponent.get('slots'), component)
354
- this.syncComponent(sharedComponent, component)
355
- slot.insert(component)
356
- }
357
- if (this.selection.isSelected) {
358
- if (slot === this.selection.anchorSlot && this.selection.anchorOffset! > index) {
359
- this.selection.setAnchor(slot, this.selection.anchorOffset! + length)
360
- }
361
- if (slot === this.selection.focusSlot && this.selection.focusOffset! > index) {
362
- this.selection.setFocus(slot, this.selection.focusOffset! + length)
363
- }
364
- }
365
- } else if (action.delete) {
366
- const index = slot.index
367
- slot.retain(slot.index)
368
- slot.delete(action.delete)
369
- if (this.selection.isSelected) {
370
- if (slot === this.selection.anchorSlot && this.selection.anchorOffset! >= index) {
371
- this.selection.setAnchor(slot, this.selection.startOffset! - action.delete)
372
- }
373
- if (slot === this.selection.focusSlot && this.selection.focusOffset! >= index) {
374
- this.selection.setFocus(slot, this.selection.focusOffset! - action.delete)
375
- }
376
- }
377
- } else if (action.attributes) {
378
- slot.updateState(draft => {
379
- if (typeof draft === 'object' && draft !== null) {
380
- Object.assign(draft, action.attributes)
381
- } else {
382
- return action.attributes
383
- }
384
- })
385
- }
386
- })
387
- })
388
- }
389
- content.observe(syncRemote)
390
-
391
- const sub = slot.onContentChange.subscribe(actions => {
392
- this.runLocalUpdate(() => {
393
- let offset = 0
394
- let length = 0
395
- for (const action of actions) {
396
- if (action.type === 'retain') {
397
- const formats = action.formats
398
- if (formats) {
399
- const keys = Object.keys(formats)
400
- let length = keys.length
401
- keys.forEach(key => {
402
- const formatter = this.registry.getFormatter(key)
403
- if (!formatter) {
404
- length--
405
- Reflect.deleteProperty(formats, key)
406
- }
407
- })
408
- if (length) {
409
- content.format(offset, action.offset, formats)
410
- }
411
- } else {
412
- offset = action.offset
413
- }
414
- } else if (action.type === 'insert') {
415
- const delta = content.toDelta()
416
- const isEmpty = delta.length === 1 && delta[0].insert === Slot.emptyPlaceholder
417
- if (typeof action.content === 'string') {
418
- length = action.content.length
419
- content.insert(offset, action.content, action.formats || {})
420
- } else {
421
- length = 1
422
- const sharedComponent = this.createSharedComponentByComponent(action.ref as ComponentInstance)
423
- content.insertEmbed(offset, sharedComponent, action.formats || {})
424
- }
425
-
426
- if (isEmpty && offset === 0) {
427
- content.delete(content.length - 1, 1)
428
- }
429
- offset += length
430
- } else if (action.type === 'delete') {
431
- const delta = content.toDelta()
432
- if (content.length) {
433
- content.delete(offset, action.count)
434
- }
435
- if (content.length === 0) {
436
- content.insert(0, '\n', delta[0]?.attributes)
437
- }
438
- }
439
- }
440
- })
441
- })
442
-
443
- sub.add(slot.onChildComponentRemove.subscribe(components => {
444
- components.forEach(c => {
445
- this.cleanSubscriptionsByComponent(c)
446
- })
447
- }))
448
- this.contentSyncCaches.set(slot, () => {
449
- content.unobserve(syncRemote)
450
- sub.unsubscribe()
451
- })
452
- }
453
-
454
- private syncSlot(remoteSlot: YMap<any>, slot: Slot) {
455
- const syncRemote = (ev, tr) => {
456
- this.runRemoteUpdate(tr, () => {
457
- ev.keysChanged.forEach(key => {
458
- if (key === 'state') {
459
- const state = (ev.target as YMap<any>).get('state')
460
- slot.updateState(draft => {
461
- if (typeof draft === 'object' && draft !== null) {
462
- Object.assign(draft, state)
463
- } else {
464
- return state
465
- }
466
- })
467
- }
468
- })
469
- })
470
- }
471
- remoteSlot.observe(syncRemote)
472
-
473
- const sub = slot.onStateChange.subscribe(change => {
474
- this.runLocalUpdate(() => {
475
- remoteSlot.set('state', change.newState)
476
- }, change.record)
477
- })
478
- this.slotStateSyncCaches.set(slot, () => {
479
- remoteSlot.unobserve(syncRemote)
480
- sub.unsubscribe()
481
- })
482
- }
483
-
484
- private syncSlots(remoteSlots: YArray<any>, component: ComponentInstance) {
485
- const slots = component.slots
486
- const syncRemote = (ev, tr) => {
487
- this.runRemoteUpdate(tr, () => {
488
- let index = 0
489
- ev.delta.forEach(action => {
490
- if (Reflect.has(action, 'retain')) {
491
- index += action.retain
492
- slots.retain(index)
493
- } else if (action.insert) {
494
- (action.insert as Array<YMap<any>>).forEach(item => {
495
- const slot = this.createSlotBySharedSlot(item)
496
- slots.insert(slot)
497
- this.syncContent(item.get('content'), slot)
498
- this.syncSlot(item, slot)
499
- index++
500
- })
501
- } else if (action.delete) {
502
- slots.retain(index)
503
- slots.delete(action.delete)
504
- }
505
- })
506
- })
507
- }
508
- remoteSlots.observe(syncRemote)
509
-
510
- const sub = slots.onChange.subscribe(operations => {
511
- this.runLocalUpdate(() => {
512
- const applyActions = operations.apply
513
- let index: number
514
- applyActions.forEach(action => {
515
- if (action.type === 'retain') {
516
- index = action.offset
517
- } else if (action.type === 'insertSlot') {
518
- const sharedSlot = this.createSharedSlotBySlot(action.ref)
519
- remoteSlots.insert(index, [sharedSlot])
520
- index++
521
- } else if (action.type === 'delete') {
522
- remoteSlots.delete(index, action.count)
523
- }
524
- })
525
- })
526
- })
527
-
528
- sub.add(slots.onChildSlotRemove.subscribe(slots => {
529
- slots.forEach(slot => {
530
- this.cleanSubscriptionsBySlot(slot)
531
- })
532
- }))
533
-
534
- this.slotsSyncCaches.set(component, () => {
535
- remoteSlots.unobserve(syncRemote)
536
- sub.unsubscribe()
537
- })
538
- }
539
-
540
- private syncComponent(remoteComponent: YMap<any>, component: ComponentInstance) {
541
- const syncRemote = (ev, tr) => {
542
- this.runRemoteUpdate(tr, () => {
543
- ev.keysChanged.forEach(key => {
544
- if (key === 'state') {
545
- const state = (ev.target as YMap<any>).get('state')
546
- component.updateState(draft => {
547
- if (typeof draft === 'object' && draft !== null) {
548
- Object.assign(draft, state)
549
- } else {
550
- return state
551
- }
552
- })
553
- }
554
- })
555
- })
556
- }
557
- remoteComponent.observe(syncRemote)
558
-
559
- const sub = component.onStateChange.subscribe(change => {
560
- this.runLocalUpdate(() => {
561
- remoteComponent.set('state', change.newState)
562
- }, change.record)
563
- })
564
- this.componentStateSyncCaches.set(component, () => {
565
- remoteComponent.unobserve(syncRemote)
566
- sub.unsubscribe()
567
- })
568
- }
569
-
570
- private runLocalUpdate(fn: () => void, record = true) {
571
- if (this.updateFromRemote || this.controller.readonly) {
572
- return
573
- }
574
- this.updateRemoteActions.push({
575
- record,
576
- action: fn
577
- })
578
- }
579
-
580
- private runRemoteUpdate(tr: Transaction, fn: () => void) {
581
- if (tr.origin === this.yDoc) {
582
- return
583
- }
584
- this.updateFromRemote = true
585
- if (tr.origin === this.manager) {
586
- this.scheduler.historyApplyTransact(fn)
587
- } else {
588
- this.scheduler.remoteUpdateTransact(fn)
589
- }
590
- this.updateFromRemote = false
591
- }
592
-
593
- private createSharedComponentByComponent(component: ComponentInstance): YMap<any> {
594
- const sharedComponent = new YMap()
595
- sharedComponent.set('state', component.state)
596
- sharedComponent.set('name', component.name)
597
- const sharedSlots = new YArray()
598
- sharedComponent.set('slots', sharedSlots)
599
- component.slots.toArray().forEach(slot => {
600
- const sharedSlot = this.createSharedSlotBySlot(slot)
601
- sharedSlots.push([sharedSlot])
602
- })
603
- this.syncSlots(sharedSlots, component)
604
- this.syncComponent(sharedComponent, component)
605
- return sharedComponent
606
- }
607
-
608
- private createSharedSlotBySlot(slot: Slot): YMap<any> {
609
- const sharedSlot = new YMap()
610
- sharedSlot.set('schema', slot.schema)
611
- sharedSlot.set('state', slot.state)
612
- const sharedContent = new YText()
613
- sharedSlot.set('content', sharedContent)
614
- let offset = 0
615
- slot.toDelta().forEach(i => {
616
- let formats: any = {}
617
- if (i.formats) {
618
- i.formats.forEach(item => {
619
- formats[item[0].name] = item[1]
620
- })
621
- } else {
622
- formats = null
623
- }
624
- if (typeof i.insert === 'string') {
625
- sharedContent.insert(offset, i.insert, formats)
626
- } else {
627
- const sharedComponent = this.createSharedComponentByComponent(i.insert)
628
- sharedContent.insertEmbed(offset, sharedComponent, formats)
629
- }
630
- offset += i.insert.length
631
- })
632
- this.syncContent(sharedContent, slot)
633
- this.syncSlot(sharedSlot, slot)
634
- return sharedSlot
635
- }
636
-
637
- private createComponentBySharedComponent(yMap: YMap<any>, canInsertInlineComponent: boolean): ComponentInstance {
638
- const sharedSlots = yMap.get('slots') as YArray<YMap<any>>
639
- const slots: Slot[] = []
640
- sharedSlots.forEach(sharedSlot => {
641
- const slot = this.createSlotBySharedSlot(sharedSlot)
642
- slots.push(slot)
643
- })
644
- const name = yMap.get('name')
645
- const state = yMap.get('state')
646
- const instance = this.translator.createComponentByData(name, {
647
- state,
648
- slots
649
- })
650
- if (instance) {
651
- instance.slots.toArray().forEach((slot, index) => {
652
- let sharedSlot = sharedSlots.get(index)
653
- if (!sharedSlot) {
654
- sharedSlot = this.createSharedSlotBySlot(slot)
655
- sharedSlots.push([sharedSlot])
656
- }
657
- this.syncSlot(sharedSlot, slot)
658
- this.syncContent(sharedSlot.get('content'), slot)
659
- })
660
- return instance
661
- }
662
- return createUnknownComponent(name, canInsertInlineComponent).createInstance(this.starter)
663
- }
664
-
665
- private createSlotBySharedSlot(sharedSlot: YMap<any>): Slot {
666
- const content = sharedSlot.get('content') as YText
667
- const delta = content.toDelta()
668
-
669
- const slot = this.translator.createSlot({
670
- schema: sharedSlot.get('schema'),
671
- state: sharedSlot.get('state'),
672
- formats: {},
673
- content: []
674
- })
675
-
676
- for (const action of delta) {
677
- if (action.insert) {
678
- if (typeof action.insert === 'string') {
679
- slot.insert(action.insert, remoteFormatsToLocal(this.registry, action.attributes))
680
- } else {
681
- const sharedComponent = action.insert as YMap<any>
682
- const canInsertInlineComponent = slot.schema.includes(ContentType.InlineComponent)
683
- const component = this.createComponentBySharedComponent(sharedComponent, canInsertInlineComponent)
684
- slot.insert(component, remoteFormatsToLocal(this.registry, action.attributes))
685
- this.syncSlots(sharedComponent.get('slots'), component)
686
- this.syncComponent(sharedComponent, component)
687
- }
688
- } else {
689
- throw collaborateErrorFn('unexpected delta action.')
690
- }
691
- }
692
- return slot
693
- }
694
-
695
- private cleanSubscriptionsBySlot(slot: Slot) {
696
- this.contentMap.delete(slot);
697
- [this.contentSyncCaches.get(slot), this.slotStateSyncCaches.get(slot)].forEach(fn => {
698
- if (fn) {
699
- fn()
700
- }
701
- })
702
- slot.sliceContent().forEach(i => {
703
- if (typeof i !== 'string') {
704
- this.cleanSubscriptionsByComponent(i)
705
- }
706
- })
707
- }
708
-
709
- private cleanSubscriptionsByComponent(component: ComponentInstance) {
710
- [this.slotsSyncCaches.get(component), this.componentStateSyncCaches.get(component)].forEach(fn => {
711
- if (fn) {
712
- fn()
713
- }
714
- })
715
- component.slots.toArray().forEach(slot => {
716
- this.cleanSubscriptionsBySlot(slot)
717
- })
718
- }
719
- }
720
-
721
- function remoteFormatsToLocal(registry: Registry, attrs?: any,) {
722
- const formats: Formats = []
723
- if (attrs) {
724
- Object.keys(attrs).forEach(key => {
725
- const formatter = registry.getFormatter(key)
726
- if (formatter) {
727
- formats.push([formatter, attrs[key]])
728
- }
729
- })
730
- }
731
- return formats
732
- }