@textbus/collaborate 3.0.0-alpha.1 → 3.0.0-alpha.11

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