Repository navigation
Expand file tree
/
Copy pathchanges.ts
More file actions
124 lines (119 loc) · 4.83 KB
/
Copy pathchanges.ts
File metadata and controls
124 lines (119 loc) · 4.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import { fromCollectionRef } from '../observable/fromRef';
import { Observable, SchedulerLike } from 'rxjs';
import { distinctUntilChanged, map, pairwise, scan, startWith } from 'rxjs/operators';
import { DocumentChange, DocumentChangeAction, DocumentChangeType, Query } from '../interfaces';
/**
* Return a stream of document changes on a query. These results are not in sort order but in
* order of occurence.
*/
export function docChanges<T>(query: Query, scheduler?: SchedulerLike): Observable<DocumentChangeAction<T>[]> {
return fromCollectionRef(query, scheduler)
.pipe(
startWith(undefined),
pairwise(),
map(([priorAction, action]) => {
const docChanges = action.payload.docChanges();
const actions = docChanges.map(change => ({ type: change.type, payload: change }));
// the metadata has changed from the prior emission
if (priorAction && JSON.stringify(priorAction.payload.metadata) !== JSON.stringify(action.payload.metadata)) {
// go through all the docs in payload and figure out which ones changed
action.payload.docs.forEach((currentDoc, currentIndex) => {
const docChange = docChanges.find(d => d.doc.ref.isEqual(currentDoc.ref));
const priorDoc = priorAction?.payload.docs.find(d => d.ref.isEqual(currentDoc.ref));
if (docChange && JSON.stringify(docChange.doc.metadata) === JSON.stringify(currentDoc.metadata) ||
!docChange && priorDoc && JSON.stringify(priorDoc.metadata) === JSON.stringify(currentDoc.metadata)) {
// document doesn't appear to have changed, don't log another action
} else {
// since the actions are processed in order just push onto the array
actions.push({
type: 'modified',
payload: {
oldIndex: currentIndex,
newIndex: currentIndex,
type: 'modified',
doc: currentDoc
}
});
}
});
}
return actions as DocumentChangeAction<T>[];
}),
);
}
/**
* Return a stream of document changes on a query. These results are in sort order.
*/
export function sortedChanges<T>(
query: Query,
events: DocumentChangeType[],
scheduler?: SchedulerLike): Observable<DocumentChangeAction<T>[]> {
return docChanges<T>(query, scheduler)
.pipe(
scan((current, changes) => combineChanges<T>(current, changes.map(it => it.payload), events), []),
distinctUntilChanged(), // cut down on unneed change cycles
map(changes => changes.map(c => ({ type: c.type, payload: c } as DocumentChangeAction<T>))));
}
/**
* Combines the total result set from the current set of changes from an incoming set
* of changes.
*/
export function combineChanges<T>(current: DocumentChange<T>[], changes: DocumentChange<T>[], events: DocumentChangeType[]) {
changes.forEach(change => {
// skip unwanted change types
if (events.indexOf(change.type) > -1) {
current = combineChange(current, change);
}
});
return current;
}
/**
* Splice arguments on top of a sliced array, to break top-level ===
* this is useful for change-detection
*/
function sliceAndSplice<T>(
original: T[],
start: number,
deleteCount: number,
...args: T[]
): T[] {
const returnArray = original.slice();
returnArray.splice(start, deleteCount, ...args);
return returnArray;
}
/**
* Creates a new sorted array from a new change.
* Build our own because we allow filtering of action types ('added', 'removed', 'modified') before scanning
* and so we have greater control over change detection (by breaking ===)
*/
export function combineChange<T>(combined: DocumentChange<T>[], change: DocumentChange<T>): DocumentChange<T>[] {
switch (change.type) {
case 'added':
if (combined[change.newIndex] && combined[change.newIndex].doc.ref.isEqual(change.doc.ref)) {
// Not sure why the duplicates are getting fired
} else {
return sliceAndSplice(combined, change.newIndex, 0, change);
}
break;
case 'modified':
if (combined[change.oldIndex] == null || combined[change.oldIndex].doc.ref.isEqual(change.doc.ref)) {
// When an item changes position we first remove it
// and then add it's new position
if (change.oldIndex !== change.newIndex) {
const copiedArray = combined.slice();
copiedArray.splice(change.oldIndex, 1);
copiedArray.splice(change.newIndex, 0, change);
return copiedArray;
} else {
return sliceAndSplice(combined, change.newIndex, 1, change);
}
}
break;
case 'removed':
if (combined[change.oldIndex] && combined[change.oldIndex].doc.ref.isEqual(change.doc.ref)) {
return sliceAndSplice(combined, change.oldIndex, 1);
}
break;
}
return combined;
}