# HG changeset patch # User drewp@bigasterisk.com # Date 1683576320 25200 # Node ID cf642d395be475ca580c2c1e2708c73ae86f986c # Parent ea0b4e46de2b4a18ceb3fe59a3de5bbf17c0ddb9 new simpler Patch class; fancier 'hide' view config support diff -r ea0b4e46de2b -r cf642d395be4 src/Patch.ts --- a/src/Patch.ts Sat May 06 15:35:11 2023 -0700 +++ b/src/Patch.ts Mon May 08 13:05:20 2023 -0700 @@ -1,19 +1,24 @@ import { Quad, Store } from "n3"; import { Stream } from "rdf-js"; +export enum PatchDirection { + ADD = "+", + DEL = "-", +} + export class Patch { - delQuads: Quad[] = []; - addQuads: Quad[] = []; + quads: Quad[] = []; + + constructor(public direction: PatchDirection) {} + toString(): string { - return `Patch -${this.delQuads.length} +${this.addQuads.length}`; + return `Patch ${this.direction} ${this.quads.length}`; } - constructor() { } - // fill `addQuads` with this stream public async streamImport(quadStream: Stream): Promise { return new Promise((resolve, reject) => { quadStream.on("data", (quad) => { - this.addQuads.push(quad); + this.quads.push(quad); }); quadStream.on("error", reject); quadStream.on("end", resolve); @@ -21,7 +26,13 @@ } public applyToStore(s: Store) { - s.removeQuads(this.delQuads); - s.addQuads(this.addQuads); + if (this.direction == PatchDirection.ADD) { + s.addQuads(this.quads); + } else { + s.removeQuads(this.quads); + } + } + public isEmpty(): boolean { + return this.quads.length == 0; } } diff -r ea0b4e46de2b -r cf642d395be4 src/SourceGraph.ts --- a/src/SourceGraph.ts Sat May 06 15:35:11 2023 -0700 +++ b/src/SourceGraph.ts Mon May 08 13:05:20 2023 -0700 @@ -1,7 +1,17 @@ import { JsonLdParser } from "jsonld-streaming-parser"; import { Parser, Store } from "n3"; import { SubEvent } from "sub-events"; -import { Patch } from "./Patch"; +import { Patch, PatchDirection } from "./Patch"; + +type JsonLdData = Object; + +interface CombinedPatchBody { + adds?: JsonLdData; + deletes?: JsonLdData; +} +interface CombinedPatchEvent { + patch: CombinedPatchBody; +} // Possibly-changing graph for one source. Maintains its own dataset. // Read-only. @@ -22,6 +32,7 @@ // async reloadRdf() { const resp = await fetch(this.url); + this.clearStore(); const ctype = resp.headers.get("content-type"); if (ctype?.startsWith("text/event-stream")) { await this.reloadEventStream(); @@ -30,43 +41,85 @@ } } + private clearStore() { + this.store.removeMatches(null, null, null, null); + } + + async makePatchFromParsed( + dir: PatchDirection, + jsonLdObj: Object | undefined + ): Promise { + const p = new Patch(dir); + if (jsonLdObj === undefined) { + return p; + } + const parser = new JsonLdParser(); + + parser.write(JSON.stringify(jsonLdObj)); + parser.end(); + + await p.streamImport(parser); + return p; + } + + private applyPatch(p: Patch) { + if (p.isEmpty()){ + return; + } + p.applyToStore(this.store); + if (this.sourceGraphChanged.getStat().unnamed == 0) { + console.warn("no listeners to this sourceGraphChanged event"); + } + this.sourceGraphChanged.emit(p); + } + private async reloadEventStream(): Promise { return new Promise((res, rej) => { - // todo deal with reconnects const ev = new EventSource(this.url); - let firstPatch = true; - // clear store here? - // maybe the event messages should be 'add' and 'del', - // for less parsing and clearer order of ops. - ev.addEventListener("patch", async (ev) => { - // this is reentrant- ok? - - const patchMsg = JSON.parse((ev as any).data); - - const p = new Patch(); - + // old-style fullGraph. I now think it would be better to send plain + // adds and for this SourceGraph to emptyGraph when there's a new connection. + ev.addEventListener("fullGraph", async (ev) => { + this.clearStore(); + const p = new Patch(PatchDirection.ADD); const parser = new JsonLdParser(); - parser.write(JSON.stringify(patchMsg.patch.adds)); + parser.write(ev.data); parser.end(); await p.streamImport(parser); + p.applyToStore(this.store); this.isCurrent = true; - - p.applyToStore(this.store); - console.log("patchlife0: eventsream store changed"); - // firing before there are listeners this.sourceGraphChanged.emit(p); - if (firstPatch) { - firstPatch = false; - res(); - } + res(); }); + + // this is for old-style dels-then-adds patches + ev.addEventListener("patch", async (ev) => { + // this is reentrant- patches might be able to get applied out of order! + + const patchMsg: CombinedPatchEvent = JSON.parse((ev as any).data); + + const p0 = await this.makePatchFromParsed( + PatchDirection.DEL, + patchMsg.patch.deletes + ); + const p1 = await this.makePatchFromParsed( + PatchDirection.ADD, + patchMsg.patch.adds + ); + + this.applyPatch(p0); + this.applyPatch(p1); + + this.isCurrent = true; + }); + // here, add listeners for new-style add/del patches }); } private async reloadSimpleFile(resp: Response) { const bodyText = await resp.text(); + const p = new Patch(PatchDirection.ADD); const parser = new Parser({ format: "application/trig" }); await new Promise((res, rej) => { parser.parse(bodyText, (error, quad, prefixes) => { @@ -76,16 +129,14 @@ return; } if (quad) { - this.store.addQuad(quad); // prob want to do this as one or a small number of patches + p.quads.push(quad); } else { res(); } }); }); + this.applyPatch(p); this.isCurrent = true; - // this may have to fire per addQuad call for correctness, or else we batch the adds where no readers can see them in advance. - console.log("patchlife0: simple file store changed"); - this.sourceGraphChanged.emit(new Patch()); } quadCount(): number { diff -r ea0b4e46de2b -r cf642d395be4 src/elements/graph-view/GraphView.ts --- a/src/elements/graph-view/GraphView.ts Sat May 06 15:35:11 2023 -0700 +++ b/src/elements/graph-view/GraphView.ts Mon May 08 13:05:20 2023 -0700 @@ -33,12 +33,12 @@ static styles = [pageStyle, graphViewStyle]; update(changedProperties: PropertyValues) { if (changedProperties.has("graph") && this.graph) { - // const viewUri = new NamedNode(this.viewEl?.getAttribute('uri')) - const viewUri = new NamedNode(new URL("#view", document.baseURI).href); + const viewUri = new NamedNode(this.viewEl?.getAttribute('uri') || 'no-view') + // const viewUri = new NamedNode(new URL("#view", document.baseURI).href); this.viewConfig = new ViewConfig(this.graph, viewUri); // "when viewconfig is updated..." - setTimeout(()=>this.requestUpdate(), 1000) + // setTimeout(()=>this.requestUpdate(), 1000) this.graph.graphChanged.subscribe(this.onChange?.bind(this)); } @@ -67,14 +67,14 @@ this._addLabelsForAllTerms(graph, labels); labels.planDisplayForNode(this.viewConfig.viewRoot); // todo shoudltn be needed this.nodeDisplay = new NodeDisplay(labels); - let viewTitle = html` (no view)`; - viewTitle = html` using view + let viewTitle = html``; + viewTitle = html`

View {this.nodeDisplay.render(this.viewConfig.viewRoot)}${this.nodeDisplay.render(this.viewConfig.viewRoot)}`; return html`
-

View: ${viewTitle}

+ ${viewTitle}