Squashed 'spiffworkflow-frontend/' changes from 942256a4a..cdae31a57
cdae31a57 Merge pull request #101 from sartography/feature/more_launch_buttons_and_dropdowns c617100e2 Fix the react diagram editor to depend on the freakin universe, so eslint will pass. 7d0a3484f Merge branch 'main' into feature/more_launch_buttons_and_dropdowns. Disabling the but launch button for call activities until we complete the nested process group work. 78e4267ca picky picky linter. a46d3bda7 Merge branch 'main' into feature/more_launch_buttons_and_dropdowns f430dbbea do not render diagram editor until processModel is loaded w/ burnettk f29d34ab9 Adding ability to get a list of DMN and Json files from the process model into a dropdown list in the bpmn editor. c4182e12d explicit themes 9f6a146eb add nav items so people can get everywhere 342b7c9e1 Open BPMN, JSON, and DMN files in a new window when the "Launch Editor" button is clicked. b7e094dc9 Add a Markdown Editor. git-subtree-dir: spiffworkflow-frontend git-subtree-split: cdae31a57add18097211711db03536dff949cb6c
This commit is contained in:
parent
4b42d6cde6
commit
44e49e6ae6
File diff suppressed because it is too large
Load Diff
|
@ -20,12 +20,13 @@
|
|||
"@types/node": "^18.6.5",
|
||||
"@types/react": "^18.0.17",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@uiw/react-md-editor": "^3.19.5",
|
||||
"autoprefixer": "10.4.8",
|
||||
"axios": "^0.27.2",
|
||||
"bootstrap": "^5.2.0",
|
||||
"bpmn-js": "^9.3.2",
|
||||
"bpmn-js-properties-panel": "^1.10.0",
|
||||
"bpmn-js-spiffworkflow": "sartography/bpmn-js-spiffworkflow#main",
|
||||
"bpmn-js-spiffworkflow": "sartography/bpmn-js-spiffworkflow#feature/more_launch_buttons_and_dropdowns",
|
||||
"craco": "^0.0.3",
|
||||
"date-fns": "^2.28.0",
|
||||
"diagram-js": "^8.5.0",
|
||||
|
|
48
src/App.tsx
48
src/App.tsx
|
@ -46,28 +46,30 @@ export default function App() {
|
|||
}
|
||||
|
||||
return (
|
||||
<ErrorContext.Provider value={errorContextValueArray}>
|
||||
<BrowserRouter>
|
||||
<NavigationBar />
|
||||
<Content>
|
||||
{errorTag}
|
||||
<ErrorBoundary>
|
||||
<Routes>
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route path="/tasks" element={<HomePage />} />
|
||||
<Route path="/admin/*" element={<AdminRoutes />} />
|
||||
<Route
|
||||
path="/tasks/:process_instance_id/:task_id"
|
||||
element={<TaskShow />}
|
||||
/>
|
||||
<Route
|
||||
path="/tasks/:process_instance_id/:task_id"
|
||||
element={<TaskShow />}
|
||||
/>
|
||||
</Routes>
|
||||
</ErrorBoundary>
|
||||
</Content>
|
||||
</BrowserRouter>
|
||||
</ErrorContext.Provider>
|
||||
<div className="cds--white">
|
||||
<ErrorContext.Provider value={errorContextValueArray}>
|
||||
<BrowserRouter>
|
||||
<NavigationBar />
|
||||
<Content>
|
||||
{errorTag}
|
||||
<ErrorBoundary>
|
||||
<Routes>
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route path="/tasks" element={<HomePage />} />
|
||||
<Route path="/admin/*" element={<AdminRoutes />} />
|
||||
<Route
|
||||
path="/tasks/:process_instance_id/:task_id"
|
||||
element={<TaskShow />}
|
||||
/>
|
||||
<Route
|
||||
path="/tasks/:process_instance_id/:task_id"
|
||||
element={<TaskShow />}
|
||||
/>
|
||||
</Routes>
|
||||
</ErrorBoundary>
|
||||
</Content>
|
||||
</BrowserRouter>
|
||||
</ErrorContext.Provider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -101,12 +101,29 @@ export default function NavigationBar() {
|
|||
>
|
||||
Process Instances
|
||||
</HeaderMenuItem>
|
||||
<HeaderMenuItem
|
||||
href="/admin/messages"
|
||||
isCurrentPage={isActivePage('/admin/messages')}
|
||||
>
|
||||
Messages
|
||||
</HeaderMenuItem>
|
||||
<HeaderMenuItem
|
||||
href="/admin/secrets"
|
||||
isCurrentPage={isActivePage('/admin/secrets')}
|
||||
>
|
||||
Secrets
|
||||
</HeaderMenuItem>
|
||||
<HeaderMenuItem
|
||||
href="/admin/authentications"
|
||||
isCurrentPage={isActivePage('/admin/authentications')}
|
||||
>
|
||||
Authentications
|
||||
</HeaderMenuItem>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
if (activeKey) {
|
||||
// TODO: apply theme g100 to the header
|
||||
return (
|
||||
<HeaderContainer
|
||||
render={({ isSideNavExpanded, onClickSideNavExpand }: any) => (
|
||||
|
|
|
@ -69,8 +69,14 @@ type OwnProps = {
|
|||
diagramXML?: string | null;
|
||||
fileName?: string;
|
||||
onLaunchScriptEditor?: (..._args: any[]) => any;
|
||||
onLaunchMarkdownEditor?: (..._args: any[]) => any;
|
||||
onLaunchBpmnEditor?: (..._args: any[]) => any;
|
||||
onLaunchJsonEditor?: (..._args: any[]) => any;
|
||||
onLaunchDmnEditor?: (..._args: any[]) => any;
|
||||
onElementClick?: (..._args: any[]) => any;
|
||||
onServiceTasksRequested?: (..._args: any[]) => any;
|
||||
onJsonFilesRequested?: (..._args: any[]) => any;
|
||||
onDmnFilesRequested?: (..._args: any[]) => any;
|
||||
url?: string;
|
||||
};
|
||||
|
||||
|
@ -87,8 +93,14 @@ export default function ReactDiagramEditor({
|
|||
diagramXML,
|
||||
fileName,
|
||||
onLaunchScriptEditor,
|
||||
onLaunchMarkdownEditor,
|
||||
onLaunchBpmnEditor,
|
||||
onLaunchJsonEditor,
|
||||
onLaunchDmnEditor,
|
||||
onElementClick,
|
||||
onServiceTasksRequested,
|
||||
onJsonFilesRequested,
|
||||
onDmnFilesRequested,
|
||||
url,
|
||||
}: OwnProps) {
|
||||
const [diagramXMLString, setDiagramXMLString] = useState('');
|
||||
|
@ -191,6 +203,17 @@ export default function ReactDiagramEditor({
|
|||
}
|
||||
}
|
||||
|
||||
function handleLaunchMarkdownEditor(
|
||||
element: any,
|
||||
value: string,
|
||||
eventBus: any
|
||||
) {
|
||||
if (onLaunchMarkdownEditor) {
|
||||
setPerformingXmlUpdates(true);
|
||||
onLaunchMarkdownEditor(element, value, eventBus);
|
||||
}
|
||||
}
|
||||
|
||||
function handleElementClick(event: any) {
|
||||
if (onElementClick) {
|
||||
onElementClick(event.element);
|
||||
|
@ -205,7 +228,7 @@ export default function ReactDiagramEditor({
|
|||
|
||||
setDiagramModelerState(diagramModeler);
|
||||
|
||||
diagramModeler.on('script.editor.launch', (event: any) => {
|
||||
diagramModeler.on('spiff.script.edit', (event: any) => {
|
||||
const { error, element, scriptType, script, eventBus } = event;
|
||||
if (error) {
|
||||
console.log(error);
|
||||
|
@ -213,6 +236,35 @@ export default function ReactDiagramEditor({
|
|||
handleLaunchScriptEditor(element, script, scriptType, eventBus);
|
||||
});
|
||||
|
||||
diagramModeler.on('spiff.markdown.edit', (event: any) => {
|
||||
const { error, element, value, eventBus } = event;
|
||||
if (error) {
|
||||
console.log(error);
|
||||
}
|
||||
handleLaunchMarkdownEditor(element, value, eventBus);
|
||||
});
|
||||
|
||||
/**
|
||||
* fixme: this is not in use yet, we need the ability to find bpmn files by id.
|
||||
*/
|
||||
diagramModeler.on('spiff.callactivity.edit', (event: any) => {
|
||||
if (onLaunchBpmnEditor) {
|
||||
onLaunchBpmnEditor(event.processId);
|
||||
}
|
||||
});
|
||||
|
||||
diagramModeler.on('spiff.file.edit', (event: any) => {
|
||||
if (onLaunchJsonEditor) {
|
||||
onLaunchJsonEditor(event.value);
|
||||
}
|
||||
});
|
||||
|
||||
diagramModeler.on('spiff.dmn.edit', (event: any) => {
|
||||
if (onLaunchDmnEditor) {
|
||||
onLaunchDmnEditor(event.value);
|
||||
}
|
||||
});
|
||||
|
||||
// 'element.hover',
|
||||
// 'element.out',
|
||||
// 'element.click',
|
||||
|
@ -226,12 +278,34 @@ export default function ReactDiagramEditor({
|
|||
diagramModeler.on('spiff.service_tasks.requested', (event: any) => {
|
||||
handleServiceTasksRequested(event);
|
||||
});
|
||||
|
||||
diagramModeler.on('spiff.json_files.requested', (event: any) => {
|
||||
if (onJsonFilesRequested) {
|
||||
onJsonFilesRequested(event);
|
||||
}
|
||||
});
|
||||
|
||||
diagramModeler.on('spiff.dmn_files.requested', (event: any) => {
|
||||
if (onDmnFilesRequested) {
|
||||
onDmnFilesRequested(event);
|
||||
}
|
||||
});
|
||||
|
||||
diagramModeler.on('spiff.json_files.requested', (event: any) => {
|
||||
handleServiceTasksRequested(event);
|
||||
});
|
||||
}, [
|
||||
diagramModelerState,
|
||||
diagramType,
|
||||
onLaunchScriptEditor,
|
||||
onLaunchMarkdownEditor,
|
||||
onLaunchBpmnEditor,
|
||||
onLaunchDmnEditor,
|
||||
onLaunchJsonEditor,
|
||||
onElementClick,
|
||||
onServiceTasksRequested,
|
||||
onJsonFilesRequested,
|
||||
onDmnFilesRequested,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -5,6 +5,11 @@
|
|||
// @include grid.flex-grid();
|
||||
|
||||
// @use '@carbon/react/scss/colors';
|
||||
// .cds--header, a.cds--header__menu-item {
|
||||
// background-color: colors.$gray-100;
|
||||
// }
|
||||
|
||||
// site is mainly using white theme.
|
||||
// header is mainly using g100
|
||||
// mockup wanted white, not grey, text
|
||||
.cds--header, a.cds--header__menu-item {
|
||||
// background-color: colors.$gray-100;
|
||||
color: white;
|
||||
}
|
||||
|
|
|
@ -17,11 +17,29 @@ export interface ProcessGroup {
|
|||
description?: string | null;
|
||||
}
|
||||
|
||||
export interface ProcessFileReference {
|
||||
id: string; // The unique id of the process or decision table.
|
||||
name: string; // The process or decision table name.
|
||||
type: string; // either "decision" or "process"
|
||||
}
|
||||
|
||||
export interface ProcessFile {
|
||||
content_type: string;
|
||||
last_modified: string;
|
||||
name: string;
|
||||
process_group_id: string;
|
||||
process_model_id: string;
|
||||
references: ProcessFileReference[];
|
||||
size: number;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface ProcessModel {
|
||||
id: string;
|
||||
process_group_id: string;
|
||||
display_name: string;
|
||||
primary_file_name: string;
|
||||
files: ProcessFile[];
|
||||
}
|
||||
|
||||
// tuple of display value and URL
|
||||
|
|
|
@ -1,23 +1,29 @@
|
|||
import { useContext, useEffect, useRef, useState } from 'react';
|
||||
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
||||
import {
|
||||
generatePath,
|
||||
useNavigate,
|
||||
useParams,
|
||||
useSearchParams,
|
||||
} from 'react-router-dom';
|
||||
// @ts-ignore
|
||||
import { Button, Modal, Stack, Content } from '@carbon/react';
|
||||
// import Container from 'react-bootstrap/Container';
|
||||
import Row from 'react-bootstrap/Row';
|
||||
import Col from 'react-bootstrap/Col';
|
||||
|
||||
import Editor from '@monaco-editor/react';
|
||||
|
||||
import MDEditor from '@uiw/react-md-editor';
|
||||
import ReactDiagramEditor from '../components/ReactDiagramEditor';
|
||||
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
||||
import HttpService from '../services/HttpService';
|
||||
import ErrorContext from '../contexts/ErrorContext';
|
||||
import { makeid } from '../helpers';
|
||||
import { ProcessModel } from '../interfaces';
|
||||
import { ProcessFile, ProcessModel } from '../interfaces';
|
||||
|
||||
export default function ProcessModelEditDiagram() {
|
||||
const [showFileNameEditor, setShowFileNameEditor] = useState(false);
|
||||
const handleShowFileNameEditor = () => setShowFileNameEditor(true);
|
||||
const [processModel, setProcessModel] = useState<ProcessModel | null>(null);
|
||||
|
||||
const [scriptText, setScriptText] = useState<string>('');
|
||||
const [scriptType, setScriptType] = useState<string>('');
|
||||
|
@ -27,6 +33,12 @@ export default function ProcessModelEditDiagram() {
|
|||
const [showScriptEditor, setShowScriptEditor] = useState(false);
|
||||
const handleShowScriptEditor = () => setShowScriptEditor(true);
|
||||
|
||||
const [markdownText, setMarkdownText] = useState<string | undefined>('');
|
||||
const [markdownEventBus, setMarkdownEventBus] = useState<any>(null);
|
||||
const [showMarkdownEditor, setShowMarkdownEditor] = useState(false);
|
||||
|
||||
const handleShowMarkdownEditor = () => setShowMarkdownEditor(true);
|
||||
|
||||
const editorRef = useRef(null);
|
||||
const monacoRef = useRef(null);
|
||||
|
||||
|
@ -70,8 +82,6 @@ export default function ProcessModelEditDiagram() {
|
|||
const [bpmnXmlForDiagramRendering, setBpmnXmlForDiagramRendering] =
|
||||
useState(null);
|
||||
|
||||
const [processModel, setProcessModel] = useState<ProcessModel | null>(null);
|
||||
|
||||
const processModelPath = `process-models/${params.process_group_id}/${params.process_model_id}`;
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -85,7 +95,7 @@ export default function ProcessModelEditDiagram() {
|
|||
}, [processModelPath]);
|
||||
|
||||
useEffect(() => {
|
||||
const processResult = (result: any) => {
|
||||
const fileResult = (result: any) => {
|
||||
setProcessModelFile(result);
|
||||
setBpmnXmlForDiagramRendering(result.file_contents);
|
||||
};
|
||||
|
@ -93,7 +103,7 @@ export default function ProcessModelEditDiagram() {
|
|||
if (params.file_name) {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/${processModelPath}/files/${params.file_name}`,
|
||||
successCallback: processResult,
|
||||
successCallback: fileResult,
|
||||
});
|
||||
}
|
||||
}, [processModelPath, params]);
|
||||
|
@ -246,6 +256,34 @@ export default function ProcessModelEditDiagram() {
|
|||
});
|
||||
};
|
||||
|
||||
const onJsonFilesRequested = (event: any) => {
|
||||
if (processModel) {
|
||||
const jsonFiles = processModel.files.filter((f) => f.type === 'json');
|
||||
const options = jsonFiles.map((f) => {
|
||||
return { label: f.name, value: f.name };
|
||||
});
|
||||
event.eventBus.fire('spiff.json_files.returned', { options });
|
||||
} else {
|
||||
console.log('There is no process Model.');
|
||||
}
|
||||
};
|
||||
|
||||
const onDmnFilesRequested = (event: any) => {
|
||||
if (processModel) {
|
||||
const dmnFiles = processModel.files.filter((f) => f.type === 'dmn');
|
||||
const options: any[] = [];
|
||||
dmnFiles.forEach((file) => {
|
||||
file.references.forEach((ref) => {
|
||||
options.push({ label: ref.name, value: ref.id });
|
||||
});
|
||||
});
|
||||
console.log('Options', options);
|
||||
event.eventBus.fire('spiff.dmn_files.returned', { options });
|
||||
} else {
|
||||
console.log('There is no process model.');
|
||||
}
|
||||
};
|
||||
|
||||
const getScriptUnitTestElements = (element: any) => {
|
||||
const { extensionElements } = element.businessObject;
|
||||
if (extensionElements && extensionElements.values.length > 0) {
|
||||
|
@ -292,7 +330,7 @@ export default function ProcessModelEditDiagram() {
|
|||
};
|
||||
|
||||
const handleScriptEditorClose = () => {
|
||||
scriptEventBus.fire('script.editor.update', {
|
||||
scriptEventBus.fire('spiff.script.update', {
|
||||
scriptType,
|
||||
script: scriptText,
|
||||
element: scriptElement,
|
||||
|
@ -580,6 +618,107 @@ export default function ProcessModelEditDiagram() {
|
|||
</Modal>
|
||||
);
|
||||
};
|
||||
const onLaunchMarkdownEditor = (
|
||||
element: any,
|
||||
markdown: string,
|
||||
eventBus: any
|
||||
) => {
|
||||
setMarkdownText(markdown || '');
|
||||
setMarkdownEventBus(eventBus);
|
||||
handleShowMarkdownEditor();
|
||||
};
|
||||
const handleMarkdownEditorClose = () => {
|
||||
markdownEventBus.fire('spiff.markdown.update', {
|
||||
value: markdownText,
|
||||
});
|
||||
setShowMarkdownEditor(false);
|
||||
};
|
||||
|
||||
const markdownEditor = () => {
|
||||
return (
|
||||
<Modal
|
||||
size="xl"
|
||||
show={showMarkdownEditor}
|
||||
onHide={handleMarkdownEditorClose}
|
||||
>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Edit Markdown Content</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<MDEditor value={markdownText} onChange={setMarkdownText} />
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant="secondary" onClick={handleMarkdownEditorClose}>
|
||||
Close
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const findFileNameForReferenceId = (
|
||||
id: string,
|
||||
type: string
|
||||
): ProcessFile | null => {
|
||||
// Given a reference id (like a process_id, or decision_id) finds the file
|
||||
// that contains that reference and returns it.
|
||||
let matchFile = null;
|
||||
if (processModel) {
|
||||
const files = processModel.files.filter((f) => f.type === type);
|
||||
files.some((file) => {
|
||||
if (file.references.some((ref) => ref.id === id)) {
|
||||
matchFile = file;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
return matchFile;
|
||||
};
|
||||
|
||||
/**
|
||||
* fixme: Not currently in use. This would only work for bpmn files within the process model. Which is right for DMN and json, but not right here. Need to merge in work on the nested process groups before tackling this.
|
||||
* @param processId
|
||||
*/
|
||||
const onLaunchBpmnEditor = (processId: string) => {
|
||||
const file = findFileNameForReferenceId(processId, 'bpmn');
|
||||
if (file) {
|
||||
const path = generatePath(
|
||||
'/admin/process-models/:process_group_id/:process_model_id/files/:file_name',
|
||||
{
|
||||
process_group_id: params.process_group_id,
|
||||
process_model_id: params.process_model_id,
|
||||
file_name: file.name,
|
||||
}
|
||||
);
|
||||
window.open(path);
|
||||
}
|
||||
};
|
||||
const onLaunchJsonEditor = (fileName: string) => {
|
||||
const path = generatePath(
|
||||
'/admin/process-models/:process_group_id/:process_model_id/form/:file_name',
|
||||
{
|
||||
process_group_id: params.process_group_id,
|
||||
process_model_id: params.process_model_id,
|
||||
file_name: fileName,
|
||||
}
|
||||
);
|
||||
window.open(path);
|
||||
};
|
||||
const onLaunchDmnEditor = (processId: string) => {
|
||||
const file = findFileNameForReferenceId(processId, 'dmn');
|
||||
if (file) {
|
||||
const path = generatePath(
|
||||
'/admin/process-models/:process_group_id/:process_model_id/files/:file_name',
|
||||
{
|
||||
process_group_id: params.process_group_id,
|
||||
process_model_id: params.process_model_id,
|
||||
file_name: file.name,
|
||||
}
|
||||
);
|
||||
window.open(path);
|
||||
}
|
||||
};
|
||||
|
||||
const isDmn = () => {
|
||||
const fileName = params.file_name || '';
|
||||
|
@ -622,12 +761,18 @@ export default function ProcessModelEditDiagram() {
|
|||
diagramType="bpmn"
|
||||
onLaunchScriptEditor={onLaunchScriptEditor}
|
||||
onServiceTasksRequested={onServiceTasksRequested}
|
||||
onLaunchMarkdownEditor={onLaunchMarkdownEditor}
|
||||
onLaunchBpmnEditor={onLaunchBpmnEditor}
|
||||
onLaunchJsonEditor={onLaunchJsonEditor}
|
||||
onJsonFilesRequested={onJsonFilesRequested}
|
||||
onLaunchDmnEditor={onLaunchDmnEditor}
|
||||
onDmnFilesRequested={onDmnFilesRequested}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// if a file name is not given then this is a new model and the ReactDiagramEditor component will handle it
|
||||
if (bpmnXmlForDiagramRendering || !params.file_name) {
|
||||
if ((bpmnXmlForDiagramRendering || !params.file_name) && processModel) {
|
||||
return (
|
||||
<>
|
||||
<ProcessBreadcrumb
|
||||
|
@ -642,6 +787,7 @@ export default function ProcessModelEditDiagram() {
|
|||
{appropriateEditor()}
|
||||
{newFileNameBox()}
|
||||
{scriptEditor()}
|
||||
{markdownEditor()}
|
||||
|
||||
<div id="diagram-container" />
|
||||
</>
|
||||
|
|
Loading…
Reference in New Issue