UNPKG

voluptasmollitia

Version:
150 lines (140 loc) 4.7 kB
/** * @license * Copyright 2018 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import firebase from 'firebase'; import { QueryChange, ListenEvent } from '../interfaces'; import { Observable, of, merge, from } from 'rxjs'; import { validateEventsArray } from '../utils'; import { fromRef } from '../fromRef'; import { switchMap, scan, distinctUntilChanged, map } from 'rxjs/operators'; import { changeToData } from '../object'; type Query = firebase.database.Query; export function stateChanges( query: Query, events?: ListenEvent[] ): Observable<QueryChange> { events = validateEventsArray(events); const childEvent$ = events.map(event => fromRef(query, event)); return merge(...childEvent$); } function fromOnce(query: Query): Observable<QueryChange> { return from(query.once(ListenEvent.value)).pipe( map(snapshot => { const event = ListenEvent.value; return { snapshot, prevKey: null, event }; }) ); } export function list( query: Query, events?: ListenEvent[] ): Observable<QueryChange[]> { const eventsList = validateEventsArray(events); return fromOnce(query).pipe( switchMap(change => { const childEvent$ = [of(change)]; for (const event of eventsList) { childEvent$.push(fromRef(query, event)); } return merge(...childEvent$).pipe(scan(buildView, [])); }), distinctUntilChanged() ); } /** * Get an object mapped to its value, and optionally its key * @param query object ref or query * @param keyField map the object key to a specific field */ export function listVal<T>(query: Query, keyField?: string): Observable<T[]> { return list(query).pipe( map(arr => arr.map(change => changeToData(change, keyField) as T)) ); } function positionFor(changes: QueryChange[], key: string | null): number { const len = changes.length; for (let i = 0; i < len; i++) { if (changes[i].snapshot.key === key) { return i; } } return -1; } function positionAfter(changes: QueryChange[], prevKey?: string): number { if (prevKey == null) { return 0; } else { const i = positionFor(changes, prevKey); if (i === -1) { return changes.length; } else { return i + 1; } } } function buildView(current: QueryChange[], change: QueryChange): QueryChange[] { const { snapshot, prevKey, event } = change; const { key } = snapshot; const currentKeyPosition = positionFor(current, key); const afterPreviousKeyPosition = positionAfter(current, prevKey || undefined); switch (event) { case ListenEvent.value: if (change.snapshot && change.snapshot.exists()) { let prevKey: string | null = null; change.snapshot.forEach(snapshot => { const action: QueryChange = { snapshot, event: ListenEvent.value, prevKey }; prevKey = snapshot.key; current = [...current, action]; return false; }); } return current; case ListenEvent.added: if (currentKeyPosition > -1) { // check that the previouskey is what we expect, else reorder const previous = current[currentKeyPosition - 1]; if (((previous && previous.snapshot.key) || null) !== prevKey) { current = current.filter(x => x.snapshot.key !== snapshot.key); current.splice(afterPreviousKeyPosition, 0, change); } } else if (prevKey == null) { return [change, ...current]; } else { current = current.slice(); current.splice(afterPreviousKeyPosition, 0, change); } return current; case ListenEvent.removed: return current.filter(x => x.snapshot.key !== snapshot.key); case ListenEvent.changed: return current.map(x => (x.snapshot.key === key ? change : x)); case ListenEvent.moved: if (currentKeyPosition > -1) { const data = current.splice(currentKeyPosition, 1)[0]; current = current.slice(); current.splice(afterPreviousKeyPosition, 0, data); return current; } return current; // default will also remove null results default: return current; } }