|
@@ -0,0 +1,147 @@
|
|
|
+import * as React from "react";
|
|
|
+import {IconChevronLeft, IconChevronRight} from "@tabler/icons";
|
|
|
+import {SimpleGrid, Group, Stack, Title, Button, Text, Modal, Center} from "@mantine/core";
|
|
|
+
|
|
|
+import {RountangleEditor} from "./rountangleEditor/RountangleEditor";
|
|
|
+import {Version} from "../onion/version";
|
|
|
+import {emptyGraph, graphForces} from "./constants";
|
|
|
+import {d3Types, Graph} from "./graph";
|
|
|
+import {PrimitiveDelta, EdgeCreation, EdgeUpdate, PrimitiveRegistry, NodeCreation} from "../onion/primitive_delta";
|
|
|
+import {GraphType} from "./editable_graph";
|
|
|
+import {UUID} from "../onion/types";
|
|
|
+import {GraphState} from "../onion/graph_state";
|
|
|
+import {D3GraphStateUpdater} from "./d3_state";
|
|
|
+
|
|
|
+export interface ManualRendererProps {
|
|
|
+ asGraph: GraphType;
|
|
|
+ csParentGraph: GraphType;
|
|
|
+ csParentGraphState: GraphState;
|
|
|
+ asDeltasToRender: PrimitiveDelta[];
|
|
|
+
|
|
|
+ done: (csDeltas: PrimitiveDelta[]) => void;
|
|
|
+ cancel: () => void;
|
|
|
+}
|
|
|
+
|
|
|
+// Replay all deltas in version to get graph state (+ D3 graph state)
|
|
|
+function getD3GraphState(version: Version, setGraph) {
|
|
|
+ const graphState = new GraphState();
|
|
|
+ const d3Updater = new D3GraphStateUpdater(setGraph, 0, 0);
|
|
|
+ for (const d of [...version].reverse()) {
|
|
|
+ graphState.exec(d, d3Updater)
|
|
|
+ }
|
|
|
+ return graphState;
|
|
|
+}
|
|
|
+
|
|
|
+export function newManualRenderer({generateUUID, primitiveRegistry}) {
|
|
|
+
|
|
|
+ // Return functional React component
|
|
|
+ function ManualRenderer(props: ManualRendererProps) {
|
|
|
+ const [csGraph, setCsGraph] = React.useState<GraphType>(props.csParentGraph);
|
|
|
+ const [csGraphState] = React.useState<GraphState>(props.csParentGraphState);
|
|
|
+ const [csDeltas, setCsDeltas] = React.useState<PrimitiveDelta[]>([]);
|
|
|
+
|
|
|
+ const [undone, setUndone] = React.useState<PrimitiveDelta[]>([]);
|
|
|
+
|
|
|
+ console.log({asDeltasToRender: props.asDeltasToRender});
|
|
|
+
|
|
|
+ const lines = props.asDeltasToRender.map(d => {
|
|
|
+ if (d instanceof NodeCreation) {
|
|
|
+ return "Newly created AS state " + JSON.stringify(d.id.value) + " needs CS geometry";
|
|
|
+ }
|
|
|
+ else if (d instanceof EdgeCreation) {
|
|
|
+ return "Newly created parent link from " + JSON.stringify(d.source.id.value) + " to " + JSON.stringify((d.target.getTarget() as NodeCreation).id.value) + " needs to be reflected by geometric 'insideness'";
|
|
|
+ }
|
|
|
+ else if (d instanceof EdgeUpdate) {
|
|
|
+ const oldTarget = d.overwrites.target.getTarget();
|
|
|
+ const newTarget = d.target.getTarget();
|
|
|
+ const sourceId = JSON.stringify(d.getCreation().source.id.value);
|
|
|
+ if (oldTarget === null && newTarget !== null) {
|
|
|
+ return "Newly created parent link from " + sourceId + " to " + JSON.stringify((newTarget as NodeCreation).id.value) + " needs to be reflected by geometric 'insideness'";
|
|
|
+ }
|
|
|
+ else if (oldTarget !== null && newTarget === null) {
|
|
|
+ return "Deleted parent link from " + sourceId + " to " + JSON.stringify((oldTarget as NodeCreation).id.value);
|
|
|
+ }
|
|
|
+ else if (oldTarget !== null && newTarget !== null) {
|
|
|
+ return "Updated parent link from source " + sourceId + " to " + JSON.stringify((newTarget as NodeCreation).id.value);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ return <>
|
|
|
+ <SimpleGrid cols={2}>
|
|
|
+ <Stack>
|
|
|
+ <Title order={5}>Abstract Syntax (read-only)</Title>
|
|
|
+ <Graph
|
|
|
+ graph={props.asGraph}
|
|
|
+ forces={graphForces}
|
|
|
+ />
|
|
|
+ </Stack>
|
|
|
+ <Stack>
|
|
|
+ <Title order={5}>Concrete Syntax (please adjust geometries)</Title>
|
|
|
+ <RountangleEditor
|
|
|
+ graph={csGraph}
|
|
|
+ generateUUID={generateUUID}
|
|
|
+ primitiveRegistry={primitiveRegistry}
|
|
|
+ graphState={csGraphState}
|
|
|
+ onUserEdit={(deltas: PrimitiveDelta[], description: string) => {
|
|
|
+ // the user is not allowed to create/remove rountangles, so we only respond to EdgeUpdates
|
|
|
+ const filteredDeltas = deltas.filter(d => d instanceof EdgeUpdate);
|
|
|
+ setCsDeltas(prevCsDeltas => prevCsDeltas.concat(filteredDeltas));
|
|
|
+ setUndone([]);
|
|
|
+ filteredDeltas.forEach(d => csGraphState.exec(d, new D3GraphStateUpdater(setCsGraph, 0, 0)));
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ <Center>
|
|
|
+ <Group>
|
|
|
+ <Button disabled={csDeltas.length === 0} onClick={() => {
|
|
|
+ const csDelta = csDeltas[csDeltas.length-1];
|
|
|
+ setCsDeltas(csDeltas.slice(0,-1));
|
|
|
+ setUndone(undone.concat(csDelta));
|
|
|
+ csGraphState.unexec(csDelta, new D3GraphStateUpdater(setCsGraph, 0, 0));
|
|
|
+ }} compact leftIcon={<IconChevronLeft/>}>Undo</Button>
|
|
|
+ <Button disabled={undone.length === 0} onClick={() => {
|
|
|
+ const csDelta = undone[undone.length-1];
|
|
|
+ setCsDeltas(csDeltas.concat(csDelta));
|
|
|
+ setUndone(undone.slice(0,-1));
|
|
|
+ csGraphState.exec(csDelta, new D3GraphStateUpdater(setCsGraph, 0, 0));
|
|
|
+ }} compact rightIcon={<IconChevronRight/>}>Redo</Button>
|
|
|
+ </Group>
|
|
|
+ </Center>
|
|
|
+ </Stack>
|
|
|
+ </SimpleGrid>
|
|
|
+ <Text>Missing geometry information:</Text>
|
|
|
+ <ul>
|
|
|
+ {lines.map((l, i) => <li key={i}>{l}</li>)}
|
|
|
+ </ul>
|
|
|
+ <Group position="right">
|
|
|
+ <Button variant="subtle" onClick={() => props.cancel()}>Cancel</Button>
|
|
|
+ <Button onClick={() => props.done(csDeltas)}>Done</Button>
|
|
|
+ </Group>
|
|
|
+ </>;
|
|
|
+ }
|
|
|
+
|
|
|
+ const getModalManualRenderer = (manualRendererState: null | ManualRendererProps) => {
|
|
|
+ return <Modal
|
|
|
+ opened={manualRendererState !== null}
|
|
|
+ onClose={() => manualRendererState?.cancel?.()}
|
|
|
+ title=""
|
|
|
+ size={1000}
|
|
|
+ >
|
|
|
+ {manualRendererState === null ? <></> :
|
|
|
+ <ManualRenderer
|
|
|
+ asGraph={manualRendererState.asGraph}
|
|
|
+ csParentGraph={manualRendererState.csParentGraph}
|
|
|
+ csParentGraphState={manualRendererState.csParentGraphState}
|
|
|
+ asDeltasToRender={manualRendererState.asDeltasToRender}
|
|
|
+ done={manualRendererState.done}
|
|
|
+ cancel={manualRendererState.cancel}
|
|
|
+ />}
|
|
|
+ </Modal>;
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ ManualRenderer,
|
|
|
+ getModalManualRenderer,
|
|
|
+ }
|
|
|
+}
|
|
|
+
|