瀏覽代碼

Forgot to commit file + update manual renderer UI

Joeri Exelmans 2 年之前
父節點
當前提交
feb6e47316
共有 3 個文件被更改,包括 179 次插入28 次删除
  1. 30 26
      src/frontend/correspondence.tsx
  2. 147 0
      src/frontend/manual_renderer.tsx
  3. 2 2
      src/frontend/versioned_model.tsx

+ 30 - 26
src/frontend/correspondence.tsx

@@ -117,25 +117,22 @@ export function newCorrespondence({generateUUID, primitiveRegistry, cs, as}) {
         renderMap.set(asVersion, corrVersion);
       }
     };
-    const renderExistingVersion = (asVersion: Version, manualRenderCallback) => {
-      for (const [asParentVersion, compositeDelta] of asVersion.parents) {
+    const renderExistingVersion = async (asVersion: Version, setManualRendererState) => {
+      const [asParentVersion, compositeDelta] = asVersion.parents[0];
 
-        const asDeltas = (compositeDelta as CompositeDelta).deltas;
-        const description = compositeDelta.getDescription();
-
-        if (!renderMap.has(asParentVersion)) {
-          renderExistingVersion(asParentVersion, manualRenderCallback);
-        }
+      const asDeltas = (compositeDelta as CompositeDelta).deltas;
+      const description = compositeDelta.getDescription();
 
+      const render = () => {
         const corrParentVersion = renderMap.get(asParentVersion);
         if (corrParentVersion === undefined) {
           console.log("Cannot parse - no parent CORR version");
-          continue;
+          return;
         }
         const csParentVersion = corrMap.get(corrParentVersion)?.csVersion;
         if (csParentVersion === undefined) {
           console.log("Cannot parse - no parent CS version");
-          continue;
+          return;
         }
 
         const [csGS, corrGS, asGS] = [csParentVersion, corrParentVersion, asParentVersion].map(v => getGraphState(v));
@@ -154,6 +151,7 @@ export function newCorrespondence({generateUUID, primitiveRegistry, cs, as}) {
 
         if (complete) {
           finishRender({corrDeltas, csDeltas});
+          return Promise.resolve();
         }
         else {
           function getD3State(version: Version, additionalDeltas: PrimitiveDelta[] = []) {
@@ -170,13 +168,31 @@ export function newCorrespondence({generateUUID, primitiveRegistry, cs, as}) {
           const {graph: asGraph} = getD3State(asVersion);
           const {graph: csParentGraph, graphState: csParentGraphState} = getD3State(csParentVersion, csDeltas);
 
-          manualRenderCallback({asGraph, csParentGraph, csParentGraphState, asDeltasToRender: asDeltas})
-          .then(additionalCsDeltas => {
+          return new Promise((resolve: (csDeltas: PrimitiveDelta[]) => void, reject: () => void) => {
+            setManualRendererState({
+              asGraph,
+              csParentGraph,
+              csParentGraphState,
+              asDeltasToRender: asDeltas,
+              done: resolve,
+              cancel: reject,
+            });
+          })
+          .then((additionalCsDeltas: PrimitiveDelta[]) => {
             finishRender({corrDeltas, csDeltas: csDeltas.concat(additionalCsDeltas)});
           })
-          .catch();
+          .catch()
+          .finally(() => setManualRendererState(null));
         }
       }
+
+      if (!renderMap.has(asParentVersion)) {
+        return renderExistingVersion(asParentVersion, setManualRendererState)
+        .then(render);
+      }
+      else {
+        return render();
+      }
     };
     const gotoVersion = (corrVersion: Version) => {
       const {csVersion, asVersion} = corrMap.get(corrVersion)!;
@@ -184,18 +200,6 @@ export function newCorrespondence({generateUUID, primitiveRegistry, cs, as}) {
       gotoVersionOrig(corrVersion);
       asReducer.gotoVersion(asVersion);
     };
-    const getManualRenderCallback = (setManualRendererState) => {
-      return props => {
-        return new Promise((resolve, reject) => {
-          setManualRendererState({
-            ...props,
-            resolve,
-            reject,
-          });
-        })
-        .finally(() => setManualRendererState(null));
-      }
-    }
     const getParseButton = (csVersion, dir: "left"|"right" = "right") => {
       return <Mantine.Button compact
           disabled={parseMap.has(csVersion)}
@@ -207,7 +211,7 @@ export function newCorrespondence({generateUUID, primitiveRegistry, cs, as}) {
     const getRenderButton = (asVersion, setManualRendererState, dir: "left"|"right" = "left") => {
       return <Mantine.Button compact
           disabled={renderMap.has(asVersion)}
-          onClick={() => renderExistingVersion(asVersion, getManualRenderCallback(setManualRendererState))}
+          onClick={() => renderExistingVersion(asVersion, setManualRendererState)}
           rightIcon={dir === "right" ? <Icons.IconChevronsRight/>: null}
           leftIcon={dir === "left" ? <Icons.IconChevronsLeft/>: null}
         >Render</Mantine.Button>;

+ 147 - 0
src/frontend/manual_renderer.tsx

@@ -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,
+  }
+}
+

+ 2 - 2
src/frontend/versioned_model.tsx

@@ -263,7 +263,7 @@ export function newVersionedModel({generateUUID, primitiveRegistry, readonly}) {
     const stackedUndoButtons = state.version.parents.map(([parentVersion,deltaToUndo]) => {
       return (
         <div key={fullVersionId(parentVersion)}>
-          <Mantine.Button fullWidth={true} compact={true} leftIcon={<Icons.IconPlayerTrackPrev size={18}/>} onClick={callbacks.onUndoClicked?.bind(null, parentVersion, deltaToUndo)}>
+          <Mantine.Button fullWidth={true} compact={true} leftIcon={<Icons.IconChevronLeft size={18}/>} onClick={callbacks.onUndoClicked?.bind(null, parentVersion, deltaToUndo)}>
             UNDO {deltaToUndo.getDescription()}
           </Mantine.Button>
           <Mantine.Space h="xs"/>
@@ -273,7 +273,7 @@ export function newVersionedModel({generateUUID, primitiveRegistry, readonly}) {
     const stackedRedoButtons = state.version.children.map(([childVersion,deltaToRedo]) => {
       return (
         <div key={fullVersionId(childVersion)}>
-          <Mantine.Button style={{width: "100%"}} compact={true} rightIcon={<Icons.IconPlayerTrackNext size={18}/>} onClick={callbacks.onRedoClicked?.bind(null, childVersion, deltaToRedo)}>
+          <Mantine.Button style={{width: "100%"}} compact={true} rightIcon={<Icons.IconChevronRight size={18}/>} onClick={callbacks.onRedoClicked?.bind(null, childVersion, deltaToRedo)}>
             REDO {deltaToRedo.getDescription()}
           </Mantine.Button>
           <Mantine.Space h="xs"/>