UNPKG

@platform/cell.typesystem

Version:

The 'strongly typed sheets' system of the CellOS.

193 lines (192 loc) 6.93 kB
import { filter, map, share, takeUntil } from 'rxjs/operators'; import { TypeCache } from '../../TypeSystem.core'; import { deleteUndefined, R, Schema, t, Uri } from './common'; export class TypedSheetState { constructor(args) { this._changes = {}; this._dispose$ = new t.Subject(); this.dispose$ = this._dispose$.pipe(share()); this.clear = { cache: () => { const ns = this.uri.id; const fetch = this.fetch; const cache = fetch.cache; const prefix = { ns: fetch.cacheKey('getNs', ns), cells: fetch.cacheKey('getCells', ns), }; cache.keys .filter(key => key.startsWith(prefix.cells) || key.startsWith(prefix.ns)) .forEach(key => cache.delete(key)); }, changes: (action) => { const sheet = this._sheet; const from = Object.assign({}, this._changes); const to = {}; this._changes = {}; this.fire({ type: 'SHEET/changes/cleared', payload: { sheet, from, to, action }, }); }, }; this.change = { ns: (to) => { const ns = this.uri.toString(); this.fire({ type: 'SHEET/change', payload: { kind: 'NS', ns, to }, }); }, cell: (key, to) => { const ns = this.uri.id; this.fire({ type: 'SHEET/change', payload: { kind: 'CELL', ns, key, to }, }); }, }; this._sheet = args.sheet; this._event$ = args.event$; const fetch = TypeCache.fetch(args.fetch, { cache: args.cache }); const getCells = async (args) => { const res = await fetch.getCells(args); const cellChanges = this._changes.cells || {}; const keys = Object.keys(cellChanges); if (keys.length > 0) { const cells = (res.cells = Object.assign({}, (res.cells || {}))); keys .filter(key => Schema.coord.cell.isCell(key) && cells[key]) .forEach(key => (cells[key] = Object.assign({}, cellChanges[key].to))); } return res; }; const getNs = async (args) => { var _a; const ns = (_a = this._changes.ns) === null || _a === void 0 ? void 0 : _a.to; return ns ? { ns } : fetch.getNs(args); }; this.fetch = Object.assign(Object.assign({}, fetch), { getCells, getNs }); this.event$ = this._event$.pipe(takeUntil(this._dispose$), share()); this.change$ = this.event$.pipe(takeUntil(this.dispose$), filter(e => e.type === 'SHEET/change'), map(e => e.payload), filter(e => this.isWithinNamespace(e.ns)), share()); this.changed$ = this.event$.pipe(takeUntil(this.dispose$), filter(e => e.type === 'SHEET/changed'), map(e => e.payload), filter(e => this.isWithinNamespace(e.sheet.uri.toString())), share()); this.change$ .pipe(filter(e => e.kind === 'CELL'), map(e => e)) .subscribe(({ key, to }) => this.fireCellChanged({ key, to })); this.change$ .pipe(filter(e => e.kind === 'NS')) .subscribe(({ to }) => this.fireNsChanged({ to })); } static create(args) { return new TypedSheetState(args); } dispose() { this._dispose$.next(); this._dispose$.complete(); } get uri() { return this._sheet.uri; } get isDisposed() { return this._dispose$.isStopped; } get changes() { const changes = this._changes; return deleteUndefined({ ns: changes.ns ? Object.assign({}, changes.ns) : undefined, cells: changes.cells ? Object.assign({}, changes.cells) : undefined, }); } get hasChanges() { const changes = this._changes; if (changes.ns) { return true; } if (changes.cells && Object.keys(changes.cells).length > 0) { return true; } return false; } async getNs() { const ns = this.uri.id; return (await this.fetch.getNs({ ns })).ns; } async getCell(key) { if (!Schema.coord.cell.isCell(key)) { throw new Error(`Expected a cell key (eg "A1").`); } const cellChanges = this._changes.cells || {}; if (cellChanges[key]) { return cellChanges[key].to; } const ns = this.uri.id; const query = `${key}:${key}`; const res = await this.fetch.getCells({ ns, query }); return (res.cells || {})[key]; } fire(e) { this._event$.next(e); } async fireNsChanged(args) { const { to } = args; const existing = this._changes.ns; if (existing && R.equals(existing.to, to)) { return; } const from = (existing ? existing.from : await this.getNs()) || {}; const change = { kind: 'NS', ns: this.uri.id, from, to, }; this._changes = Object.assign(Object.assign({}, this._changes), { ns: change }); this.fireChanged({ change }); } async fireCellChanged(args) { const { to, key } = args; const existing = (this._changes.cells || {})[key]; if (existing && R.equals(existing.to, to)) { return; } const ns = this.uri.id; const from = (existing ? existing.from : await this.getCell(key)) || {}; delete from.hash; delete to.hash; const change = { kind: 'CELL', ns, key, from, to, }; const cells = Object.assign(Object.assign({}, (this._changes.cells || {})), { [key]: change }); this._changes = Object.assign(Object.assign({}, this._changes), { cells }); this.fireChanged({ change }); } fireChanged(args) { const { change } = args; this.fire({ type: 'SHEET/changed', payload: { sheet: this._sheet, change, changes: this.changes, }, }); } isWithinNamespace(input) { const text = (input || '').trim(); const ns = this.uri; if (!text.includes(':')) { return Uri.strip.ns(text) === ns.id; } if (text.startsWith('ns:') || !text.includes(':')) { return text === ns.toString(); } if (text.startsWith('cell:')) { return text.startsWith(`cell:${ns.id}:`); } return false; } }