Incremental work on embedding activities
This commit is contained in:
parent
8600dc5fcd
commit
afb312654f
|
|
@ -66,11 +66,11 @@ export namespace Components {
|
|||
"value": string;
|
||||
}
|
||||
interface ElsaDefaultActivityTemplate {
|
||||
"activity": Activity;
|
||||
"activityJson": string;
|
||||
"activityType": string;
|
||||
"canStartWorkflow": boolean;
|
||||
"displayText": string;
|
||||
"displayType": string;
|
||||
"selected": boolean;
|
||||
}
|
||||
interface ElsaDropdownButton {
|
||||
"icon"?: any;
|
||||
|
|
@ -651,11 +651,11 @@ declare namespace LocalJSX {
|
|||
"value"?: string;
|
||||
}
|
||||
interface ElsaDefaultActivityTemplate {
|
||||
"activity"?: Activity;
|
||||
"activityJson"?: string;
|
||||
"activityType"?: string;
|
||||
"canStartWorkflow"?: boolean;
|
||||
"displayText"?: string;
|
||||
"displayType"?: string;
|
||||
"selected"?: boolean;
|
||||
}
|
||||
interface ElsaDropdownButton {
|
||||
"icon"?: any;
|
||||
|
|
|
|||
|
|
@ -350,7 +350,7 @@ export class FlowchartComponent implements ContainerActivityComponent {
|
|||
}
|
||||
|
||||
onNodeMoved = (e: PositionEventArgs<JQuery.ClickEvent>) => {
|
||||
const node = e.node;
|
||||
const node = e.node as ActivityNodeShape;
|
||||
const activity = node.data as Activity;
|
||||
const nodePosition = node.position({relative: false});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
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: Node){
|
||||
export function assignParent(graph: Graph, node: ActivityNodeShape) {
|
||||
const underlyingNodes = graph.getNodesUnderNode(node);
|
||||
|
||||
if (underlyingNodes.length > 0) {
|
||||
const underlyingNode = underlyingNodes[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');
|
||||
|
|
@ -22,9 +23,10 @@ export function assignParent(graph: Graph, node: Node){
|
|||
const localPortRect = Rectangle.create(graph.pageToLocal(portRect));
|
||||
|
||||
if (movedNodeViewRect.intersectsWithRect(localPortRect)) {
|
||||
const portName = camelCase(portElement.dataset.portName);
|
||||
const portName = camelCase(portElement.dataset.portName);
|
||||
const childActivity = node.data as Activity;
|
||||
underlyingActivity[portName] = childActivity;
|
||||
underlyingNode.activity = {...underlyingActivity};
|
||||
graph.removeCell(node);
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,21 +64,37 @@ export class ActivityNode extends Shape.HTML {
|
|||
document.body.append(wrapper);
|
||||
|
||||
// Wait for activity element to be completely rendered.
|
||||
const tryUpdate = () => {
|
||||
// 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'));
|
||||
|
||||
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) {
|
||||
|
||||
// Request an animation frame and call ourselves back immediately after.
|
||||
window.requestAnimationFrame(tryUpdateSize);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const rect = wrapper.firstElementChild.getBoundingClientRect();
|
||||
const width = rect.width;
|
||||
const height = rect.height;
|
||||
|
||||
if (height == 0 || width == 0) {
|
||||
window.requestAnimationFrame(tryUpdate);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update size of the activity node.
|
||||
this.prop({size: {width, height}});
|
||||
|
||||
// Remove the temporary element (used only to calculate its size).
|
||||
wrapper.remove();
|
||||
};
|
||||
|
||||
tryUpdate();
|
||||
// Begin try to get our element size.
|
||||
tryUpdateSize();
|
||||
}
|
||||
|
||||
createHtml() {
|
||||
|
|
@ -99,7 +115,6 @@ export class ActivityNode extends Shape.HTML {
|
|||
}
|
||||
|
||||
ActivityNode.config({
|
||||
//portMarkup: [Markup.getForeignObjectMarkup()],
|
||||
ports: {
|
||||
groups: {
|
||||
in: {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import {isNullOrWhitespace} from "../../../utils";
|
|||
export class DefaultActivityTemplate {
|
||||
private readonly iconRegistry: ActivityIconRegistry;
|
||||
private activityDescriptor: ActivityDescriptor;
|
||||
private activity: Activity;
|
||||
private parsedActivity: Activity;
|
||||
private icon: ActivityIcon;
|
||||
|
||||
constructor() {
|
||||
|
|
@ -21,47 +21,61 @@ export class DefaultActivityTemplate {
|
|||
}
|
||||
|
||||
@Prop({attribute: 'activity-type'}) activityType: string;
|
||||
@Prop({attribute: 'activity'}) activityJson: string;
|
||||
@Prop({attribute: 'display-text'}) displayText: string;
|
||||
@Prop({attribute: 'display-type'}) displayType: string;
|
||||
@Prop({attribute: 'can-start-workflow'}) canStartWorkflow: boolean;
|
||||
@Prop({attribute: 'activity'}) activityJson: string;
|
||||
@Prop() selected: boolean;
|
||||
@Prop() activity: Activity;
|
||||
@State() private selectedPortName: string;
|
||||
|
||||
componentWillLoad() {
|
||||
const activityType = this.activityType;
|
||||
const iconRegistry = this.iconRegistry;
|
||||
const encodedActivityJson = this.activityJson;
|
||||
this.activityDescriptor = descriptorsStore.activityDescriptors.find(x => x.activityType == activityType);
|
||||
|
||||
if (!isNullOrWhitespace(encodedActivityJson)) {
|
||||
const decodedActivityJson = decodeURI(encodedActivityJson);
|
||||
this.activity = JSON.parse(decodedActivityJson);
|
||||
if (!!this.activity) {
|
||||
{
|
||||
this.parsedActivity = this.activity;
|
||||
}
|
||||
} else {
|
||||
const encodedActivityJson = this.activityJson;
|
||||
|
||||
if (!isNullOrWhitespace(encodedActivityJson)) {
|
||||
const decodedActivityJson = decodeURI(encodedActivityJson);
|
||||
this.parsedActivity = JSON.parse(decodedActivityJson);
|
||||
}
|
||||
}
|
||||
|
||||
this.activityDescriptor = descriptorsStore.activityDescriptors.find(x => x.activityType == this.activityType);
|
||||
const activityType = this.activityType;
|
||||
this.icon = iconRegistry.has(activityType) ? iconRegistry.get(activityType) : null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const activityDescriptor = this.activityDescriptor;
|
||||
const canStartWorkflow = this.canStartWorkflow;
|
||||
const activity = this.parsedActivity;
|
||||
const canStartWorkflow = activity?.canStartWorkflow;
|
||||
const icon = this.icon;
|
||||
const textColor = canStartWorkflow ? 'text-white' : 'text-gray-700';
|
||||
const isTrigger = activityDescriptor?.kind == ActivityKind.Trigger;
|
||||
const backgroundColor = canStartWorkflow ? isTrigger ? 'bg-green-400' : 'bg-blue-400' : 'bg-white';
|
||||
const iconBackgroundColor = isTrigger ? 'bg-green-500' : 'bg-blue-500';
|
||||
const borderColor = canStartWorkflow ? isTrigger ? 'border-green-600' : 'border-blue-600' : 'border-gray-300';
|
||||
const borderColor = this.selected ? 'border-blue-600' : canStartWorkflow ? isTrigger ? 'border-green-600' : 'border-blue-600' : 'border-gray-300';
|
||||
const displayTypeIsPicker = this.displayTypeIsPicker;
|
||||
const cssClass = displayTypeIsPicker ? 'px-2 py-2' : 'px-4 py-4';
|
||||
let displayText = this.displayText;
|
||||
const displayTypeIsEmbedded = this.displayTypeIsEmbedded;
|
||||
const containerCssClass = displayTypeIsEmbedded ? '' : 'drop-shadow-md';
|
||||
const contentCssClass = displayTypeIsPicker ? 'px-2 py-2' : 'px-4 py-4';
|
||||
let displayText = activity?.metadata?.displayText;
|
||||
|
||||
if (isNullOrWhitespace(displayText))
|
||||
displayText = activityDescriptor?.displayName;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div class={`activity-wrapper border ${borderColor} ${backgroundColor} rounded text-white overflow-hidden drop-shadow-md`}>
|
||||
<div class={`activity-wrapper border ${borderColor} ${backgroundColor} ${containerCssClass} rounded text-white overflow-hidden`}>
|
||||
<div class="activity-content-wrapper flex flex-row">
|
||||
<div class={`flex flex-shrink items-center ${iconBackgroundColor}`}>
|
||||
{this.renderIcon(icon)}
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class={cssClass}>
|
||||
<div class={contentCssClass}>
|
||||
<span class={textColor}>{displayText}</span>
|
||||
<div>
|
||||
{this.renderPorts()}
|
||||
|
|
@ -75,11 +89,10 @@ export class DefaultActivityTemplate {
|
|||
}
|
||||
|
||||
private renderIcon = (icon?: ActivityIcon): string => {
|
||||
const displayTypeIsPicker = this.displayTypeIsPicker;
|
||||
const iconCssClass = displayTypeIsPicker ? 'px-2' : 'px-4';
|
||||
const iconCssClass = this.displayTypeIsPicker ? 'px-2' : 'px-4';
|
||||
|
||||
if (!icon)
|
||||
return undefined; //return '<div class="px-1 py-1"><span></span></div>';
|
||||
return undefined;
|
||||
|
||||
return (
|
||||
<div class={`${iconCssClass} py-1`}>
|
||||
|
|
@ -90,7 +103,7 @@ export class DefaultActivityTemplate {
|
|||
|
||||
private renderPorts = () => {
|
||||
|
||||
if (this?.displayType != 'designer')
|
||||
if (this.displayTypeIsPicker)
|
||||
return undefined;
|
||||
|
||||
const activityDescriptor = this.activityDescriptor;
|
||||
|
|
@ -107,11 +120,12 @@ export class DefaultActivityTemplate {
|
|||
};
|
||||
|
||||
private renderPort = (port: Port) => {
|
||||
const canStartWorkflow = this.canStartWorkflow;
|
||||
const canStartWorkflow = this.parsedActivity?.canStartWorkflow;
|
||||
const textColor = canStartWorkflow ? 'text-white' : 'text-gray-700';
|
||||
const portName = camelCase(port.name);
|
||||
const activity = this.activity;
|
||||
const activity = this.parsedActivity;
|
||||
const childActivity: Activity = activity ? activity[portName] : null;
|
||||
const isSelected = port.name == this.selectedPortName;
|
||||
|
||||
return (
|
||||
<div class="activity-port" data-port-name={port.name}>
|
||||
|
|
@ -120,18 +134,16 @@ export class DefaultActivityTemplate {
|
|||
</div>
|
||||
<div>
|
||||
{childActivity ? (
|
||||
<div onDragOver={this.onDragOverPort}
|
||||
onDrop={this.onDropOnPort}
|
||||
onMouseDown={this.onPortMouseDown}
|
||||
<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={this.onPortMouseUp}
|
||||
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">
|
||||
<span class={textColor}>{childActivity.typeName}</span>
|
||||
>
|
||||
<elsa-default-activity-template activityType={childActivity.typeName} displayType="embedded" activity={childActivity} selected={isSelected}/>
|
||||
{/*<span class={textColor}>{childActivity.typeName}</span>*/}
|
||||
</div>
|
||||
) : (
|
||||
<div onDragOver={this.onDragOverPort}
|
||||
onDrop={this.onDropOnPort}
|
||||
onMouseDown={this.onPortMouseDown}
|
||||
onMouseUp={this.onPortMouseUp}
|
||||
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"/>
|
||||
|
|
@ -146,8 +158,17 @@ export class DefaultActivityTemplate {
|
|||
return this.displayType == "picker";
|
||||
}
|
||||
|
||||
private onPortMouseDown = (e: MouseEvent) => {
|
||||
private get displayTypeIsEmbedded(): boolean {
|
||||
return this.displayType == "embedded";
|
||||
}
|
||||
|
||||
private onPortMouseDown = (e: MouseEvent, port: Port) => {
|
||||
e.stopPropagation();
|
||||
|
||||
if (this.selectedPortName != port.name)
|
||||
this.selectedPortName = port.name;
|
||||
else
|
||||
this.selectedPortName = null;
|
||||
};
|
||||
|
||||
private onPortMouseUp = (e: MouseEvent) => {
|
||||
|
|
|
|||
|
|
@ -11,24 +11,9 @@ export class DefaultActivityDriver implements ActivityDriver {
|
|||
const activityDescriptor = context.activityDescriptor;
|
||||
const activityType = activityDescriptor.activityType;
|
||||
const activity = context.activity;
|
||||
const canStartWorkflow = activity?.canStartWorkflow == true;
|
||||
const text = activityDescriptor?.displayName;
|
||||
let displayText = activity?.metadata?.displayText;
|
||||
|
||||
if (isNullOrWhitespace(displayText))
|
||||
displayText = text;
|
||||
|
||||
const displayType = context.displayType;
|
||||
const activityJson = displayType == 'designer' ? encodeURI(JSON.stringify(activity)) : '';
|
||||
|
||||
return (`
|
||||
<elsa-default-activity-template
|
||||
activity-type="${activityType}"
|
||||
display-text="${displayText}"
|
||||
display-type="${displayType}"
|
||||
activity="${activityJson}"
|
||||
can-start-workflow="${canStartWorkflow}" />
|
||||
`);
|
||||
return (`<elsa-default-activity-template activity-type="${activityType}" activity="${activityJson}" display-type="${displayType}" />`);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue