diff --git a/light9/collector/web/Light9CollectorUi.ts b/light9/collector/web/Light9CollectorUi.ts --- a/light9/collector/web/Light9CollectorUi.ts +++ b/light9/collector/web/Light9CollectorUi.ts @@ -1,75 +1,63 @@ import debug from "debug"; import { html, LitElement } from "lit"; -import { customElement, property } from "lit/decorators.js"; +import { customElement, property, state } from "lit/decorators.js"; import ReconnectingWebSocket from "reconnectingwebsocket"; import { sortBy, uniq } from "underscore"; +import { SyncedGraph } from "../../web/SyncedGraph"; +import { GraphAwarePage } from "../../web/GraphAwarePage"; +import { getTopGraph, GraphChangedEvent } from "../../web/RdfdbSyncedGraph"; +import { NamedNode } from "n3"; +import { Patch } from "../../web/patch"; +import { linkHorizontal } from "d3"; -debug.enable('*'); +export { RdfdbSyncedGraph } from "../../web/RdfdbSyncedGraph"; +export { Light9CollectorDevice } from "../../web/collector/Light9CollectorDevice"; + +debug.enable("*"); const log = debug("collector"); -class Updates { - constructor() { - this.listeners = []; - } - addListener(cb) { - this.listeners.push(cb); - } - onMessage(msg) { - this.listeners.forEach(function (lis) { - lis(msg); - }); - } -} - @customElement("light9-collector-ui") -export class Light9CollectorUi extends LitElement { +export class Light9CollectorUi extends GraphAwarePage { + graph?: SyncedGraph; static styles = []; render() { - return html` - - + return html`${super.render()}

Collector [metrics]

Devices

-
- -
+ `; + } +} + +@customElement("light9-collector-device-list") +export class Light9CollectorDeviceList extends LitElement { + graph!: SyncedGraph; + @property() devices: NamedNode[] = []; + + render() { + return html` +

Devices

+ +
${this.devices.map((d) => html``)}
`; } - - @property() graph: Object = {}; - @property() updates: Updates; - @property() devices: Array = []; - // observers: [ - // 'onGraph(graph)', - // ], - + constructor() { super(); - this.updates = new Updates(); - const ws = new ReconnectingWebSocket(location.href.replace("http", "ws") + "api/updates"); - ws.addEventListener("message", (ev: any) => { - log("ws msg", ev); - this.updates.onMessage(ev.data); + getTopGraph().then((g) => { + this.graph = g; + this.graph.runHandler(this.findDevices.bind(this), "findDevices"); }); } - - onGraph(graph) { - this.graph.runHandler(this.findDevices.bind(this), "findDevices"); - } - - findDevices() { - var U = function (x) { - return this.graph.Uri(x); - }; - this.set("devices", []); - + + findDevices(patch?: Patch) { + const U = this.graph.U(); + + this.devices = []; let classes = this.graph.subjects(U("rdf:type"), U(":DeviceClass")); uniq(sortBy(classes, "value"), true).forEach((dc) => { sortBy(this.graph.subjects(U("rdf:type"), dc), "value").forEach((dev) => { - this.push("devices", dev); + this.devices.push(dev as NamedNode); }); }); } diff --git a/light9/collector/web/index.html b/light9/collector/web/index.html --- a/light9/collector/web/index.html +++ b/light9/collector/web/index.html @@ -5,7 +5,7 @@ - + - graph: [[status]] - - - - - + constructor() { + super(); + this.status = "startup"; + this.graph = new SyncedGraph( + this.testGraph ? null : "/rdfdb/api/syncedGraph", + { + "": "http://light9.bigasterisk.com/", + dev: "http://light9.bigasterisk.com/device/", + rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + rdfs: "http://www.w3.org/2000/01/rdf-schema#", + xsd: "http://www.w3.org/2001/XMLSchema#", + }, + (s: string) => { + this.status = s; + }, + this.onClear.bind(this), + this.onGraphChanged.bind(this) + ); + // (window as any).topSyncedGraph = this.graph; + res(this.graph); + } + + private onGraphChanged(graph: SyncedGraph, patch: Patch) { + this.dispatchEvent( + new GraphChangedEvent("changed", { + detail: { graph, patch }, + bubbles: true, + composed: true, + }) + ); + } + }); +}); + + +async function getTopGraph(): Promise { + const s = (window as any).topSyncedGraph; + return await s; +} + +export { RdfdbSyncedGraph, getTopGraph }; \ No newline at end of file diff --git a/light9/web/resource-display.html b/light9/web/ResourceDisplay.ts rename from light9/web/resource-display.html rename to light9/web/ResourceDisplay.ts --- a/light9/web/resource-display.html +++ b/light9/web/ResourceDisplay.ts @@ -1,158 +1,165 @@ - - - +import debug from "debug"; +import { css, html, LitElement, PropertyValues } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; +import { NamedNode } from "n3"; +// import { GraphChangedEvent } from "../../web/RdfdbSyncedGraph"; +// import { runHandler } from "./GraphAwarePage"; +import { Patch, patchContainsPreds, patchSizeSummary } from "./patch"; +import { getTopGraph } from "./RdfdbSyncedGraph"; +import { SyncedGraph } from "./SyncedGraph"; +debug.enable("*"); +const log = debug("device-el"); +const RDFS_LABEL = new NamedNode("http://www.w3.org/2000/01/rdf-schema#label"); - - - - + constructor() { + super(); + getTopGraph().then((g) => { + this.graph = g; + this.runUriHandler(); + }); + } + + realUri(): NamedNode { + if (!this.uri) { + return new NamedNode(""); + } + return typeof this.uri === "string" ? new NamedNode(this.uri) : this.uri; + } + + href() { + if (!this.uri) { + return "javascript:;"; + } + return typeof this.uri === "string" ? this.uri : this.uri.value; + } + + updated(changedProperties: PropertyValues) { + if (changedProperties.has("uri")) { + if (!this.graph) { + return; /*too soon*/ + } + this.runUriHandler(); + } + } + + resClasses() { + return this.minor ? "resource minor" : "resource"; + } + + runUriHandler() { + this.graph.runHandler(this.onUri.bind(this), `rdisplay ${this.href()}` /*needs uniqueness?*/); + } + + onUri(patch?: Patch) { + if (!this.uri) { + this.label = ""; + return; + } + + const uri = this.realUri(); + this.graph.runHandler(this.setLabel.bind(this), `label ${uri.value}`); + } + + setLabel(patch?: Patch) { + if (patch && !patchContainsPreds(patch, [RDFS_LABEL])) { + return; + } + const uri = this.realUri(); + this.label = this.graph.labelOrTail(uri); + } + + onRename() { + this.renameTo = this.label; + this.shadowRoot.querySelector("#renameDialog").open(); + this.shadowRoot.querySelector("#renameTo").setSelectionRange(0, -1); + } + + onRenameKey(ev) { + if (ev.key == "Enter") { + this.shadowRoot.querySelector("[dialog-confirm]").click(); + } + if (ev.key == "Escape") { + this.shadowRoot.querySelector("[dialog-dismiss]").click(); + } + } + + onRenameClosed() { + var dialog = this.shadowRoot.querySelector("#renameDialog"); + if (dialog.closingReason.confirmed) { + var label = this.graph.Uri("rdfs:label"); + var ctxs = this.graph.contextsWithPattern(this.uri, label, null); + if (ctxs.length != 1) { + throw new Error(`${ctxs.length} label stmts for ${this.uri.label}`); + } + this.graph.patchObject(typeof this.uri === "string" ? this.graph.Uri(this.uri) : this.uri, label, this.graph.Literal(this.renameTo), ctxs[0]); + } + } +} diff --git a/light9/web/graph.ts b/light9/web/SyncedGraph.ts rename from light9/web/graph.ts rename to light9/web/SyncedGraph.ts --- a/light9/web/graph.ts +++ b/light9/web/SyncedGraph.ts @@ -1,196 +1,64 @@ import * as d3 from "d3"; import debug from "debug"; import * as N3 from "n3"; -import { Quad, Quad_Subject, Quad_Predicate, Quad_Object, Quad_Graph } from "n3"; -import { filter, sortBy, unique } from "underscore"; -import { allPatchSubjs, Patch } from "./patch"; +import { Quad, Quad_Object, Quad_Predicate, Quad_Subject } from "n3"; +import { sortBy, unique } from "underscore"; +import { AutoDependencies, HandlerFunc } from "./AutoDependencies"; +import { Patch, patchSizeSummary } from "./patch"; import { RdfDbClient } from "./rdfdbclient"; const log = debug("graph"); const RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; -interface QuadPattern { - subject: Quad_Subject | null; - predicate: Quad_Predicate | null; - object: Quad_Object | null; - graph: Quad_Graph | null; -} - -class Handler { - patterns: QuadPattern[]; - innerHandlers: Handler[]; - // a function and the quad patterns it cared about - constructor(public func: ((p: Patch) => void) | null, public label: string) { - this.patterns = []; // s,p,o,g quads that should trigger the next run - this.innerHandlers = []; // Handlers requested while this one was running - } -} - -class AutoDependencies { - handlers: Handler; - handlerStack: Handler[]; - constructor() { - // tree of all known Handlers (at least those with non-empty - // patterns). Top node is not a handler. - this.handlers = new Handler(null, "root"); - this.handlerStack = [this.handlers]; // currently running - } - - runHandler(func: any, label: any) { - // what if we have this func already? duplicate is safe? - - if (label == null) { - throw new Error("missing label"); - } - - const h = new Handler(func, label); - const tailChildren = this.handlerStack[this.handlerStack.length - 1].innerHandlers; - const matchingLabel = filter(tailChildren, (c: { label: any }) => c.label === label).length; - // ohno, something depends on some handlers getting run twice :( - if (matchingLabel < 2) { - tailChildren.push(h); - } - //console.time("handler #{label}") - return this._rerunHandler(h, null); - } - //console.timeEnd("handler #{label}") - //@_logHandlerTree() - - _rerunHandler(handler: Handler, patch: any) { - handler.patterns = []; - this.handlerStack.push(handler); - try { - if (handler.func === null) { - throw new Error("tried to rerun root"); - } - return handler.func(patch); - } catch (e) { - return log("error running handler: ", e); - } finally { - // assuming here it didn't get to do all its queries, we could - // add a *,*,*,* handler to call for sure the next time? - //log('done. got: ', handler.patterns) - this.handlerStack.pop(); - } - } - // handler might have no watches, in which case we could forget about it - - _logHandlerTree() { - log("handler tree:"); - var prn = function (h: Handler, depth: number) { - let indent = ""; - for (let i = 0, end = depth, asc = 0 <= end; asc ? i < end : i > end; asc ? i++ : i--) { - indent += " "; - } - log(`${indent} \"${h.label}\" ${h.patterns.length} pats`); - return Array.from(h.innerHandlers).map((c: any) => prn(c, depth + 1)); - }; - return prn(this.handlers, 0); - } - - _handlerIsAffected(child: Handler, patchSubjs: Set) { - if (patchSubjs === null) { - return true; - } - if (!child.patterns.length) { - return false; - } - - for (let stmt of Array.from(child.patterns)) { - if (stmt.subject === null) { - // wildcard on subject - return true; - } - if (patchSubjs.has(stmt.subject.value)) { - return true; - } - } - - return false; - } - - graphChanged(patch: Patch) { - // SyncedGraph is telling us this patch just got applied to the graph. - - const subjs = allPatchSubjs(patch); - - var rerunInners = (cur: Handler) => { - const toRun = cur.innerHandlers.slice(); - for (let child of Array.from(toRun)) { - //match = @_handlerIsAffected(child, subjs) - //continue if not match - //log('match', child.label, match) - //child.innerHandlers = [] # let all children get called again - - this._rerunHandler(child, patch); - rerunInners(child); - } - }; - return rerunInners(this.handlers); - } - - askedFor(s: Quad_Subject | null, p: Quad_Predicate | null, o: Quad_Object | null, g: Quad_Graph | null) { - // SyncedGraph is telling us someone did a query that depended on - // quads in the given pattern. - const current = this.handlerStack[this.handlerStack.length - 1]; - if (current != null && current !== this.handlers) { - return current.patterns.push({ subject: s, predicate: p, object: o, graph: g } as QuadPattern); - } - } -} - export class SyncedGraph { - _autoDeps: AutoDependencies; - _client: any; - graph: N3.Store; + private _autoDeps: AutoDependencies; + private _client: RdfDbClient; + private graph: N3.Store; cachedFloatValues: any; cachedUriValues: any; prefixFuncs: (x: string) => string = (x) => x; serial: any; _nextNumber: any; - // Main graph object for a browser to use. Syncs both ways with - // rdfdb. Meant to hide the choice of RDF lib, so we can change it + // Main graph object for a browser to use. Consider using RdfdbSyncedGraph element to create & own + // one of these. Syncs both ways with rdfdb. Meant to hide the choice of RDF lib, so we can change it // later. // // Note that _applyPatch is the only method to write to the graph, so // it can fire subscriptions. constructor( - // patchSenderUrl is the /syncedGraph path of an rdfdb server. - public patchSenderUrl: any, + // url is the /syncedGraph path of an rdfdb server. + public url: any, // prefixes can be used in Uri(curie) calls. public prefixes: { [short: string]: string }, private setStatus: any, // called if we clear the graph - private clearCb: any + private clearCb: any, + private onGraphChanged: (graph: SyncedGraph, newPatch: Patch)=>void ) { this.graph = new N3.Store(); - this._autoDeps = new AutoDependencies(); // replaces GraphWatchers + this._autoDeps = new AutoDependencies(); this.clearGraph(); - if (this.patchSenderUrl) { - this._client = new RdfDbClient(this.patchSenderUrl, this._clearGraphOnNewConnection.bind(this), this._applyPatch.bind(this), this.setStatus); - } + this._client = new RdfDbClient(this.url, this._clearGraphOnNewConnection.bind(this), this._applyPatch.bind(this), this.setStatus); } clearGraph() { // just deletes the statements; watchers are unaffected. - if (this.graph != null) { - this._applyPatch({ adds: [], dels: this.graph.getQuads(null, null, null, null) }); - } + this.cachedFloatValues = new Map(); // s + '|' + p -> number + this.cachedUriValues = new Map(); // s + '|' + p -> Uri + this._applyPatch({ adds: [], dels: this.graph.getQuads(null, null, null, null) }); // if we had a Store already, this lets N3.Store free all its indices/etc this.graph = new N3.Store(); this._addPrefixes(this.prefixes); - this.cachedFloatValues = new Map(); // s + '|' + p -> number - return (this.cachedUriValues = new Map()); // s + '|' + p -> Uri } _clearGraphOnNewConnection() { // must not send a patch to the server! - log("graph: clearGraphOnNewConnection"); + log("clearGraphOnNewConnection"); this.clearGraph(); - log("graph: clearGraphOnNewConnection done"); + log("clearGraphOnNewConnection done"); if (this.clearCb != null) { return this.clearCb(); } @@ -203,6 +71,10 @@ export class SyncedGraph { this.prefixFuncs = N3.Util.prefixes(this.prefixes); } + U() { // just a shorthand + return this.Uri.bind(this); + } + Uri(curie: string) { if (curie == null) { throw new Error("no uri"); @@ -299,20 +171,21 @@ export class SyncedGraph { _applyPatch(patch: Patch) { // In most cases you want applyAndSendPatch. // - // This is the only method that writes to @graph! - let quad: any; + // This is the only method that writes to this.graph! + log("patch from server [1]") this.cachedFloatValues.clear(); this.cachedUriValues.clear(); - for (quad of Array.from(patch.dels)) { + for (let quad of Array.from(patch.dels)) { //log("remove #{JSON.stringify(quad)}") const did = this.graph.removeQuad(quad); } //log("removed: #{did}") - for (quad of Array.from(patch.adds)) { + for (let quad of Array.from(patch.adds)) { this.graph.addQuad(quad); } - //log('applied patch locally', patchSizeSummary(patch)) - return this._autoDeps.graphChanged(patch); + log("applied patch locally", patchSizeSummary(patch)); + this._autoDeps.graphChanged(patch); + this.onGraphChanged(this, patch); } getObjectPatch(s: N3.NamedNode, p: N3.NamedNode, newObject: N3.Quad_Object, g: N3.NamedNode): Patch { @@ -326,7 +199,7 @@ export class SyncedGraph { } patchObject(s: N3.NamedNode, p: N3.NamedNode, newObject: N3.Quad_Object, g: N3.NamedNode) { - return this.applyAndSendPatch(this.getObjectPatch(s, p, newObject, g)); + this.applyAndSendPatch(this.getObjectPatch(s, p, newObject, g)); } clearObjects(s: N3.NamedNode, p: N3.NamedNode, g: N3.NamedNode) { @@ -336,7 +209,7 @@ export class SyncedGraph { }); } - runHandler(func: any, label: any) { + runHandler(func: HandlerFunc, label: string) { // runs your func once, tracking graph calls. if a future patch // matches what you queried, we runHandler your func again (and // forget your queries from the first time). @@ -350,7 +223,7 @@ export class SyncedGraph { this.serial += 1; //label = label + @serial - return this._autoDeps.runHandler(func, label); + this._autoDeps.runHandler(func, label); } _singleValue(s: Quad_Subject, p: Quad_Predicate) { @@ -416,13 +289,13 @@ export class SyncedGraph { return ret; } - objects(s: any, p: any) { + objects(s: any, p: any): Quad_Object[] { this._autoDeps.askedFor(s, p, null, null); const quads = this.graph.getQuads(s, p, null, null); return Array.from(quads).map((q: { object: any }) => q.object); } - subjects(p: any, o: any) { + subjects(p: any, o: any): Quad_Subject[] { this._autoDeps.askedFor(null, p, o, null); const quads = this.graph.getQuads(null, p, o, null); return Array.from(quads).map((q: { subject: any }) => q.subject); @@ -454,7 +327,7 @@ export class SyncedGraph { return out; } - contains(s: any, p: any, o: any) { + contains(s: any, p: any, o: any): boolean { this._autoDeps.askedFor(s, p, o, null); log("contains calling getQuads when graph has ", this.graph.size); return this.graph.getQuads(s, p, o, null).length > 0; diff --git a/light9/collector/web/Light9CollectorDevice.ts b/light9/web/collector/Light9CollectorDevice.ts rename from light9/collector/web/Light9CollectorDevice.ts rename to light9/web/collector/Light9CollectorDevice.ts --- a/light9/collector/web/Light9CollectorDevice.ts +++ b/light9/web/collector/Light9CollectorDevice.ts @@ -1,7 +1,11 @@ -import * as debug from "debug"; +import debug from "debug"; import { css, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators.js"; +import { NamedNode } from "n3"; +import { GraphChangedEvent } from "../RdfdbSyncedGraph"; +export {ResourceDisplay} from "../ResourceDisplay" debug.enable("*"); +const log = debug("device-el"); @customElement("light9-collector-device") export class Light9CollectorDevice extends LitElement { @@ -34,38 +38,50 @@ export class Light9CollectorDevice exten render() { return html` -

+

- + ${this.attrs.map( + (item) => html` + + + + + + ` + )}
out attr value chan
${item.attr}${item.val} →${item.chan}
`; } - @property() graph: Object = {}; - @property() uri: Object = {}; - @property() attrs: Array = []; + @property({ + // todo don't rebuild uri; pass it right + converter: (s: string | null) => new NamedNode(s || ""), + }) + uri: NamedNode = new NamedNode(""); + @property() attrs: Array<{ attr: string; valClass: string; val: string; chan: string }> = []; + constructor() { + super(); + // addGraphChangeListener(this.onGraphChanged.bind(this)); + } + onChanged(ev: GraphChangedEvent) { + log("patch from server [5]"); + } // observers: [ // "initUpdates(updates)", // ], - initUpdates(updates) { - updates.addListener(function (msg) { - if (msg.outputAttrsSet && msg.outputAttrsSet.dev == this.uri.value) { - this.set("attrs", msg.outputAttrsSet.attrs); - this.attrs.forEach(function (row) { - row.valClass = row.val == 255 ? "full" : row.val ? "nonzero" : ""; - }); - } - }); - } + // initUpdates(updates) { + // updates.addListener(function (msg) { + // if (msg.outputAttrsSet && msg.outputAttrsSet.dev == this.uri.value) { + // this.set("attrs", msg.outputAttrsSet.attrs); + // this.attrs.forEach(function (row) { + // row.valClass = row.val == 255 ? "full" : row.val ? "nonzero" : ""; + // }); + // } + // }); + // } } diff --git a/light9/web/collector/README.md b/light9/web/collector/README.md new file mode 100644 --- /dev/null +++ b/light9/web/collector/README.md @@ -0,0 +1,1 @@ +this is meant to be at light9/collector/web but I couldn't figure out the vite paths \ No newline at end of file diff --git a/light9/web/rdfdbclient.ts b/light9/web/rdfdbclient.ts --- a/light9/web/rdfdbclient.ts +++ b/light9/web/rdfdbclient.ts @@ -9,10 +9,10 @@ export class RdfDbClient { _patchesReceived: number; _patchesSent: number; _connectionId: string; - _reconnectionTimeout: number | null; - ws: WebSocket | undefined; - _pingLoopTimeout: any; - // Send and receive patches from rdfdb + _reconnectionTimeout?: number; + ws?: WebSocket; + _pingLoopTimeout?: number; + // Send and receive patches from rdfdb. Primarily used in SyncedGraph. // // What this should do, and does not yet, is keep the graph // 'coasting' over a reconnect, applying only the diffs from the old @@ -30,7 +30,6 @@ export class RdfDbClient { this._patchesReceived = 0; this._patchesSent = 0; this._connectionId = "??"; - this._reconnectionTimeout = null; this.ws = undefined; this._newConnection(); @@ -63,7 +62,7 @@ export class RdfDbClient { } sendPatch(patch: Patch) { - log("rdfdbclient: queue patch to server ", patchSizeSummary(patch)); + log("queue patch to server ", patchSizeSummary(patch)); this._patchesToSend.push(patch); this._updateStatus(); this._continueSending(); @@ -76,35 +75,37 @@ export class RdfDbClient { this.ws.close(); } this.ws = new WebSocket(fullUrl); + this.ws.onopen = this.onWsOpen.bind(this); + this.ws.onerror = this.onWsError.bind(this); + this.ws.onclose = this.onWsClose.bind(this); + this.ws.onmessage = this._onMessage.bind(this); + } - this.ws.onopen = () => { - log("rdfdbclient: new connection to", fullUrl); - this._updateStatus(); - this.clearGraphOnNewConnection(); - return this._pingLoop(); - }; + private onWsOpen() { + log("new connection to", this.patchSenderUrl); + this._updateStatus(); + this.clearGraphOnNewConnection(); + return this._pingLoop(); + } - this.ws.onerror = (e: Event) => { - log("rdfdbclient: ws error " + e); - if (this.ws !== undefined) { - const closeHandler = this.ws.onclose?.bind(this.ws); - if (!closeHandler) { - throw new Error(); - } - closeHandler(new CloseEvent("forced")); + private onWsError(e: Event) { + log("ws error", e); + if (this.ws !== undefined) { + const closeHandler = this.ws.onclose?.bind(this.ws); + if (!closeHandler) { + throw new Error(); } - }; + closeHandler(new CloseEvent("forced")); + } + } - this.ws.onclose = (ev: CloseEvent) => { - log("rdfdbclient: ws close"); - this._updateStatus(); - if (this._reconnectionTimeout != null) { - clearTimeout(this._reconnectionTimeout); - } - this._reconnectionTimeout = (setTimeout(this._newConnection.bind(this), 1000) as unknown) as number; - }; - - this.ws.onmessage = this._onMessage.bind(this); + private onWsClose(ev: CloseEvent) { + log("ws close"); + this._updateStatus(); + if (this._reconnectionTimeout !== undefined) { + clearTimeout(this._reconnectionTimeout); + } + this._reconnectionTimeout = (setTimeout(this._newConnection.bind(this), 1000) as unknown) as number; } _pingLoop() { @@ -115,7 +116,7 @@ export class RdfDbClient { if (this._pingLoopTimeout != null) { clearTimeout(this._pingLoopTimeout); } - this._pingLoopTimeout = setTimeout(this._pingLoop.bind(this), 10000); + this._pingLoopTimeout = (setTimeout(this._pingLoop.bind(this), 10000) as unknown) as number; } } @@ -131,6 +132,7 @@ export class RdfDbClient { if (input.connectedAs) { this._connectionId = input.connectedAs; } else { + log("patch from server [0]") parseJsonPatch(input, this.applyPatch.bind(this)); this._patchesReceived++; } @@ -148,7 +150,7 @@ export class RdfDbClient { const sendOne = (patch: any, cb: (arg0: any) => any) => { return toJsonPatch(patch, (json: string) => { - log("rdfdbclient: send patch to server, " + json.length + " bytes"); + log("send patch to server, " + json.length + " bytes"); if (!this.ws) { throw new Error("can't send"); }