Incremental work on embedded activities

This commit is contained in:
Sipke Schoorstra 2022-06-18 15:08:39 +02:00
parent 71d0daff12
commit 36c589c062
11 changed files with 70 additions and 123 deletions

View file

@ -264,6 +264,8 @@ export namespace Components {
"workflowDefinition"?: WorkflowDefinition;
"workflowInstance"?: WorkflowInstance;
}
interface ElsaWorkflowNavigator {
}
interface ElsaWorkflowPublishButton {
"publishing": boolean;
}
@ -549,6 +551,12 @@ declare global {
prototype: HTMLElsaWorkflowManagerElement;
new (): HTMLElsaWorkflowManagerElement;
};
interface HTMLElsaWorkflowNavigatorElement extends Components.ElsaWorkflowNavigator, HTMLStencilElement {
}
var HTMLElsaWorkflowNavigatorElement: {
prototype: HTMLElsaWorkflowNavigatorElement;
new (): HTMLElsaWorkflowNavigatorElement;
};
interface HTMLElsaWorkflowPublishButtonElement extends Components.ElsaWorkflowPublishButton, HTMLStencilElement {
}
var HTMLElsaWorkflowPublishButtonElement: {
@ -614,6 +622,7 @@ declare global {
"elsa-workflow-instance-viewer": HTMLElsaWorkflowInstanceViewerElement;
"elsa-workflow-journal": HTMLElsaWorkflowJournalElement;
"elsa-workflow-manager": HTMLElsaWorkflowManagerElement;
"elsa-workflow-navigator": HTMLElsaWorkflowNavigatorElement;
"elsa-workflow-publish-button": HTMLElsaWorkflowPublishButtonElement;
"elsa-workflow-toolbar": HTMLElsaWorkflowToolbarElement;
"elsa-workflow-toolbar-menu": HTMLElsaWorkflowToolbarMenuElement;
@ -840,6 +849,8 @@ declare namespace LocalJSX {
"workflowDefinition"?: WorkflowDefinition;
"workflowInstance"?: WorkflowInstance;
}
interface ElsaWorkflowNavigator {
}
interface ElsaWorkflowPublishButton {
"onExportClicked"?: (event: CustomEvent<any>) => void;
"onImportClicked"?: (event: CustomEvent<File>) => void;
@ -899,6 +910,7 @@ declare namespace LocalJSX {
"elsa-workflow-instance-viewer": ElsaWorkflowInstanceViewer;
"elsa-workflow-journal": ElsaWorkflowJournal;
"elsa-workflow-manager": ElsaWorkflowManager;
"elsa-workflow-navigator": ElsaWorkflowNavigator;
"elsa-workflow-publish-button": ElsaWorkflowPublishButton;
"elsa-workflow-toolbar": ElsaWorkflowToolbar;
"elsa-workflow-toolbar-menu": ElsaWorkflowToolbarMenu;
@ -954,6 +966,7 @@ declare module "@stencil/core" {
"elsa-workflow-instance-viewer": LocalJSX.ElsaWorkflowInstanceViewer & JSXBase.HTMLAttributes<HTMLElsaWorkflowInstanceViewerElement>;
"elsa-workflow-journal": LocalJSX.ElsaWorkflowJournal & JSXBase.HTMLAttributes<HTMLElsaWorkflowJournalElement>;
"elsa-workflow-manager": LocalJSX.ElsaWorkflowManager & JSXBase.HTMLAttributes<HTMLElsaWorkflowManagerElement>;
"elsa-workflow-navigator": LocalJSX.ElsaWorkflowNavigator & JSXBase.HTMLAttributes<HTMLElsaWorkflowNavigatorElement>;
"elsa-workflow-publish-button": LocalJSX.ElsaWorkflowPublishButton & JSXBase.HTMLAttributes<HTMLElsaWorkflowPublishButtonElement>;
"elsa-workflow-toolbar": LocalJSX.ElsaWorkflowToolbar & JSXBase.HTMLAttributes<HTMLElsaWorkflowToolbarElement>;
"elsa-workflow-toolbar-menu": LocalJSX.ElsaWorkflowToolbarMenu & JSXBase.HTMLAttributes<HTMLElsaWorkflowToolbarMenuElement>;

View file

@ -20,7 +20,6 @@ import FromJSONData = Model.FromJSONData;
import {ContextMenuAnchorPoint, MenuItem, MenuItemGroup} from "../../shared/context-menu/models";
import PointLike = Point.PointLike;
import descriptorsStore from "../../../data/descriptors-store";
import {assignParent} from "./flowchart/assign-parent";
@Component({
tag: 'elsa-flowchart',
@ -350,6 +349,9 @@ export class FlowchartComponent implements ContainerActivityComponent {
}
onNodeMoved = (e: PositionEventArgs<JQuery.ClickEvent>) => {
console.debug("Node moved...");
const node = e.node as ActivityNodeShape;
const activity = node.data as Activity;
const nodePosition = node.position({relative: false});
@ -364,8 +366,6 @@ export class FlowchartComponent implements ContainerActivityComponent {
}
}
}
assignParent(this.graph, node);
}
onEdgeConnected = async (e: { isNew: boolean, edge: Edge }) => {

View file

@ -1,36 +0,0 @@
import {Graph, Rectangle, Node} from "@antv/x6";
import {camelCase} from 'lodash';
import {Activity} from "../../../../models";
import {ActivityNode as ActivityNodeShape} from "../shapes";
export function assignParent(graph: Graph, node: ActivityNodeShape) {
const underlyingNodes = graph.getNodesUnderNode(node);
if (underlyingNodes.length > 0) {
const underlyingNode = underlyingNodes[0] as ActivityNodeShape;
const underlyingActivity = underlyingNode.data as Activity;
const underlyingView: any = graph.findView(underlyingNode);
const portElements: Array<HTMLElement> = underlyingView.selectors.foContent.getElementsByClassName('activity-port');
if (portElements.length == 0)
return;
const movedNodeView = graph.findView(node);
const movedNodeViewRect = movedNodeView.getBBox();
for (const portElement of portElements) {
const portRect = Rectangle.create(portElement.getBoundingClientRect());
const localPortRect = Rectangle.create(graph.pageToLocal(portRect));
if (movedNodeViewRect.intersectsWithRect(localPortRect)) {
const portName = camelCase(portElement.dataset.portName);
const childActivity = node.data as Activity;
underlyingActivity[portName] = childActivity;
underlyingNode.activity = {...underlyingActivity};
graph.removeCell(node);
break;
}
}
}
}

View file

@ -1,17 +0,0 @@
// export function findParent(args: any): Array<any> {
// const childNode = args.node;
// const bbox = childNode.getBBox();
//
// return this.getNodes().filter((node) => {
//
// const data = node.getData();
//
// if (data?.typeName == 'Elsa.If') {
// debugger;
// const targetBBox = node.getBBox();
// return bbox.isIntersectWithRect(targetBBox);
// }
//
// return false;
// })
// }

View file

@ -1,7 +1,6 @@
import {CellView, Graph, Node, Shape} from '@antv/x6';
import {v4 as uuid} from 'uuid';
import './ports';
import {assignParent} from "./flowchart/assign-parent";
export function createGraph(
container: HTMLElement,

View file

@ -68,20 +68,15 @@ export class ActivityNode extends Shape.HTML {
// Wait for activity element to be completely rendered.
// When using custom elements, they are rendered after they are mounted. Before then, they have a 0 width and height.
const tryUpdateSize = () => {
// TODO: Figure out if there's a way to select *any* custom element.
// Worst case, we need to look for a registered set if tags, or introduce a convention where custom elements need to specify a class name for example.
const activityElements: Array<Element> = Array.from(wrapper.getElementsByTagName('elsa-default-activity-template'));
const activityElement: Element = wrapper.getElementsByTagName('elsa-default-activity-template')[0];
const activityElementRect = activityElement.getBoundingClientRect();
for (const activityElement of activityElements) {
const activityElementRect = activityElement.getBoundingClientRect();
// If the custom element has no width or height yet, it means it has not yet rendered.
if (activityElementRect.width == 0 || activityElementRect.height == 0) {
// If the custom element has no width or height yet, it means it has not yet rendered.
if (activityElementRect.width == 0 || activityElementRect.height == 0) {
// Request an animation frame and call ourselves back immediately after.
window.requestAnimationFrame(tryUpdateSize);
return;
}
// Request an animation frame and call ourselves back immediately after.
window.requestAnimationFrame(tryUpdateSize);
return;
}
const rect = wrapper.firstElementChild.getBoundingClientRect();

View file

@ -131,7 +131,11 @@ export class DefaultActivityTemplate {
const portName = camelCase(port.name);
const activity = this.parsedActivity;
const childActivity: Activity = activity ? activity[portName] : null;
const isSelected = port.name == this.selectedPortName;
const childActivityDescriptor: ActivityDescriptor = childActivity != null ? descriptorsStore.activityDescriptors.find(x => x.activityType == childActivity.typeName) : null;
let childActivityDisplayText = childActivity?.metadata?.displayText;
if (isNullOrWhitespace(childActivityDisplayText))
childActivityDisplayText = childActivityDescriptor?.displayName;
return (
<div class="activity-port" data-port-name={port.name} ref={el => this.portElements.push(el)}>
@ -140,20 +144,29 @@ export class DefaultActivityTemplate {
</div>
<div>
{childActivity ? (
<div class={`relative block w-full focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500`}
onMouseDown={e => this.onPortMouseDown(e, port)}
onMouseUp={e => this.onPortMouseUp(e, port)}
>
<elsa-default-activity-template activityType={childActivity.typeName} displayType="embedded" activity={childActivity} selected={isSelected}/>
{/*<span class={textColor}>{childActivity.typeName}</span>*/}
<div class="relative block w-full border-2 border-gray-300 border-solid rounded-lg p-5 text-center focus:outline-none">
<div class="flex space-x-2">
<div class="flex-grow">
<span class={textColor}>{childActivityDisplayText}</span>
</div>
<div class="flex-shrink">
<a href="#" class="text-gray-500 hover:text-yellow-700">
<svg class="h-6 w-6" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z"/>
<path d="M4 20h4l10.5 -10.5a1.5 1.5 0 0 0 -4 -4l-10.5 10.5v4"/>
<line x1="13.5" y1="6.5" x2="17.5" y2="10.5"/>
</svg>
</a>
</div>
</div>
</div>
) : (
<div onDragOver={this.onDragOverPort}
onDrop={this.onDropOnPort}
class="relative block w-full border-2 border-gray-300 border-dashed rounded-lg p-6 text-center hover:border-gray-400 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
<svg class="h-6 w-6 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
</svg>
<div class="relative block w-full border-2 border-gray-300 border-dashed rounded-lg p-5 text-center focus:outline-none">
<a href="#" class="text-gray-400 hover:text-gray-600">
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
</svg>
</a>
</div>)}
</div>
</div>
@ -178,43 +191,4 @@ export class DefaultActivityTemplate {
this.selectedPortName = null;
}
private onPortMouseDown = (e: MouseEvent, port: Port) => {
e.stopPropagation();
};
private onPortMouseUp = (e: MouseEvent, port: Port) => {
e.stopPropagation();
if (this.selectedPortName != port.name) {
this.selectedPortName = port.name;
const activity = this.parsedActivity;
const portName = camelCase(port.name);
const childActivity: Activity = activity ? activity[portName] : null;
const args: ActivitySelectedArgs = {
activity: childActivity,
applyChanges: a => {
activity[portName] = a;
},
deleteActivity: a => {
activity[portName] = null;
}
};
this.activitySelected.emit(args);
}
};
private onDragOverPort = (e: DragEvent) => {
console.debug("Dragging over!");
e.stopPropagation();
e.preventDefault();
}
private onDropOnPort = (e: DragEvent) => {
//debugger;
console.debug("Dropped!");
}
}

View file

@ -9,7 +9,7 @@ export class Toolbar {
render() {
return (
<div class="flex justify-center absolute border-b border-gray-200 top-0 px-1 pl-4 py-2 text-sm bg-white z-10 elsa-panel-toolbar">
<div class="elsa-panel-toolbar flex justify-center absolute border-b border-gray-200 top-0 px-1 pl-4 py-2 text-sm bg-white z-10">
<elsa-tooltip tooltipPosition="right" tooltipContent={<p class="text-gray-600 text-sm">Currently in development</p>}>
<button class="relative inline-flex items-center px-4 py-2 rounded border border-blue-600 bg-blue-600 text-sm font-medium text-white hover:bg-blue-700 focus:z-10 focus:outline-none focus:ring-blue-600 hover:border-blue-700">
Auto-Layout

View file

@ -4,7 +4,7 @@
}
elsa-canvas {
left: var(--activity-picker-width);
top: 0;
top: 54px;
right: var(--activity-editor-width);
bottom: 0;
}
@ -24,6 +24,13 @@ elsa-panel.panel-state-expanded.elsa-activity-editor-container {
left: unset;
}
elsa-workflow-navigator {
top: 70px;
position: absolute;
left: var(--activity-picker-width);
right: var(--activity-editor-width);
}
.activity-picker-closed {
.activity-list {

View file

@ -174,6 +174,7 @@ export class WorkflowDefinitionEditor {
<WorkflowEditorTunnel.Provider state={tunnelState}>
<div class="absolute inset-0" ref={el => this.container = el}>
<elsa-workflow-definition-editor-toolbar zoomToFit={this.onZoomToFit} />
<elsa-workflow-navigator />
<elsa-panel
class="elsa-activity-picker-container"
position={PanelPosition.Left}

View file

@ -0,0 +1,11 @@
import {Component, h} from "@stencil/core";
@Component({
tag: 'elsa-workflow-navigator',
shadow: false
})
export class WorkflowNavigator {
render() {
return <div class="text-gray-500">Breadcrumbs</div>
}
}