graph-explorer
Version:
Graph Explorer can be used to explore and RDF graphs in SPARQL endpoints or on the web.
140 lines (126 loc) • 4.18 kB
text/typescript
import { ElementModel, ElementIri, LinkModel, sameLink } from "../data/model";
import { Element, Link, FatLinkType } from "./elements";
import { Vector, isPolylineEqual } from "./geometry";
import { Command } from "./history";
import { DiagramModel } from "./model";
export class RestoreGeometry implements Command {
readonly title = "Move elements and links";
constructor(
private elementState: readonly { element: Element; position: Vector }[],
private linkState: readonly {
link: Link;
vertices: readonly Vector[];
}[]
) {}
static capture(model: DiagramModel) {
return RestoreGeometry.captureElementsAndLinks(model.elements, model.links);
}
private static captureElementsAndLinks(
elements: readonly Element[],
links: readonly Link[]
) {
return new RestoreGeometry(
elements.map((element) => ({ element, position: element.position })),
links.map((link) => ({ link, vertices: link.vertices }))
);
}
hasChanges() {
return this.elementState.length > 0 || this.linkState.length > 0;
}
filterOutUnchanged(): RestoreGeometry {
return new RestoreGeometry(
this.elementState.filter(
({ element, position }) => !Vector.equals(element.position, position)
),
this.linkState.filter(
({ link, vertices }) => !isPolylineEqual(link.vertices, vertices)
)
);
}
invoke(): RestoreGeometry {
const previous = RestoreGeometry.captureElementsAndLinks(
this.elementState.map((state) => state.element),
this.linkState.map((state) => state.link)
);
// restore in reverse order to workaround position changed event
// handling in EmbeddedLayer inside nested elements
// (child's position change causes group to resize or move itself)
for (const { element, position } of [...this.elementState].reverse()) {
element.setPosition(position);
}
for (const { link, vertices } of this.linkState) {
link.setVertices(vertices);
}
return previous;
}
}
export function restoreCapturedLinkGeometry(link: Link): Command {
const vertices = link.vertices;
return Command.create("Change link vertices", () => {
const capturedInverse = restoreCapturedLinkGeometry(link);
link.setVertices(vertices);
return capturedInverse;
});
}
export function setElementExpanded(
element: Element,
expanded: boolean
): Command {
const title = expanded ? "Expand element" : "Collapse element";
return Command.create(title, () => {
element.setExpanded(expanded);
return setElementExpanded(element, !expanded);
});
}
export function changeLinkTypeVisibility(params: {
linkType: FatLinkType;
visible: boolean;
showLabel: boolean;
preventLoading?: boolean;
}): Command {
const { linkType, visible, showLabel, preventLoading } = params;
return Command.create("Change link type visibility", () => {
const previousVisible = linkType.visible;
const previousShowLabel = linkType.showLabel;
linkType.setVisibility({ visible, showLabel, preventLoading });
return changeLinkTypeVisibility({
linkType,
visible: previousVisible,
showLabel: previousShowLabel,
preventLoading,
});
});
}
export function setElementData(
model: DiagramModel,
target: ElementIri,
data: ElementModel
): Command {
const elements = model.elements.filter((el) => el.iri === target);
const previous = elements.length > 0 ? elements[0].data : undefined;
return Command.create("Set element data", () => {
for (const element of model.elements.filter((el) => el.iri === target)) {
element.setData(data);
}
return setElementData(model, data.id, previous);
});
}
export function setLinkData(
model: DiagramModel,
oldData: LinkModel,
newData: LinkModel
): Command {
if (!sameLink(oldData, newData)) {
throw new Error(
"Cannot change typeId, sourceId or targetId when changing link data"
);
}
return Command.create("Set link data", () => {
for (const link of model.links) {
if (sameLink(link.data, oldData)) {
link.setData(newData);
}
}
return setLinkData(model, newData, oldData);
});
}