@textbus/collaborate 2.2.0-alpha.0 → 2.3.0-alpha.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.
@@ -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
- }