|
@@ -1,9 +1,24 @@
|
|
|
import * as React from "react";
|
|
|
import * as Icons from "@tabler/icons";
|
|
|
-import {Title, Text, Grid, Button, SimpleGrid, Group, Center, ScrollArea, Card, CloseButton, Divider, Space} from "@mantine/core";
|
|
|
+import {
|
|
|
+ Title,
|
|
|
+ Text,
|
|
|
+ Grid,
|
|
|
+ Button,
|
|
|
+ SimpleGrid,
|
|
|
+ Group,
|
|
|
+ Center,
|
|
|
+ ScrollArea,
|
|
|
+ Card,
|
|
|
+ CloseButton,
|
|
|
+ Divider,
|
|
|
+ Space
|
|
|
+} from "@mantine/core";
|
|
|
|
|
|
import {PrimitiveRegistry} from "../onion/primitive_delta";
|
|
|
import {mockUuid} from "../onion/test_helpers";
|
|
|
+import {GrContactInfo} from "react-icons/all";
|
|
|
+import {Actionblock, Resultblock} from "./blocks";
|
|
|
|
|
|
export const demo_PD_description =
|
|
|
<>
|
|
@@ -11,122 +26,189 @@ export const demo_PD_description =
|
|
|
Primitive deltas
|
|
|
</Title>
|
|
|
<Text>
|
|
|
- In this demo, we restrict the view to a - in this case directly manipulatable - state graph, history, and primitive deltas.
|
|
|
+ On the right side you will see three canvases "State", "History", and "Deltas(L0)".
|
|
|
+ </Text>
|
|
|
+ <Text>
|
|
|
+ "State" shows the state graph, which represents the state of our model. If you now e.g. click with the right
|
|
|
+ mouse button on a free area in the canvas, then this action is converted into a "Delta". The set of deltas
|
|
|
+ is then in turn applied to the state grap, resulting in the visualization of a new node.
|
|
|
</Text>
|
|
|
+ <Actionblock>
|
|
|
+ 1. Click with your right mouse button into the "State" canvas!
|
|
|
+ </Actionblock>
|
|
|
+ <Resultblock>
|
|
|
+ A "NEW(0)"-delta occurs in the "Delta(L0)" canvas.
|
|
|
+ </Resultblock>
|
|
|
+ <Resultblock>
|
|
|
+ The "0" corresponds to the ID of the created node.
|
|
|
+ </Resultblock>
|
|
|
+ <Resultblock>
|
|
|
+ A new version appears in the "History" canvas that depends on the previous (initial version).
|
|
|
+ </Resultblock>
|
|
|
+ <Actionblock>
|
|
|
+ 2. Hover with your mouse over the circle with an (i) inside the "State" canvas!
|
|
|
+ </Actionblock>
|
|
|
+ <Resultblock>
|
|
|
+ A tooltip with information about possible interactions of this canvas is shown.
|
|
|
+ </Resultblock>
|
|
|
+ <Actionblock>
|
|
|
+ 3. Create an edge to a value by right-dragging from the created node into the canvas!
|
|
|
+ </Actionblock>
|
|
|
+ <Resultblock>
|
|
|
+ The generated delta "EDG(..)" depends on the creation (NEW(0)) of the node from which the edge originates.
|
|
|
+ </Resultblock>
|
|
|
+ This detailed recording of each action allows to easily detect conflicts between two versions or to merge
|
|
|
+ conflict-free versions.
|
|
|
+ <Actionblock>
|
|
|
+ 4. Press the button "Undo" once!
|
|
|
+ </Actionblock>
|
|
|
+ <Resultblock>
|
|
|
+ The version before creating the edge is selected in the "History" canvas.
|
|
|
+ </Resultblock>
|
|
|
+ <Resultblock>
|
|
|
+ In the "Delta"-view the set of Deltas that are associated with the currently selected version is drawn with
|
|
|
+ a bold border.
|
|
|
+ </Resultblock>
|
|
|
+ <Actionblock>
|
|
|
+ 5. Press the "Clone"-Button and enter a new branch name!
|
|
|
+ </Actionblock>
|
|
|
+ <Actionblock>
|
|
|
+ 6. Delete the node in the cloned branch by a click with the middle mouse button!
|
|
|
+ </Actionblock>
|
|
|
+ <Actionblock>
|
|
|
+ 7. Press the "Redo"-Button in the "master"-branch!
|
|
|
+ </Actionblock>
|
|
|
+ <Actionblock>
|
|
|
+ 8. Press the "Union"-Button in the lower branch!
|
|
|
+ </Actionblock>
|
|
|
+ <Resultblock>
|
|
|
+ While merging the sets of Deltas of both branches, the framework recognizes a deletion-update conflict
|
|
|
+ between the previously created edge and the deletion of the node from which the edge originated (shown by
|
|
|
+ the red line between them).
|
|
|
+ </Resultblock>
|
|
|
+ <Resultblock>
|
|
|
+ In the History, you can now choose between two "Restore" actions, but they cannot be applied simultaneously due to the conflict.
|
|
|
+ </Resultblock>
|
|
|
+
|
|
|
</>;
|
|
|
|
|
|
|
|
|
import {
|
|
|
- newVersionedModel,
|
|
|
- VersionedModelState,
|
|
|
+ newVersionedModel,
|
|
|
+ VersionedModelState,
|
|
|
} from "./versioned_model";
|
|
|
|
|
|
export function getDemoPD() {
|
|
|
- const primitiveRegistry = new PrimitiveRegistry();
|
|
|
- const generateUUID = mockUuid();
|
|
|
-
|
|
|
- const model = newVersionedModel({readonly: false, generateUUID, primitiveRegistry});
|
|
|
-
|
|
|
- const initialState: [string, VersionedModelState, any][] = [
|
|
|
- ["master", model.initialState, model],
|
|
|
- ];
|
|
|
-
|
|
|
- return function() {
|
|
|
- const [globalState, setGlobalState] = React.useState<[string, VersionedModelState, any][]>(initialState);
|
|
|
-
|
|
|
- const getSetBranchState = i => {
|
|
|
- return callback => {
|
|
|
- setGlobalState(prevGlobalState => {
|
|
|
- const copy = prevGlobalState.slice();
|
|
|
- const [branchName, prevBranchState, m] = copy[i];
|
|
|
- copy[i] = [branchName, callback(prevBranchState), m];
|
|
|
- return copy;
|
|
|
- });
|
|
|
- };
|
|
|
- }
|
|
|
-
|
|
|
- return <>{ globalState.map(([branchName, branchState, {getCurrentVersion, getReducer, getReactComponents}], i) => {
|
|
|
- const setBranchState = getSetBranchState(i);
|
|
|
-
|
|
|
- const reducer = getReducer(setBranchState);
|
|
|
-
|
|
|
- const components = getReactComponents(branchState, {
|
|
|
- onUserEdit: reducer.createAndGotoNewVersion,
|
|
|
- onUndoClicked: reducer.undo,
|
|
|
- onRedoClicked: reducer.redo,
|
|
|
- onVersionClicked: reducer.gotoVersion,
|
|
|
- });
|
|
|
+ const primitiveRegistry = new PrimitiveRegistry();
|
|
|
+ const generateUUID = mockUuid();
|
|
|
+
|
|
|
+ const model = newVersionedModel({readonly: false, generateUUID, primitiveRegistry});
|
|
|
+
|
|
|
+ const initialState: [string, VersionedModelState, any][] = [
|
|
|
+ ["master", model.initialState, model],
|
|
|
+ ];
|
|
|
+
|
|
|
+ return function () {
|
|
|
+ const [globalState, setGlobalState] = React.useState<[string, VersionedModelState, any][]>(initialState);
|
|
|
+
|
|
|
+ const getSetBranchState = i => {
|
|
|
+ return callback => {
|
|
|
+ setGlobalState(prevGlobalState => {
|
|
|
+ const copy = prevGlobalState.slice();
|
|
|
+ const [branchName, prevBranchState, m] = copy[i];
|
|
|
+ copy[i] = [branchName, callback(prevBranchState), m];
|
|
|
+ return copy;
|
|
|
+ });
|
|
|
+ };
|
|
|
+ }
|
|
|
|
|
|
- const unionClicked = () => {
|
|
|
- const [mergeWithBranchName, mergeWithBranchState, {getReducer: getOtherReducer}] = globalState[i-1];
|
|
|
- const otherReducer = getOtherReducer(getSetBranchState(i-1));
|
|
|
- const addRecursive = ([version, delta, _]) => {
|
|
|
- if (version.parents.length > 0) {
|
|
|
- addRecursive(version.parents[0])
|
|
|
+ return <>{globalState.map(([branchName, branchState, {
|
|
|
+ getCurrentVersion,
|
|
|
+ getReducer,
|
|
|
+ getReactComponents
|
|
|
+ }], i) => {
|
|
|
+ const setBranchState = getSetBranchState(i);
|
|
|
+
|
|
|
+ const reducer = getReducer(setBranchState);
|
|
|
+
|
|
|
+ const components = getReactComponents(branchState, {
|
|
|
+ onUserEdit: reducer.createAndGotoNewVersion,
|
|
|
+ onUndoClicked: reducer.undo,
|
|
|
+ onRedoClicked: reducer.redo,
|
|
|
+ onVersionClicked: reducer.gotoVersion,
|
|
|
+ });
|
|
|
+
|
|
|
+ const unionClicked = () => {
|
|
|
+ const [mergeWithBranchName, mergeWithBranchState, {getReducer: getOtherReducer}] = globalState[i - 1];
|
|
|
+ const otherReducer = getOtherReducer(getSetBranchState(i - 1));
|
|
|
+ const addRecursive = ([version, delta, _]) => {
|
|
|
+ if (version.parents.length > 0) {
|
|
|
+ addRecursive(version.parents[0])
|
|
|
+ }
|
|
|
+ otherReducer.addDeltasAndVersion(delta.deltas, delta.getDescription(), version.hash);
|
|
|
+ }
|
|
|
+ addRecursive(getCurrentVersion().parents[0]);
|
|
|
+ setGlobalState(prevGlobalState => [
|
|
|
+ ...prevGlobalState.slice(0, i),
|
|
|
+ ...prevGlobalState.slice(i + 1),
|
|
|
+ ]);
|
|
|
}
|
|
|
- otherReducer.addDeltasAndVersion(delta.deltas, delta.getDescription(), version.hash);
|
|
|
- }
|
|
|
- addRecursive(getCurrentVersion().parents[0]);
|
|
|
- setGlobalState(prevGlobalState => [
|
|
|
- ...prevGlobalState.slice(0,i),
|
|
|
- ...prevGlobalState.slice(i+1),
|
|
|
- ]);
|
|
|
- }
|
|
|
|
|
|
- const cloneClicked = () => {
|
|
|
- const newBranchName = prompt("Branch name: (ESC to cancel)", "branch");
|
|
|
- if (newBranchName === null) {
|
|
|
- return;
|
|
|
- }
|
|
|
- if (globalState.some(([existingBranchName]) => existingBranchName === newBranchName)) {
|
|
|
- alert("Branch with this name already exists!");
|
|
|
- return;
|
|
|
- }
|
|
|
- const newModel = newVersionedModel({readonly: false, generateUUID, primitiveRegistry});
|
|
|
- const newBranchState = newModel.initialState;
|
|
|
-
|
|
|
- setGlobalState(prevGlobalState => [
|
|
|
- ...prevGlobalState.slice(0,i+1),
|
|
|
- [newBranchName, newBranchState, newModel],
|
|
|
- ...prevGlobalState.slice(i+1),
|
|
|
- ]);
|
|
|
-
|
|
|
- const setNewBranchState = getSetBranchState(i+1);
|
|
|
-
|
|
|
- const reducer = newModel.getReducer(setNewBranchState);
|
|
|
- const compositeDeltas = [...getCurrentVersion()].reverse();
|
|
|
- compositeDeltas.forEach((c: any) => {
|
|
|
- reducer.createAndGotoNewVersion(c.deltas, c.getDescription());
|
|
|
- });
|
|
|
- }
|
|
|
+ const cloneClicked = () => {
|
|
|
+ const newBranchName = prompt("Branch name: (ESC to cancel)", "branch");
|
|
|
+ if (newBranchName === null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (globalState.some(([existingBranchName]) => existingBranchName === newBranchName)) {
|
|
|
+ alert("Branch with this name already exists!");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const newModel = newVersionedModel({readonly: false, generateUUID, primitiveRegistry});
|
|
|
+ const newBranchState = newModel.initialState;
|
|
|
+
|
|
|
+ setGlobalState(prevGlobalState => [
|
|
|
+ ...prevGlobalState.slice(0, i + 1),
|
|
|
+ [newBranchName, newBranchState, newModel],
|
|
|
+ ...prevGlobalState.slice(i + 1),
|
|
|
+ ]);
|
|
|
+
|
|
|
+ const setNewBranchState = getSetBranchState(i + 1);
|
|
|
+
|
|
|
+ const reducer = newModel.getReducer(setNewBranchState);
|
|
|
+ const compositeDeltas = [...getCurrentVersion()].reverse();
|
|
|
+ compositeDeltas.forEach((c: any) => {
|
|
|
+ reducer.createAndGotoNewVersion(c.deltas, c.getDescription());
|
|
|
+ });
|
|
|
+ }
|
|
|
|
|
|
- return <div key={branchName}>
|
|
|
- <Divider my="sm" label={"branch: "+branchName} labelPosition="center" />
|
|
|
- {/*{<CloseButton/>}*/}
|
|
|
- <SimpleGrid cols={3}>
|
|
|
- <div>
|
|
|
- <Text>State</Text>
|
|
|
- {components.graphStateComponent}
|
|
|
+ return <div key={branchName}>
|
|
|
+ <Divider my="sm" label={"branch: " + branchName} labelPosition="center"/>
|
|
|
+ {/*{<CloseButton/>}*/}
|
|
|
+ <SimpleGrid cols={3}>
|
|
|
+ <div>
|
|
|
+ <Text>State</Text>
|
|
|
+ {components.graphStateComponent}
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <Text>History</Text>
|
|
|
+ {components.historyComponent}
|
|
|
+ <Space h="md"/>
|
|
|
+ <SimpleGrid cols={4} spacing="xs">
|
|
|
+ <Button onClick={unionClicked} compact disabled={i === 0}
|
|
|
+ leftIcon={<Icons.IconChevronUp/>}>Union </Button>
|
|
|
+ {components.undoButton}
|
|
|
+ {components.redoButton}
|
|
|
+ <Button onClick={cloneClicked} compact rightIcon={<Icons.IconChevronDown/>}>Clone</Button>
|
|
|
+ </SimpleGrid>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <Text>Deltas (L0)</Text>
|
|
|
+ {components.depGraphL0Component}
|
|
|
+ </div>
|
|
|
+ </SimpleGrid>
|
|
|
+ <Space h="md"/>
|
|
|
</div>
|
|
|
- <div>
|
|
|
- <Text>History</Text>
|
|
|
- {components.historyComponent}
|
|
|
- <Space h="md"/>
|
|
|
- <SimpleGrid cols={4} spacing="xs">
|
|
|
- <Button onClick={unionClicked} compact disabled={i===0} leftIcon={<Icons.IconChevronUp/>}>Union </Button>
|
|
|
- {components.undoButton}
|
|
|
- {components.redoButton}
|
|
|
- <Button onClick={cloneClicked} compact rightIcon={<Icons.IconChevronDown/>}>Clone</Button>
|
|
|
- </SimpleGrid>
|
|
|
- </div>
|
|
|
- <div>
|
|
|
- <Text>Deltas (L0)</Text>
|
|
|
- {components.depGraphL0Component}
|
|
|
- </div>
|
|
|
- </SimpleGrid>
|
|
|
- <Space h="md"/>
|
|
|
- </div>
|
|
|
- })
|
|
|
- }</>;
|
|
|
- }
|
|
|
+ })
|
|
|
+ }</>;
|
|
|
+ }
|
|
|
}
|