Skip to content

Commit c26c029

Browse files
committed
Add simulators
1 parent f508c6f commit c26c029

File tree

3 files changed

+495
-0
lines changed

3 files changed

+495
-0
lines changed

docs/defining-workflows/passing-data.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ class MyActivity extends Activity
4747
}
4848
```
4949

50+
import PassingDataSimulator from '@site/src/components/PassingDataSimulator';
51+
52+
<PassingDataSimulator />
53+
5054
In general, you should only pass small amounts of data in this manner. Rather than passing large amounts of data, you should write the data to the database, cache or file system. Then pass the key or file path to the workflow and activities. The activities can then use the key or file path to read the data.
5155

5256
## Models
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import React, { useState, useEffect, useRef } from 'react';
2+
import styles from './styles.module.css';
3+
4+
const ExecutionState = {
5+
IDLE: 'idle',
6+
RUNNING: 'running',
7+
COMPLETED: 'completed',
8+
};
9+
10+
const workflowCode = `use function Workflow\\activity;
11+
use Workflow\\Workflow;
12+
13+
class MyWorkflow extends Workflow
14+
{
15+
public function execute($name)
16+
{
17+
$result = yield activity(MyActivity::class, $name);
18+
19+
return $result;
20+
}
21+
}`;
22+
23+
const activityCode = `use Workflow\\Activity;
24+
25+
class MyActivity extends Activity
26+
{
27+
public function execute($name)
28+
{
29+
return "Hello, {$name}!";
30+
}
31+
}`;
32+
33+
export default function PassingDataSimulator({
34+
title = "Hello World Simulator",
35+
inputValue = "world",
36+
}) {
37+
const [isExpanded, setIsExpanded] = useState(false);
38+
const [executionState, setExecutionState] = useState(ExecutionState.IDLE);
39+
const [currentFile, setCurrentFile] = useState('workflow'); // 'workflow' or 'activity'
40+
const [currentLine, setCurrentLine] = useState(-1);
41+
const [output, setOutput] = useState(null);
42+
const [passedValue, setPassedValue] = useState(null);
43+
const animationRef = useRef(null);
44+
45+
const code = currentFile === 'workflow' ? workflowCode : activityCode;
46+
const codeLines = code.split('\n');
47+
48+
const resetSimulation = () => {
49+
setExecutionState(ExecutionState.IDLE);
50+
setCurrentFile('workflow');
51+
setCurrentLine(-1);
52+
setOutput(null);
53+
setPassedValue(null);
54+
if (animationRef.current) {
55+
clearTimeout(animationRef.current);
56+
}
57+
};
58+
59+
const runSimulation = async () => {
60+
resetSimulation();
61+
setExecutionState(ExecutionState.RUNNING);
62+
63+
// Step 1: Workflow execute() receives $name
64+
setCurrentFile('workflow');
65+
setCurrentLine(6); // public function execute($name)
66+
setPassedValue(inputValue);
67+
await delay(800);
68+
69+
// Step 2: yield activity call
70+
setCurrentLine(8); // $result = yield activity(...)
71+
await delay(1000);
72+
73+
// Step 3: Switch to activity
74+
setCurrentFile('activity');
75+
setCurrentLine(5); // public function execute($name)
76+
await delay(800);
77+
78+
// Step 4: Activity returns
79+
setCurrentLine(7); // return "Hello, {$name}!";
80+
await delay(1000);
81+
82+
// Step 5: Back to workflow
83+
setCurrentFile('workflow');
84+
setCurrentLine(10); // return $result;
85+
await delay(800);
86+
87+
// Complete
88+
setOutput(`Hello, ${inputValue}!`);
89+
setExecutionState(ExecutionState.COMPLETED);
90+
setCurrentLine(-1);
91+
};
92+
93+
const delay = (ms) => new Promise(resolve => {
94+
animationRef.current = setTimeout(resolve, ms);
95+
});
96+
97+
useEffect(() => {
98+
return () => {
99+
if (animationRef.current) {
100+
clearTimeout(animationRef.current);
101+
}
102+
};
103+
}, []);
104+
105+
return (
106+
<div className={styles.simulatorWrapper}>
107+
<button
108+
className={`${styles.expandButton} ${isExpanded ? styles.expanded : ''}`}
109+
onClick={() => setIsExpanded(!isExpanded)}
110+
>
111+
<span className={styles.expandIcon}>{isExpanded ? '▼' : '▶'}</span>
112+
<span>Try it out!</span>
113+
</button>
114+
115+
{isExpanded && (
116+
<div className={styles.simulatorContainer}>
117+
<div className={styles.simulatorHeader}>
118+
<h4 className={styles.simulatorTitle}>{title}</h4>
119+
<div className={styles.controls}>
120+
<button
121+
className={styles.playButton}
122+
onClick={runSimulation}
123+
disabled={executionState === ExecutionState.RUNNING}
124+
>
125+
{executionState === ExecutionState.RUNNING ? '▶️ Running...' : '▶ Play'}
126+
</button>
127+
<button
128+
className={styles.resetButton}
129+
onClick={resetSimulation}
130+
disabled={executionState === ExecutionState.RUNNING}
131+
>
132+
🔄 Reset
133+
</button>
134+
</div>
135+
</div>
136+
137+
<div className={styles.fileTabs}>
138+
<button
139+
className={`${styles.fileTab} ${currentFile === 'workflow' ? styles.active : ''}`}
140+
onClick={() => executionState !== ExecutionState.RUNNING && setCurrentFile('workflow')}
141+
>
142+
MyWorkflow.php
143+
</button>
144+
<button
145+
className={`${styles.fileTab} ${currentFile === 'activity' ? styles.active : ''}`}
146+
onClick={() => executionState !== ExecutionState.RUNNING && setCurrentFile('activity')}
147+
>
148+
MyActivity.php
149+
</button>
150+
</div>
151+
152+
<div className={styles.codeContainer}>
153+
<pre className={styles.codeBlock}>
154+
{codeLines.map((line, index) => {
155+
const lineNumber = index + 1;
156+
const isHighlighted = currentLine === lineNumber;
157+
158+
return (
159+
<div
160+
key={index}
161+
className={`${styles.codeLine} ${isHighlighted ? styles.highlighted : ''}`}
162+
>
163+
<span className={styles.lineNumber}>{lineNumber}</span>
164+
<span className={styles.lineContent}>{line || ' '}</span>
165+
</div>
166+
);
167+
})}
168+
</pre>
169+
</div>
170+
171+
{passedValue !== null && executionState === ExecutionState.RUNNING && (
172+
<div className={styles.dataFlow}>
173+
<span className={styles.dataLabel}>$name =</span>
174+
<code className={styles.dataValue}>'{passedValue}'</code>
175+
</div>
176+
)}
177+
178+
{output !== null && (
179+
<div className={styles.outputSection}>
180+
<span className={styles.outputLabel}>Output:</span>
181+
<code className={styles.outputValue}>'{output}'</code>
182+
</div>
183+
)}
184+
185+
<div className={styles.statusBar}>
186+
<span className={`${styles.statusIndicator} ${styles[executionState]}`}>
187+
{executionState === ExecutionState.IDLE && '⏸️ Ready'}
188+
{executionState === ExecutionState.RUNNING && '▶️ Running'}
189+
{executionState === ExecutionState.COMPLETED && '✅ Completed'}
190+
</span>
191+
{executionState === ExecutionState.RUNNING && (
192+
<span className={styles.currentFileIndicator}>
193+
Executing: <strong>{currentFile === 'workflow' ? 'MyWorkflow' : 'MyActivity'}</strong>
194+
</span>
195+
)}
196+
</div>
197+
</div>
198+
)}
199+
</div>
200+
);
201+
}

0 commit comments

Comments
 (0)