|
@@ -1,22 +1,28 @@
|
|
|
import * as React from 'react';
|
|
|
import * as Icons from '@tabler/icons';
|
|
|
-import {Button, Divider, Group, Image, SimpleGrid, Space, Text, Title, TextInput, Select, Stack} from '@mantine/core';
|
|
|
+import {Button, Divider, Group, Image, SimpleGrid, Space, Text, Title, TextInput, Select, Stack, Paper} from '@mantine/core';
|
|
|
|
|
|
import { Graphviz } from 'graphviz-react';
|
|
|
|
|
|
import {newVersionedModel, undoButtonHelpText, VersionedModelState,} from '../versioned_model/single_model';
|
|
|
+import {InfoHoverCard} from "../info_hover_card";
|
|
|
import {OnionContext} from "../onion_context";
|
|
|
+
|
|
|
import {mockUuid} from "onion/test_helpers";
|
|
|
import {PrimitiveRegistry, PrimitiveDelta} from "onion/primitive_delta";
|
|
|
import {INodeState, IValueState} from "onion/graph_state";
|
|
|
|
|
|
-export const demo_Sem_description = <>
|
|
|
+export const demo_Live_description = <>
|
|
|
<Title order={4}>
|
|
|
- Semantics
|
|
|
+ Live Modeling
|
|
|
</Title>
|
|
|
+ <Text>This demo allows you to edit a Finite State Automaton (FSA) via a projectional editor, and to execute it via an interpreter.
|
|
|
+ </Text>
|
|
|
+ <Text>The model can be edited during its execution (called <em>live modeling</em> in literature). Live modeling can be done collaboratively as well. This works because the abstract syntax and the execution state are just versioned graphs.
|
|
|
+ </Text>
|
|
|
</>;
|
|
|
|
|
|
-export function getDemoSem() {
|
|
|
+export function getDemoLive() {
|
|
|
const primitiveRegistry = new PrimitiveRegistry();
|
|
|
const generateUUID = mockUuid();
|
|
|
|
|
@@ -95,6 +101,7 @@ export function getDemoSem() {
|
|
|
|
|
|
setDotGraph(`
|
|
|
digraph {
|
|
|
+ rankdir="LR";
|
|
|
${states.map(([name])=>name
|
|
|
+ (name === initialStateName ? `[color=blue, style=filled, fontcolor=white]`:``)
|
|
|
+ (name === currentStateName ? `[shape=doublecircle]`:`[shape=circle]`)
|
|
@@ -117,8 +124,8 @@ export function getDemoSem() {
|
|
|
});
|
|
|
|
|
|
const [addStateName, setAddStateName] = React.useState<string>("A");
|
|
|
- const [addTransitionSrc, setAddTransitionSrc] = React.useState<string>("");
|
|
|
- const [addTransitionTgt, setAddTransitionTgt] = React.useState<string>("");
|
|
|
+ const [addTransitionSrc, setAddTransitionSrc] = React.useState<string|null>(null);
|
|
|
+ const [addTransitionTgt, setAddTransitionTgt] = React.useState<string|null>(null);
|
|
|
const [addTransitionEvent, setAddTransitionEvent] = React.useState<string>("e");
|
|
|
|
|
|
function addState() {
|
|
@@ -230,6 +237,19 @@ export function getDemoSem() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ function onExecuteStep(srcName, tgtName, label, transition: INodeState) {
|
|
|
+ if (runtimeModelNode !== null) {
|
|
|
+ designModel.graphState.pushState();
|
|
|
+ runtimeModelNode.getDeltasForSetEdge(primitiveRegistry, "current", getState(tgtName) || null).forEach(d => designModel.graphState.exec(d));
|
|
|
+ const deltas = designModel.graphState.popState();
|
|
|
+ designModelReducer.createAndGotoNewVersion(deltas, "executeStep:"+transitionKey([srcName, tgtName, label]));
|
|
|
+ setCurrentState(tgtName);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const transitionKey = ([srcName, tgtName, label]) =>
|
|
|
+ srcName+'--('+label+')-->'+tgtName;
|
|
|
+
|
|
|
return <>
|
|
|
<OnionContext.Provider value={{generateUUID, primitiveRegistry}}>
|
|
|
<SimpleGrid cols={2}>
|
|
@@ -237,44 +257,67 @@ export function getDemoSem() {
|
|
|
<Group grow>
|
|
|
{designModelComponents.undoRedoButtons}
|
|
|
</Group>
|
|
|
- <Group>
|
|
|
+ <Divider my="xs" label="Design Model Editor" labelPosition="center" />
|
|
|
+ <Group grow>
|
|
|
+ <Paper shadow="xs" p="xs" withBorder>
|
|
|
+ <Group grow>
|
|
|
<TextInput value={addStateName} onChange={e => setAddStateName(e.currentTarget.value)} label="State Name" withAsterisk/>
|
|
|
- <Button onClick={addState} disabled={addStateName===""||states.some(([name]) => name ===addStateName)} leftIcon={<Icons.IconPlus/>}>Add State</Button>
|
|
|
+ <Button onClick={addState} disabled={addStateName===""||states.some(([name]) => name ===addStateName)} leftIcon={<Icons.IconPlus/>} color="green">Add</Button>
|
|
|
</Group>
|
|
|
- <Group>
|
|
|
- <Select searchable clearable label="Source" data={states.map(([stateName]) => ({value:stateName, label:stateName}))} value={addTransitionSrc} onChange={setAddTransitionSrc}/>
|
|
|
- <Select searchable clearable label="Target" data={states.map(([stateName]) => ({value:stateName, label:stateName}))} value={addTransitionTgt} onChange={setAddTransitionTgt}/>
|
|
|
- <TextInput value={addTransitionEvent} onChange={e => setAddTransitionEvent(e.currentTarget.value)} label="Event" />
|
|
|
- <Button disabled={addTransitionSrc === null || addTransitionTgt === null} onClick={addTransition} leftIcon={<Icons.IconPlus/>}>Add Transition</Button>
|
|
|
+ </Paper>
|
|
|
+ {/*<Paper shadow="xs" p="xs" withBorder>*/}
|
|
|
+ <Select searchable clearable label="Initial State" data={states.map(([stateName]) => ({value:stateName, label:stateName}))} value={initialState} onChange={onInitialStateChange}/>
|
|
|
+ {/*</Paper>*/}
|
|
|
</Group>
|
|
|
<Group>
|
|
|
{
|
|
|
states.map(([stateName, stateNodeState]) => {
|
|
|
- return <Button key={stateName} leftIcon={<Icons.IconX/>} onClick={() => onDeleteState(stateName, stateNodeState)}>Delete State {stateName}</Button>
|
|
|
+ return <Button color="red" key={stateName} leftIcon={<Icons.IconX/>} onClick={() => onDeleteState(stateName, stateNodeState)}>State {stateName}</Button>
|
|
|
})
|
|
|
}
|
|
|
</Group>
|
|
|
+ <Paper shadow="xs" p="xs" withBorder>
|
|
|
+ <Group grow>
|
|
|
+ <Select searchable clearable label="Source" data={states.map(([stateName]) => ({value:stateName, label:stateName}))} value={addTransitionSrc} onChange={setAddTransitionSrc}/>
|
|
|
+ <Select searchable clearable label="Target" data={states.map(([stateName]) => ({value:stateName, label:stateName}))} value={addTransitionTgt} onChange={setAddTransitionTgt}/>
|
|
|
+ <TextInput value={addTransitionEvent} onChange={e => setAddTransitionEvent(e.currentTarget.value)} label="Event" />
|
|
|
+ <Button disabled={addTransitionSrc === null || addTransitionTgt === null} onClick={addTransition} leftIcon={<Icons.IconPlus/>} color="green">Add</Button>
|
|
|
+ </Group>
|
|
|
+ </Paper>
|
|
|
<Group>
|
|
|
{
|
|
|
transitions.map(([srcName, tgtName, label, tNodeState]) => {
|
|
|
const key = srcName+'--('+label+')-->'+tgtName;
|
|
|
- return <Button key={key} leftIcon={<Icons.IconX/>} onClick={() => onDeleteState(key, tNodeState)}>Delete Transition {key}</Button>
|
|
|
+ return <Button color="red" key={key} leftIcon={<Icons.IconX/>} onClick={() => onDeleteState(key, tNodeState)}>Transition {key}</Button>
|
|
|
})
|
|
|
}
|
|
|
</Group>
|
|
|
- <Group>
|
|
|
- <Select searchable clearable label="Initial State" data={states.map(([stateName]) => ({value:stateName, label:stateName}))} value={initialState} onChange={onInitialStateChange}/>
|
|
|
- <Button disabled={initialState === null || runtimeModelNode !== null} onClick={onInitialize} leftIcon={<Icons.IconPlayerPlay/>}>Initialize Execution</Button>
|
|
|
- </Group>
|
|
|
- <Group>
|
|
|
- <Select disabled={runtimeModelNode === null} searchable clearable label="Current State" data={states.map(([stateName]) => ({value:stateName, label:stateName}))} value={currentState} onChange={onCurrentStateChange}/>
|
|
|
- <Button disabled={runtimeModelNode === null} onClick={onAbort} leftIcon={<Icons.IconPlayerStop/>}>Abort Execution</Button>
|
|
|
+ <Divider my="xs" label="Run-time Execution" labelPosition="center" />
|
|
|
+ <Group grow>
|
|
|
+ <Button disabled={initialState === null || runtimeModelNode !== null} onClick={onInitialize} leftIcon={<Icons.IconPlayerPlay/>}>Init</Button>
|
|
|
+ <Button color="red" disabled={runtimeModelNode === null} onClick={onAbort} leftIcon={<Icons.IconPlayerStop/>}>Abort</Button>
|
|
|
+ <Select disabled={currentState === null} data={transitions.filter(([srcName]) => srcName === currentState).map(t => {
|
|
|
+ // @ts-ignore:
|
|
|
+ const key = transitionKey(t);
|
|
|
+ return {
|
|
|
+ value: key,
|
|
|
+ label: key,
|
|
|
+ }
|
|
|
+ })} label="Execute Step" onChange={key => {
|
|
|
+ const t = transitions.find(t =>
|
|
|
+ // @ts-ignore:
|
|
|
+ transitionKey(t) == key);
|
|
|
+ onExecuteStep(...t!);
|
|
|
+ }}/>
|
|
|
+ <Select disabled={runtimeModelNode === null} searchable clearable label={<Group>Current State <InfoHoverCard>
|
|
|
+ <Text>Current state updates when executing an execution step, but can also be overridden at any point in time ("god event").</Text>
|
|
|
+ </InfoHoverCard></Group>} data={states.map(([stateName]) => ({value:stateName, label:stateName}))} value={currentState} onChange={onCurrentStateChange}/>
|
|
|
</Group>
|
|
|
<Graphviz dot={dotGraph} options={{fit:false}} className="canvas"/>
|
|
|
<Text>Powered by GraphViz and WebAssembly.</Text>
|
|
|
</Stack>
|
|
|
<Stack>
|
|
|
- {designModelComponents.makeTabs("merge", ["state", "merge", "deltaL1", "deltaL0"])}
|
|
|
+ {designModelComponents.makeTabs("state", ["state", "merge", "deltaL1", "deltaL0"])}
|
|
|
{designModelComponents.makeTabs("deltaL1", ["state", "merge", "deltaL1", "deltaL0"])}
|
|
|
</Stack>
|
|
|
</SimpleGrid>
|