Skip to content

Commit 350a5df

Browse files
committed
feat: custom code editor; use example data; copy result
chore: extract business logic to CamundaContext chore: add `sass-loader`
1 parent 05011fb commit 350a5df

24 files changed

+549
-369
lines changed

demo/App.jsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ function App() {
2020
const modelerRef = useRef(null);
2121

2222
const [ modeler, setModeler ] = useState(null);
23-
const [ tab, setTab ] = useState('debugger');
23+
const [ tab, setTab ] = useState('test');
2424

2525
useEffect(() => {
2626
if (modelerRef.current) {
@@ -65,20 +65,20 @@ function App() {
6565
<div className={ `bottom-panel_tabs-item ${tab === 'problems' ? 'active' : ''}` } onClick={ () => setTab('problems') }>
6666
Problems
6767
</div>
68-
<div className={ `bottom-panel_tabs-item ${tab === 'debugger' ? 'active' : ''}` } onClick={ () => setTab('debugger') }>
69-
Debugger
68+
<div className={ `bottom-panel_tabs-item ${tab === 'test' ? 'active' : ''}` } onClick={ () => setTab('test') }>
69+
Task testing
7070
</div>
7171
</div>
7272
<div className="bottom-panel_tabs-content">
7373
{tab === 'problems' && <ProblemsTab />}
74-
{tab === 'debugger' && <DebuggerTab { ...props } />}
74+
{tab === 'test' && <TestTab { ...props } />}
7575
</div>
7676
</div>
7777
</>
7878
);
7979
}
8080

81-
function DebuggerTab(props) {
81+
function TestTab(props) {
8282
const { injector } = props;
8383

8484
if (!injector) {
@@ -89,8 +89,7 @@ function DebuggerTab(props) {
8989
}
9090

9191
function ProblemsTab() {
92-
return <div style={ { padding: '10px' } }>I got 99 problems but debugging ain&apos;t one.</div>;
92+
return <div style={ { padding: '10px' } }>I got 99 problems but running a single task ain&apos;t one.</div>;
9393
}
9494

95-
9695
export default App;

demo/diagram.xml

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,28 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_1rrr52f" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.35.0" modeler:executionPlatform="Camunda Cloud" modeler:executionPlatformVersion="8.8.0">
2+
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:zeebe="http://camunda.org/schema/zeebe/1.0" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_1rrr52f" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.35.0" modeler:executionPlatform="Camunda Cloud" modeler:executionPlatformVersion="8.8.0">
33
<bpmn:process id="Process_1kuphyf" isExecutable="true">
44
<bpmn:startEvent id="StartEvent_1">
55
<bpmn:outgoing>Flow_1xr5ql2</bpmn:outgoing>
66
</bpmn:startEvent>
7-
<bpmn:task id="Task_1">
7+
<bpmn:task id="Task_1" name="Task_1">
8+
<bpmn:extensionElements>
9+
<zeebe:properties>
10+
<zeebe:property name="camundaModeler:exampleOutputJson" value="{&#10; &#34;response&#34;: { &#10; &#34;body&#34;: {&#10; &#34;var_1&#34;: 123,&#10; &#34;var_2&#34;: true&#10; } &#10; }&#10;}" />
11+
</zeebe:properties>
12+
</bpmn:extensionElements>
813
<bpmn:incoming>Flow_1xr5ql2</bpmn:incoming>
14+
<bpmn:outgoing>Flow_1c72h11</bpmn:outgoing>
915
</bpmn:task>
1016
<bpmn:sequenceFlow id="Flow_1xr5ql2" sourceRef="StartEvent_1" targetRef="Task_1" />
17+
<bpmn:task id="Task_2" name="Task_2">
18+
<bpmn:extensionElements>
19+
<zeebe:properties>
20+
<zeebe:property name="camundaModeler:exampleOutputJson" value="{&#10; &#34;response&#34;: { &#10; &#34;body&#34;: {&#10; &#34;var_3&#34;: 356,&#10; &#34;var_4&#34;: &#34;foo&#34;&#10; } &#10; }&#10;}" />
21+
</zeebe:properties>
22+
</bpmn:extensionElements>
23+
<bpmn:incoming>Flow_1c72h11</bpmn:incoming>
24+
</bpmn:task>
25+
<bpmn:sequenceFlow id="Flow_1c72h11" sourceRef="Task_1" targetRef="Task_2" />
1126
</bpmn:process>
1227
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
1328
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1kuphyf">
@@ -16,11 +31,20 @@
1631
</bpmndi:BPMNShape>
1732
<bpmndi:BPMNShape id="Task_1_di" bpmnElement="Task_1">
1833
<dc:Bounds x="270" y="80" width="100" height="80" />
34+
<bpmndi:BPMNLabel />
35+
</bpmndi:BPMNShape>
36+
<bpmndi:BPMNShape id="Activity_008py6b_di" bpmnElement="Task_2">
37+
<dc:Bounds x="430" y="80" width="100" height="80" />
38+
<bpmndi:BPMNLabel />
1939
</bpmndi:BPMNShape>
2040
<bpmndi:BPMNEdge id="Flow_1xr5ql2_di" bpmnElement="Flow_1xr5ql2">
2141
<di:waypoint x="218" y="120" />
2242
<di:waypoint x="270" y="120" />
2343
</bpmndi:BPMNEdge>
44+
<bpmndi:BPMNEdge id="Flow_1c72h11_di" bpmnElement="Flow_1c72h11">
45+
<di:waypoint x="370" y="120" />
46+
<di:waypoint x="430" y="120" />
47+
</bpmndi:BPMNEdge>
2448
</bpmndi:BPMNPlane>
2549
</bpmndi:BPMNDiagram>
2650
</bpmn:definitions>

karma.config.cjs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,12 @@ module.exports = function(config) {
2525
use: 'babel-loader'
2626
},
2727
{
28-
test: /\.css$/,
29-
use: [ 'style-loader', 'css-loader' ]
28+
test: /\.scss$/,
29+
use: [
30+
'style-loader',
31+
'css-loader',
32+
'sass-loader'
33+
]
3034
}
3135
]
3236
},
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import React, { useEffect, useRef } from 'react';
2+
3+
import { basicSetup } from 'codemirror';
4+
import { EditorState } from '@codemirror/state';
5+
import { EditorView, placeholder } from '@codemirror/view';
6+
import { linter } from '@codemirror/lint';
7+
import { json, jsonParseLinter } from '@codemirror/lang-json';
8+
9+
import './CodeEditor.scss';
10+
11+
export default function CodeEditor({
12+
value = '{\n}',
13+
readOnly = false,
14+
linting = true,
15+
placeholder: placeholderText,
16+
onChange = () => {},
17+
onErrorChange = () => {},
18+
}) {
19+
20+
const editorRef = useRef(null);
21+
const viewRef = useRef(null);
22+
23+
useEffect(() => {
24+
if (!editorRef.current) return;
25+
26+
const startState = EditorState.create({
27+
doc: value,
28+
extensions: [
29+
basicSetup,
30+
json(),
31+
linting ? linter(jsonLinter()) : [],
32+
placeholderText ? placeholder(placeholderText) : [],
33+
EditorView.updateListener.of((update) => {
34+
if (update.docChanged) {
35+
onChange(update.state.doc.toString());
36+
}
37+
}),
38+
EditorState.readOnly.of(readOnly)
39+
],
40+
});
41+
42+
const view = new EditorView({
43+
state: startState,
44+
parent: editorRef.current,
45+
});
46+
47+
viewRef.current = view;
48+
49+
return () => {
50+
view.destroy();
51+
};
52+
}, []);
53+
54+
useEffect(() => {
55+
if (!viewRef.current) {
56+
return;
57+
}
58+
59+
const currentDoc = viewRef.current.state.doc.toString();
60+
if (currentDoc === value) {
61+
return;
62+
}
63+
64+
viewRef.current.dispatch({
65+
changes: {
66+
from: 0,
67+
to: currentDoc.length,
68+
insert: value
69+
}
70+
});
71+
72+
// Scroll to the bottom
73+
const docLength = viewRef.current.state.doc.length;
74+
viewRef.current.dispatch({
75+
effects: EditorView.scrollIntoView(docLength, { y: 'end' })
76+
});
77+
}, [ value ]);
78+
79+
const jsonLinter = () => {
80+
return (view) => {
81+
const errors = jsonParseLinter()(view);
82+
onErrorChange(!!errors.length);
83+
return errors;
84+
};
85+
};
86+
87+
return <div ref={ editorRef } className="code-editor_container" />;
88+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
.code-editor_container {
2+
width: 100%;
3+
height: 100%;
4+
}
5+
6+
.code-editor_container .cm-editor {
7+
height: 100%;
8+
}
9+
10+
/* .code-editor_container .cm-content {
11+
background: hsla(0, 0%, 96%, 1);
12+
}
13+
14+
.code-editor_container .cm-gutter {
15+
background: #E6E5E5;
16+
} */
17+
18+
.code-editor_container .cm-focused {
19+
outline: none;
20+
}
21+
22+
.code-editor_container .cm-tooltip {
23+
font-size: 12px;
24+
}

lib/components/Codemirror/Codemirror.css

Lines changed: 0 additions & 27 deletions
This file was deleted.

lib/components/Codemirror/Codemirror.jsx

Lines changed: 0 additions & 25 deletions
This file was deleted.

lib/components/Output/Output.css

Lines changed: 0 additions & 3 deletions
This file was deleted.

lib/components/Output/Output.jsx

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,68 @@
1-
import React, { useMemo } from 'react';
1+
import React, { useMemo, useState } from 'react';
22

3-
import CodeMirror from '../Codemirror/Codemirror';
4-
import { json } from '@codemirror/lang-json';
3+
import { Button } from '@carbon/react';
54

6-
import './Output.css';
5+
import CodeEditor from '../CodeEditor/CodeEditor';
76

8-
export default function Output({ log }) {
7+
import useCamundaContext from '../../hooks/useCamundaContext';
8+
9+
export default function Output() {
10+
11+
const [ copyResultText, setCopyResultText ] = useState('Copy result');
12+
13+
const { log } = useCamundaContext();
914

1015
const value = useMemo(() => {
1116
if (!log.length) {
1217
return '';
1318
}
1419

15-
return log.map((entry) => {
16-
return `${entry.elementId}: ${entry.message}`;
20+
return log.map(({ elementId, message, type }) => {
21+
if (type === 'info') {
22+
return `${elementId}: ${message}`;
23+
} else {
24+
return message;
25+
}
1726
}).join('\n');
1827
}, [ log ]);
1928

29+
const copyResult = () => {
30+
const latestResult = [ ...log ].reverse().find(item => item.type === 'result');
31+
if (latestResult) {
32+
navigator.clipboard.writeText(latestResult.message);
33+
setCopyResultText('Copied!');
34+
setTimeout(() => {
35+
setCopyResultText('Copy result');
36+
}, 2000);
37+
}
38+
};
39+
2040
return (
21-
<div className="section output">
22-
<div className="output-header section-header">
23-
<p>Activity log</p>
24-
<p className="cds--label">
25-
{'Log of the task execution. If you run a task, the output will be displayed here.'}
26-
</p>
41+
<div className="section">
42+
<div className="section-header">
43+
<div className="section-header__info">
44+
<p>Activity log</p>
45+
<p className="cds--label">
46+
{'Log of the task execution. If you run a task, the output will be displayed here.'}
47+
</p>
48+
</div>
49+
<div className="section-header__buttons">
50+
<Button
51+
kind="tertiary"
52+
size="sm"
53+
onClick={ copyResult }
54+
disabled={ !log.length }
55+
>
56+
{copyResultText}
57+
</Button>
58+
</div>
2759
</div>
28-
<div className="section-content">
29-
<CodeMirror
60+
<div className="editor">
61+
<CodeEditor
3062
value={ value }
31-
extensions={ [ json() ] }
3263
readOnly={ true }
64+
linting={ false }
65+
placeholder="No output yet"
3366
/>
3467
</div>
3568
</div>

0 commit comments

Comments
 (0)