|
@@ -23,8 +23,7 @@ import {
|
|
|
|
|
|
import {getDeltasForDelete} from "../onion/delete_node";
|
|
|
|
|
|
-export class ParseError extends Error {
|
|
|
-}
|
|
|
+export class ParseError extends Error {}
|
|
|
|
|
|
export interface Geometry2DRect {
|
|
|
x: number;
|
|
@@ -36,9 +35,23 @@ export interface Geometry2DRect {
|
|
|
const geometryLabels = ["x", "y", "width", "height"];
|
|
|
|
|
|
// Whether a is inside of b
|
|
|
-const isInside = (a: Geometry2DRect, b: Geometry2DRect): boolean =>
|
|
|
+export const isInside = (a: Geometry2DRect, b: Geometry2DRect): boolean =>
|
|
|
a.x > b.x && a.y > b.y && a.x+a.w < b.x+b.w && a.y+a.h < b.y+b.h;
|
|
|
|
|
|
+export function getGeometry(sourceState: GraphState, nodeId: PrimitiveValue): Geometry2DRect | undefined {
|
|
|
+ const node = sourceState.nodes.get(nodeId);
|
|
|
+ if (node !== undefined) {
|
|
|
+ try {
|
|
|
+ return {
|
|
|
+ x: (node.getOutgoingEdges().get("x")!.asTarget()) as number,
|
|
|
+ y: (node.getOutgoingEdges().get("y")!.asTarget()) as number,
|
|
|
+ w: (node.getOutgoingEdges().get("width")!.asTarget()) as number,
|
|
|
+ h: (node.getOutgoingEdges().get("height")!.asTarget()) as number,
|
|
|
+ };
|
|
|
+ }
|
|
|
+ catch(e) {}
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
// A parser that creates an AS-node for every CS-node, with a Corr-node in between.
|
|
|
export class TrivialParser {
|
|
@@ -52,7 +65,6 @@ export class TrivialParser {
|
|
|
this.getUuid = getUuid;
|
|
|
}
|
|
|
|
|
|
-
|
|
|
// We can use pretty much the same code for both parsing and rendering :)
|
|
|
propagate_change(parse: boolean, sourceDeltas: PrimitiveDelta[], sourceState: GraphState, corrState: GraphState, targetState: GraphState) {
|
|
|
const targetDeltas: Delta[] = []; // deltas that are only part of target model
|
|
@@ -66,7 +78,7 @@ export class TrivialParser {
|
|
|
|
|
|
// In order to parse, the CS/CORR/AS-state may be altered in-place.
|
|
|
// Whenever the state is altered, a callback must be pushed to this array that undoes the change.
|
|
|
- const revertState: (()=>void)[] = [];
|
|
|
+ // const revertState: (()=>void)[] = [];
|
|
|
|
|
|
const applyToState = (state: GraphState, delta: PrimitiveDelta | PrimitiveDelta[]) => {
|
|
|
if (Array.isArray(delta)) {
|
|
@@ -74,19 +86,18 @@ export class TrivialParser {
|
|
|
}
|
|
|
else {
|
|
|
state.exec(delta);
|
|
|
- revertState.push(() => state.unexec(delta));
|
|
|
+ // revertState.push(() => state.unexec(delta));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ let complete = true; // is parsing/rendering complete, or do we need manual adjustment? (for missing geometry information)
|
|
|
+
|
|
|
try {
|
|
|
for (const sourceDelta of sourceDeltas) {
|
|
|
// We'll update the sourceState, in-place, for every delta that we are parsing/rendering
|
|
|
applyToState(sourceState, sourceDelta);
|
|
|
|
|
|
if (sourceDelta instanceof NodeCreation) {
|
|
|
- if (!parse) {
|
|
|
- throw new ParseError("Cannot render node creation (missing geometry information)");
|
|
|
- }
|
|
|
|
|
|
// Creations are easy, and never cause conflicts:
|
|
|
const sourceCreation = sourceDelta; // alias for readability :)
|
|
@@ -102,42 +113,49 @@ export class TrivialParser {
|
|
|
|
|
|
corrDeltas.push(corrCreation, corr2Source, corr2Target);
|
|
|
targetDeltas.push(targetCreation);
|
|
|
+
|
|
|
+ if (!parse) {
|
|
|
+ // generate a Rountangle with some default geometry:
|
|
|
+ const edges: [string,PrimitiveValue][] = [
|
|
|
+ ["type", "Rountangle"],
|
|
|
+ // ["label", ""],
|
|
|
+ ["x", 0],
|
|
|
+ ["y", 0],
|
|
|
+ ["z-index", 0],
|
|
|
+ ["width", 100],
|
|
|
+ ["height", 60],
|
|
|
+ ];
|
|
|
+ targetDeltas.push(...edges.map(([edgeLabel, value]) =>
|
|
|
+ this.primitiveRegistry.newEdgeCreation(targetCreation, edgeLabel, value)));
|
|
|
+
|
|
|
+ complete = false; // actual geometry of new rountangle to be determined by user
|
|
|
+ }
|
|
|
}
|
|
|
else if (sourceDelta instanceof NodeDeletion) {
|
|
|
const sourceDeletion = sourceDelta; // alias for readability :)
|
|
|
const sourceCreation = sourceDeletion.creation; // the NodeCreation of the deleted cs node
|
|
|
+ const sourceId = sourceCreation.id.value;
|
|
|
|
|
|
- // edge from corrspondence node to CS node:
|
|
|
- const corr2Source = sourceCreation.incomingEdges.find(delta => delta instanceof EdgeCreation && delta.label === corr2SourceLabel);
|
|
|
- if (corr2Source === undefined || !(corr2Source instanceof EdgeCreation)) {
|
|
|
- throw new Error("Assertion failed: Must be able to find corr -> " + corr2SourceLabel + " edge.");
|
|
|
- }
|
|
|
-
|
|
|
- // creation of correspondence node:
|
|
|
- const corrCreation = corr2Source.source;
|
|
|
-
|
|
|
- const corr2Target = corrCreation.outgoingEdges.find(delta => delta instanceof EdgeCreation && delta.label === corr2TargetLabel);
|
|
|
- if (corr2Target === undefined || !(corr2Target instanceof EdgeCreation)) {
|
|
|
- throw new Error("Assertion failed: Must be able to find corr -> " + corr2TargetLabel + " edge.");
|
|
|
- }
|
|
|
-
|
|
|
- const targetCreation = corr2Target.target.getTarget() as NodeCreation;
|
|
|
+ // Follow 'cs'/'as' edges in correspondence model to find the corresponding node:
|
|
|
+ const sourceNodeState = corrState.nodes.get(sourceId)!;
|
|
|
+ const [_, corrNodeState] = sourceNodeState.getIncomingEdges().find(([label, nodeState]) => label === corr2SourceLabel)!;
|
|
|
+ const targetNodeState = corrNodeState.getOutgoingEdges().get(corr2TargetLabel) as INodeState;
|
|
|
|
|
|
- // deletion of correspondence node, and its outgoing ('cs', 'as') edges.
|
|
|
- const corrDeletion = corrState.nodes.get(corrCreation.id.value)!.getDeltasForDelete(this.primitiveRegistry);
|
|
|
+ // Deletion of correspondence node, and its outgoing ('cs', 'as') edges:
|
|
|
+ const corrDeletion = corrNodeState.getDeltasForDelete(this.primitiveRegistry);
|
|
|
|
|
|
// The following operations on corrState depend on 'corrDeletion'. That's why update corrState now. We'll undo these changes later.
|
|
|
applyToState(corrState, corrDeletion);
|
|
|
|
|
|
- // We create 2 (conflicting) asDeletions:
|
|
|
+ // We create 2 asDeletions:
|
|
|
// one that is to be used only in the AS model, unaware of any CORR-stuff, and one that is to be used in the CORR model.
|
|
|
- const targetDeletion = targetState.nodes.get(targetCreation.id.value)!.getDeltasForDelete(this.primitiveRegistry);
|
|
|
- const targetDeletion1 = corrState.nodes.get(targetCreation.id.value)!.getDeltasForDelete(this.primitiveRegistry);
|
|
|
+ const targetDeletion = targetState.nodes.get(targetNodeState.creation.id.value)!.getDeltasForDelete(this.primitiveRegistry);
|
|
|
+ const targetDeletion1 = targetNodeState.getDeltasForDelete(this.primitiveRegistry);
|
|
|
|
|
|
applyToState(corrState, targetDeletion1);
|
|
|
|
|
|
// We already have the deletion in the CS model, so we only need to create another one to be used in the CORR model:
|
|
|
- const sourceDeletion1 = corrState.nodes.get(sourceCreation.id.value)!.getDeltasForDelete(this.primitiveRegistry);
|
|
|
+ const sourceDeletion1 = sourceNodeState.getDeltasForDelete(this.primitiveRegistry);
|
|
|
|
|
|
applyToState(corrState, sourceDeletion1);
|
|
|
|
|
@@ -159,26 +177,15 @@ export class TrivialParser {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- throw new ParseError("Cannot render edge creation / edge update (missing geometry information");
|
|
|
+
|
|
|
+ // parent edge updated: need to update geometry: (manually)
|
|
|
+ complete = false;
|
|
|
+ continue;
|
|
|
}
|
|
|
|
|
|
const edgeCreation = (sourceDelta as (EdgeCreation | EdgeUpdate)).getCreation();
|
|
|
const label = edgeCreation.label;
|
|
|
|
|
|
- function getGeometry(nodeId: PrimitiveValue): Geometry2DRect | undefined {
|
|
|
- const node = sourceState.nodes.get(nodeId);
|
|
|
- if (node !== undefined) {
|
|
|
- try {
|
|
|
- return {
|
|
|
- x: (node.getOutgoingEdges().get("x")!.asTarget()) as number,
|
|
|
- y: (node.getOutgoingEdges().get("y")!.asTarget()) as number,
|
|
|
- w: (node.getOutgoingEdges().get("width")!.asTarget()) as number,
|
|
|
- h: (node.getOutgoingEdges().get("height")!.asTarget()) as number,
|
|
|
- };
|
|
|
- }
|
|
|
- catch(e) {}
|
|
|
- }
|
|
|
- }
|
|
|
function findCorrespondingAsNode(csNode: INodeState, reverse: boolean = false) {
|
|
|
const pair = csNode.getIncomingEdges().find(([label]) => label===(reverse?"as":"cs"));
|
|
|
if (pair === undefined) {
|
|
@@ -194,7 +201,7 @@ export class TrivialParser {
|
|
|
|
|
|
if (geometryLabels.includes(label)) {
|
|
|
const updatedNodeId = edgeCreation.source.id.value;
|
|
|
- const updatedGeometry = getGeometry(updatedNodeId);
|
|
|
+ const updatedGeometry = getGeometry(sourceState, updatedNodeId);
|
|
|
if (updatedGeometry !== undefined) {
|
|
|
const updatedSurface = updatedGeometry.w * updatedGeometry.h;
|
|
|
const updatedAsNode = findCorrespondingAsNode(corrState.nodes.get(updatedNodeId) as INodeState);
|
|
@@ -209,7 +216,7 @@ export class TrivialParser {
|
|
|
if (otherNodeState.creation === edgeCreation.source) {
|
|
|
continue; // don't compare with ourselves
|
|
|
}
|
|
|
- const otherGeometry = getGeometry(otherNodeId);
|
|
|
+ const otherGeometry = getGeometry(sourceState, otherNodeId);
|
|
|
if (otherGeometry !== undefined) {
|
|
|
const inside = isInside(geometry, otherGeometry);
|
|
|
if (inside) {
|
|
@@ -224,7 +231,7 @@ export class TrivialParser {
|
|
|
if (smallestParent !== null) {
|
|
|
const existingLink = asNodeState.getOutgoingEdges().get("hasParent");
|
|
|
if (existingLink !== smallestParent) {
|
|
|
- console.log("updated geometry is on inside...");
|
|
|
+ // console.log("updated geometry is on inside...");
|
|
|
const asParentLink = asNodeState.getDeltasForSetEdge(this.primitiveRegistry, "hasParent", smallestParent.asTarget());
|
|
|
applyToState(corrState, asParentLink);
|
|
|
targetDeltas.push(...asParentLink);
|
|
@@ -238,10 +245,10 @@ export class TrivialParser {
|
|
|
if (otherAsNodeState !== undefined && otherAsNodeState.type === "node") {
|
|
|
const otherCsNodeState = findCorrespondingAsNode(otherAsNodeState, true);
|
|
|
const otherNodeId = otherCsNodeState.creation.id.value;
|
|
|
- const otherGeometry = getGeometry(otherNodeId);
|
|
|
+ const otherGeometry = getGeometry(sourceState, otherNodeId);
|
|
|
if (otherGeometry === undefined || !isInside(updatedGeometry, otherGeometry)) {
|
|
|
// parent relation no longer holds
|
|
|
- console.log("deleting outgoing link...")
|
|
|
+ // console.log("deleting outgoing link...")
|
|
|
// CORRECT: we'll find updatedAsNode's new parent in step 2.
|
|
|
const deleteLink = updatedAsNode.getDeltasForSetEdge(this.primitiveRegistry, "hasParent", null); // deletes the edge
|
|
|
applyToState(corrState, deleteLink);
|
|
@@ -252,7 +259,7 @@ export class TrivialParser {
|
|
|
for (const [_, otherAsNodeState] of updatedAsNode.getIncomingEdges().filter(([label, ns]) => label === "hasParent")) {
|
|
|
const otherCsNodeState = findCorrespondingAsNode(otherAsNodeState, true);
|
|
|
const otherNodeId = otherCsNodeState.creation.id.value;
|
|
|
- const otherGeometry = getGeometry(otherNodeId);
|
|
|
+ const otherGeometry = getGeometry(sourceState, otherNodeId);
|
|
|
if (otherGeometry === undefined) {
|
|
|
throw new Error("Assertion failed: The Corresponding CS node of an AS node that is target of 'hasParent' has no geometry.");
|
|
|
}
|
|
@@ -276,7 +283,7 @@ export class TrivialParser {
|
|
|
if (otherNodeState.creation === edgeCreation.source) {
|
|
|
continue; // don't compare with ourselves
|
|
|
}
|
|
|
- const otherGeometry = getGeometry(otherNodeId);
|
|
|
+ const otherGeometry = getGeometry(sourceState, otherNodeId);
|
|
|
if (otherGeometry !== undefined) {
|
|
|
findAndSetNewParent(updatedGeometry, updatedAsNode);
|
|
|
const outside = isInside(otherGeometry, updatedGeometry);
|
|
@@ -290,14 +297,14 @@ export class TrivialParser {
|
|
|
return Infinity;
|
|
|
}
|
|
|
const otherCurrentParentCs = findCorrespondingAsNode(otherCurrentParent as INodeState, true);
|
|
|
- const otherCurrentParentGeometry = getGeometry(otherCurrentParentCs.creation.id.value);
|
|
|
+ const otherCurrentParentGeometry = getGeometry(sourceState, otherCurrentParentCs.creation.id.value);
|
|
|
if (otherCurrentParentGeometry === undefined) {
|
|
|
return Infinity;
|
|
|
}
|
|
|
return otherCurrentParentGeometry.w * otherCurrentParentGeometry.h;
|
|
|
})();
|
|
|
if (updatedSurface < otherCurrentParentSurface) {
|
|
|
- console.log("updated geometry is on outside...");
|
|
|
+ // console.log("updated geometry is on outside...");
|
|
|
const asParentLink = otherAsNode.getDeltasForSetEdge(this.primitiveRegistry, "hasParent", updatedAsNode.asTarget());
|
|
|
applyToState(corrState, asParentLink);
|
|
|
targetDeltas.push(...asParentLink);
|
|
@@ -312,7 +319,7 @@ export class TrivialParser {
|
|
|
}
|
|
|
finally {
|
|
|
// Rollback all changes that were made (in-place) to the CS/CORR/AS-state
|
|
|
- revertState.reduceRight((_,callback) => {callback(); return null;}, null);
|
|
|
+ // revertState.reduceRight((_,callback) => {callback(); return null;}, null);
|
|
|
}
|
|
|
|
|
|
const corrDeltasOrderedByDependency: PrimitiveDelta[] = [];
|
|
@@ -330,6 +337,7 @@ export class TrivialParser {
|
|
|
targetDeltas,
|
|
|
sourceOverrides,
|
|
|
targetOverrides,
|
|
|
+ complete,
|
|
|
};
|
|
|
// console.log(result);
|
|
|
return result;
|
|
@@ -341,9 +349,10 @@ export class TrivialParser {
|
|
|
targetDeltas,
|
|
|
sourceOverrides: csOverrides,
|
|
|
targetOverrides: asOverrides,
|
|
|
+ complete,
|
|
|
} = this.propagate_change(true, csDeltas, csState, corrState, asState);
|
|
|
|
|
|
- return { corrDeltas, asDeltas: targetDeltas, csOverrides, asOverrides };
|
|
|
+ return { corrDeltas, asDeltas: targetDeltas, csOverrides, asOverrides, complete };
|
|
|
}
|
|
|
|
|
|
render(asDeltas: PrimitiveDelta[], csState: GraphState, corrState: GraphState, asState: GraphState) {
|
|
@@ -352,8 +361,9 @@ export class TrivialParser {
|
|
|
targetDeltas,
|
|
|
sourceOverrides: asOverrides,
|
|
|
targetOverrides: csOverrides,
|
|
|
+ complete,
|
|
|
} = this.propagate_change(false, asDeltas, asState, corrState, csState);
|
|
|
|
|
|
- return { corrDeltas, csDeltas: targetDeltas, csOverrides, asOverrides };
|
|
|
+ return { corrDeltas, csDeltas: targetDeltas, csOverrides, asOverrides, complete };
|
|
|
}
|
|
|
}
|