|
@@ -0,0 +1,188 @@
|
|
|
+import * as React from "react";
|
|
|
+import {SimpleGrid, Text, Title, Stack, Center, Group, Space, Image, Button} from "@mantine/core";
|
|
|
+import {IconTrash, IconRowInsertTop, IconRowInsertBottom} from '@tabler/icons';
|
|
|
+
|
|
|
+import {PrimitiveRegistry, PrimitiveDelta} from "onion/primitive_delta";
|
|
|
+import {mockUuid} from "onion/test_helpers";
|
|
|
+import {PrimitiveValue} from "onion/types";
|
|
|
+import {INodeState, IValueState} from "onion/graph_state";
|
|
|
+
|
|
|
+import {newVersionedModel, VersionedModelState} from "../versioned_model/single_model";
|
|
|
+import {newManualRenderer, ManualRendererProps} from "../versioned_model/manual_renderer";
|
|
|
+import {InfoHoverCard} from "../info_hover_card";
|
|
|
+import {OnionContext} from "../onion_context";
|
|
|
+import {useConst} from "../use_const";
|
|
|
+import {Actionblock, Resultblock} from "./blocks";
|
|
|
+import editor from './assets/editor.svg';
|
|
|
+
|
|
|
+export function getDemoLE() {
|
|
|
+ const cs = newVersionedModel({readonly: true});
|
|
|
+
|
|
|
+ let nextVal = 42;
|
|
|
+
|
|
|
+ // returns functional react component
|
|
|
+ return function () {
|
|
|
+ console.log("Rendering DemoLE");
|
|
|
+
|
|
|
+ const onionContext = React.useContext(OnionContext);
|
|
|
+ const [csState, setCsState] = React.useState<VersionedModelState>(cs.initialState);
|
|
|
+ const csReducer = cs.getReducer(setCsState);
|
|
|
+
|
|
|
+ const listNodeId = useConst(() => {
|
|
|
+ // The node representing the list. It is also always the last item in the list ("nil").
|
|
|
+ const listNode = onionContext.primitiveRegistry.newNodeCreation(onionContext.generateUUID());
|
|
|
+ // Has a 'head' edge, pointing to the first element in the list, initially itself (indicating the list is empty).
|
|
|
+ const v = csReducer.createAndGotoNewVersion([
|
|
|
+ listNode,
|
|
|
+ onionContext.primitiveRegistry.newEdgeCreation(listNode, "next", listNode),
|
|
|
+ onionContext.primitiveRegistry.newEdgeCreation(listNode, "prev", listNode),
|
|
|
+ ], "createList", cs.initialState.version);
|
|
|
+ return listNode.id.value;
|
|
|
+ });
|
|
|
+
|
|
|
+ const listNode = cs.graphState.nodes.get(listNodeId) as INodeState;
|
|
|
+
|
|
|
+ // Inserts list item after prevNode and before nextNode.
|
|
|
+ function insertBetween(prevNode: INodeState, nextNode: INodeState, val: PrimitiveValue) {
|
|
|
+ if (prevNode.getOutgoingEdges().get("next") !== nextNode
|
|
|
+ || nextNode.getOutgoingEdges().get("prev") !== prevNode) {
|
|
|
+ throw new Error("Assertion failed");
|
|
|
+ }
|
|
|
+
|
|
|
+ const deltas: PrimitiveDelta[] = [];
|
|
|
+ const toUnexec: (()=>void)[] = [];
|
|
|
+ function addDelta(d: PrimitiveDelta) {
|
|
|
+ deltas.push(d);
|
|
|
+ cs.graphState.exec(d);
|
|
|
+ toUnexec.push(() => cs.graphState.unexec(d));
|
|
|
+ }
|
|
|
+
|
|
|
+ const newItemCreation = onionContext.primitiveRegistry.newNodeCreation(onionContext.generateUUID());
|
|
|
+ addDelta(newItemCreation);
|
|
|
+ const updateNext = prevNode.getDeltasForSetEdge(onionContext.primitiveRegistry, "next", newItemCreation);
|
|
|
+ updateNext.forEach(d => addDelta(d));
|
|
|
+ const updatePrev = nextNode.getDeltasForSetEdge(onionContext.primitiveRegistry, "prev", newItemCreation);
|
|
|
+ updatePrev.forEach(d => addDelta(d));
|
|
|
+
|
|
|
+ addDelta(onionContext.primitiveRegistry.newEdgeCreation(newItemCreation, "value", val));
|
|
|
+ addDelta(onionContext.primitiveRegistry.newEdgeCreation(newItemCreation, "prev", prevNode.creation));
|
|
|
+ addDelta(onionContext.primitiveRegistry.newEdgeCreation(newItemCreation, "next", nextNode.creation));
|
|
|
+
|
|
|
+ // revert graphstate
|
|
|
+ toUnexec.reduceRight((_,d) => {d(); return null;}, null);
|
|
|
+
|
|
|
+ csReducer.createAndGotoNewVersion(deltas, "insert"+JSON.stringify(val));
|
|
|
+ }
|
|
|
+
|
|
|
+ function insertBefore(node: INodeState, val: PrimitiveValue) {
|
|
|
+ const prevNode = node.getOutgoingEdges().get("prev") as INodeState;
|
|
|
+ return insertBetween(prevNode, node, val);
|
|
|
+ }
|
|
|
+
|
|
|
+ function insertAfter(node: INodeState, val: PrimitiveValue) {
|
|
|
+ const nextNode = node.getOutgoingEdges().get("next") as INodeState;
|
|
|
+ return insertBetween(node, nextNode, val);
|
|
|
+ }
|
|
|
+
|
|
|
+ function deleteItem(node: INodeState) {
|
|
|
+ const prevNode = node.getOutgoingEdges().get("prev") as INodeState;
|
|
|
+ const nextNode = node.getOutgoingEdges().get("next") as INodeState;
|
|
|
+
|
|
|
+ const deltas: PrimitiveDelta[] = [];
|
|
|
+ const toUnexec: (()=>void)[] = [];
|
|
|
+ function addDelta(d: PrimitiveDelta) {
|
|
|
+ deltas.push(d);
|
|
|
+ cs.graphState.exec(d);
|
|
|
+ toUnexec.push(() => cs.graphState.unexec(d));
|
|
|
+ }
|
|
|
+
|
|
|
+ prevNode.getDeltasForSetEdge(onionContext.primitiveRegistry, "next", nextNode.creation).forEach(addDelta);
|
|
|
+ nextNode.getDeltasForSetEdge(onionContext.primitiveRegistry, "prev", prevNode.creation).forEach(addDelta);
|
|
|
+ node.getDeltasForDelete(onionContext.primitiveRegistry).forEach(addDelta);
|
|
|
+
|
|
|
+ toUnexec.reduceRight((_,d)=>{d(); return null;}, null);
|
|
|
+
|
|
|
+ csReducer.createAndGotoNewVersion(deltas, "deleteItem");
|
|
|
+ }
|
|
|
+
|
|
|
+ const csComponents = cs.getReactComponents(csState, {
|
|
|
+ onUserEdit: (deltas, description) => {
|
|
|
+ const newVersion = csReducer.createAndGotoNewVersion(deltas, description);
|
|
|
+ },
|
|
|
+ onUndoClicked: csReducer.undo,
|
|
|
+ onRedoClicked: csReducer.redo,
|
|
|
+ onVersionClicked: csReducer.gotoVersion,
|
|
|
+ });
|
|
|
+
|
|
|
+ const deltaTabs = ["dependencyL1", "dependencyL0", "history"];
|
|
|
+
|
|
|
+ const undoButtonHelpText = "Use the Undo/Redo buttons or the History panel to navigate to any version.";
|
|
|
+
|
|
|
+ function renderList(head: INodeState, stopAt: INodeState) {
|
|
|
+ const value = head.getOutgoingEdges().get("value");
|
|
|
+ const itemText = value === undefined ? "Start of list" : (value as IValueState).value;
|
|
|
+ const nextItem = head.getOutgoingEdges().get("next") as INodeState;
|
|
|
+ return <>
|
|
|
+ <div style={{border:"solid", borderWidth:"1px"}}>
|
|
|
+ {itemText}
|
|
|
+ <Button onClick={() => insertBefore(head, nextVal++)} compact leftIcon={<IconRowInsertBottom/>}>Insert before</Button>
|
|
|
+ <Button onClick={() => insertAfter(head, nextVal++)} compact leftIcon={<IconRowInsertTop/>}>Insert after</Button>
|
|
|
+ {value === undefined ? <></> : <Button compact onClick={() => deleteItem(head)} leftIcon={<IconTrash />}>Delete</Button>}
|
|
|
+ </div>
|
|
|
+ {nextItem === stopAt ? <></> : renderList(nextItem, stopAt)}
|
|
|
+ </>;
|
|
|
+ }
|
|
|
+
|
|
|
+ return <div style={{minWidth: 1300}}>
|
|
|
+ <SimpleGrid cols={3}>
|
|
|
+ <div>
|
|
|
+ <Group position="apart">
|
|
|
+ <Title order={4}>List Editor</Title>
|
|
|
+ </Group>
|
|
|
+ <Space h="48px"/>
|
|
|
+ <Stack>
|
|
|
+ {
|
|
|
+ listNode === undefined ? <>There exists no list (did you undo the list creation?)</>
|
|
|
+ : <>
|
|
|
+ <Stack>
|
|
|
+ {renderList(listNode, listNode)}
|
|
|
+ </Stack>
|
|
|
+ </>
|
|
|
+ }
|
|
|
+ <Center>
|
|
|
+ {csComponents.undoRedoButtons}
|
|
|
+ <Space w="sm"/>
|
|
|
+ <InfoHoverCard>
|
|
|
+ {undoButtonHelpText}
|
|
|
+ </InfoHoverCard>
|
|
|
+ </Center>
|
|
|
+ </Stack>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <Group position="center">
|
|
|
+ <Title order={4}>Deltas</Title>
|
|
|
+ </Group>
|
|
|
+ <Space h="sm"/>
|
|
|
+ {csComponents.makeTabs("dependencyL1", deltaTabs)}
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <Title order={4}>State Graph (read-only)</Title>
|
|
|
+ <Space h="48px"/>
|
|
|
+ {csComponents.graphStateComponent}
|
|
|
+ Nodes:
|
|
|
+ <ul>
|
|
|
+ {csState.graph.nodes.map(n => {
|
|
|
+ return <li key={n.id}>{n.id}</li>;
|
|
|
+ })}
|
|
|
+ </ul>
|
|
|
+ Edges:
|
|
|
+ <ul>
|
|
|
+ {csState.graph.links.map(e => {
|
|
|
+ return <li key={e.source.id+"-"+e.label+"->"+e.target.id}>{e.source.id} ---{e.label}--> {e.target.id}</li>
|
|
|
+ })}
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+ </SimpleGrid>
|
|
|
+ </div>;
|
|
|
+ }
|
|
|
+}
|