Enhance Process Builder Drag-and-Drop Functionality
- Improved drag-and-drop support in the Process Builder by setting drag data to 'text/plain' for better compatibility, especially on Mac. - Added visual feedback during drag operations by applying a 'dragging' class to the event target. - Updated the drop event handler to parse the dragged data correctly and prevent event propagation. - Introduced a computed property for gateway available variables to enhance node configuration options. - Adjusted the layout of the properties panel to accommodate a new VariableManager component for better variable management.
This commit is contained in:
parent
668e08884e
commit
b3ca62b548
@ -142,9 +142,13 @@ const onDragStart = (event, component) => {
|
||||
data: component.defaultProps.data
|
||||
};
|
||||
|
||||
// Set the drag data
|
||||
// Set the drag data with text/plain format for better Mac compatibility
|
||||
event.dataTransfer.effectAllowed = 'copy';
|
||||
event.dataTransfer.setData('application/json', JSON.stringify(componentData));
|
||||
event.dataTransfer.dropEffect = 'copy';
|
||||
event.dataTransfer.setData('text/plain', JSON.stringify(componentData));
|
||||
|
||||
// Add visual feedback
|
||||
event.target.classList.add('dragging');
|
||||
};
|
||||
|
||||
// Add a component directly via click
|
||||
|
@ -269,10 +269,11 @@ const onDeleteKeyPress = () => {
|
||||
// Handle drop event
|
||||
const onDrop = (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
try {
|
||||
// Get the dragged component data
|
||||
const componentData = JSON.parse(event.dataTransfer.getData('application/json'));
|
||||
const componentData = JSON.parse(event.dataTransfer.getData('text/plain'));
|
||||
if (!componentData) return;
|
||||
|
||||
// Get the Vue Flow wrapper element
|
||||
@ -296,7 +297,6 @@ const onDrop = (event) => {
|
||||
}
|
||||
};
|
||||
|
||||
// console.log('Adding new node:', newNode);
|
||||
addNodes([newNode]);
|
||||
} catch (error) {
|
||||
console.error('Error handling drop:', error);
|
||||
@ -306,7 +306,8 @@ const onDrop = (event) => {
|
||||
// Handle drag over
|
||||
const onDragOver = (event) => {
|
||||
event.preventDefault();
|
||||
event.dataTransfer.dropEffect = 'move';
|
||||
event.stopPropagation();
|
||||
event.dataTransfer.dropEffect = 'copy';
|
||||
};
|
||||
</script>
|
||||
|
||||
|
280
components/process-flow/VariableManager.vue
Normal file
280
components/process-flow/VariableManager.vue
Normal file
@ -0,0 +1,280 @@
|
||||
<template>
|
||||
<div class="variable-manager">
|
||||
<!-- Header with Add Button -->
|
||||
<div class="bg-gray-50 border-b border-gray-200 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 class="text-lg font-medium text-gray-900">Process Variables</h3>
|
||||
<p class="mt-1 text-sm text-gray-500">Manage variables for your process flow</p>
|
||||
</div>
|
||||
<RsButton
|
||||
@click="() => {
|
||||
resetForm();
|
||||
showAddVariable = true;
|
||||
}"
|
||||
variant="primary"
|
||||
size="sm"
|
||||
>
|
||||
<Icon name="material-symbols:add" class="mr-1" />
|
||||
Add Variable
|
||||
</RsButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Variable List -->
|
||||
<div class="p-4">
|
||||
<!-- Empty State -->
|
||||
<div v-if="!variables.length" class="text-center py-8">
|
||||
<Icon name="material-symbols:data-object" class="w-12 h-12 mx-auto mb-3 text-gray-400" />
|
||||
<h4 class="text-sm font-medium text-gray-900 mb-1">No Variables Added</h4>
|
||||
<p class="text-sm text-gray-500 mb-4">Add variables to store and manage data in your process</p>
|
||||
<RsButton
|
||||
@click="() => {
|
||||
resetForm();
|
||||
showAddVariable = true;
|
||||
}"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
>
|
||||
<Icon name="material-symbols:add" class="mr-1" />
|
||||
Add Your First Variable
|
||||
</RsButton>
|
||||
</div>
|
||||
|
||||
<!-- Variable List -->
|
||||
<div v-else class="space-y-2">
|
||||
<div v-for="variable in variables" :key="variable.name" class="variable-item">
|
||||
<div class="flex items-center justify-between p-3 bg-white rounded-lg border border-gray-200 hover:border-blue-200 hover:shadow-sm transition-all duration-200">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium text-gray-900">{{ variable.name }}</span>
|
||||
<RsBadge
|
||||
:variant="variable.scope === 'global' ? 'primary' : 'secondary'"
|
||||
size="sm"
|
||||
>
|
||||
{{ variable.scope }}
|
||||
</RsBadge>
|
||||
<RsBadge
|
||||
variant="outline"
|
||||
size="sm"
|
||||
class="text-gray-500"
|
||||
>
|
||||
{{ variable.type }}
|
||||
</RsBadge>
|
||||
</div>
|
||||
<p v-if="variable.description" class="mt-1 text-sm text-gray-500">
|
||||
{{ variable.description }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 ml-4">
|
||||
<button
|
||||
@click="editVariable(variable)"
|
||||
class="p-1.5 text-gray-400 hover:text-blue-500 hover:bg-blue-50 rounded-md transition-colors"
|
||||
title="Edit variable"
|
||||
>
|
||||
<Icon name="material-symbols:edit" class="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
@click="deleteVariable(variable)"
|
||||
class="p-1.5 text-gray-400 hover:text-red-500 hover:bg-red-50 rounded-md transition-colors"
|
||||
title="Delete variable"
|
||||
>
|
||||
<Icon name="material-symbols:delete" class="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add/Edit Variable Modal -->
|
||||
<RsModal
|
||||
v-model="showAddVariable"
|
||||
:title="editingVariable ? 'Edit Variable' : 'Add Variable'"
|
||||
size="md"
|
||||
:hideFooter="true"
|
||||
:overlayClose="false"
|
||||
>
|
||||
<FormKit
|
||||
type="form"
|
||||
@submit="saveVariable"
|
||||
:actions="false"
|
||||
class="space-y-4"
|
||||
>
|
||||
<FormKit
|
||||
v-model="variableForm.name"
|
||||
type="text"
|
||||
label="Name"
|
||||
placeholder="Enter variable name"
|
||||
validation="required|alpha_numeric|length:3,50"
|
||||
:validation-messages="{
|
||||
required: 'Variable name is required',
|
||||
alpha_numeric: 'Variable name can only contain letters, numbers, and underscores',
|
||||
length: 'Variable name must be between 3 and 50 characters'
|
||||
}"
|
||||
/>
|
||||
|
||||
<FormKit
|
||||
v-model="variableForm.type"
|
||||
type="select"
|
||||
label="Type"
|
||||
:options="[
|
||||
{ label: 'String', value: 'string' },
|
||||
{ label: 'Number', value: 'number' },
|
||||
{ label: 'Boolean', value: 'boolean' },
|
||||
{ label: 'Object', value: 'object' },
|
||||
{ label: 'Array', value: 'array' },
|
||||
{ label: 'Date', value: 'date' },
|
||||
{ label: 'File', value: 'file' }
|
||||
]"
|
||||
validation="required"
|
||||
:validation-messages="{
|
||||
required: 'Variable type is required'
|
||||
}"
|
||||
/>
|
||||
|
||||
<FormKit
|
||||
v-model="variableForm.scope"
|
||||
type="select"
|
||||
label="Scope"
|
||||
:options="[
|
||||
{ label: 'Process', value: 'process' },
|
||||
{ label: 'Global', value: 'global' }
|
||||
]"
|
||||
validation="required"
|
||||
:validation-messages="{
|
||||
required: 'Variable scope is required'
|
||||
}"
|
||||
/>
|
||||
|
||||
<FormKit
|
||||
v-model="variableForm.description"
|
||||
type="textarea"
|
||||
label="Description"
|
||||
placeholder="Enter variable description"
|
||||
:rows="2"
|
||||
/>
|
||||
|
||||
<FormKit
|
||||
v-model="variableForm.isRequired"
|
||||
type="checkbox"
|
||||
label="Required"
|
||||
help="Mark this variable as required"
|
||||
/>
|
||||
|
||||
<div class="flex justify-end space-x-2 pt-4 border-t border-gray-200">
|
||||
<RsButton
|
||||
type="button"
|
||||
@click="closeModal"
|
||||
variant="tertiary"
|
||||
>
|
||||
Cancel
|
||||
</RsButton>
|
||||
<FormKit
|
||||
type="submit"
|
||||
input-class="rs-button rs-button-primary"
|
||||
>
|
||||
{{ editingVariable ? 'Update' : 'Add' }}
|
||||
</FormKit>
|
||||
</div>
|
||||
</FormKit>
|
||||
</RsModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { useVariableStore } from '~/stores/variableStore';
|
||||
|
||||
const variableStore = useVariableStore();
|
||||
|
||||
// State
|
||||
const showAddVariable = ref(false);
|
||||
const editingVariable = ref(null);
|
||||
const variableForm = ref({
|
||||
name: '',
|
||||
type: 'string',
|
||||
scope: 'process',
|
||||
description: '',
|
||||
isRequired: false
|
||||
});
|
||||
|
||||
// Computed
|
||||
const variables = computed(() => {
|
||||
return variableStore.getAllVariables.process;
|
||||
});
|
||||
|
||||
// Methods
|
||||
const editVariable = (variable) => {
|
||||
editingVariable.value = variable;
|
||||
variableForm.value = { ...variable };
|
||||
showAddVariable.value = true;
|
||||
};
|
||||
|
||||
const deleteVariable = (variable) => {
|
||||
if (confirm(`Are you sure you want to delete ${variable.name}?`)) {
|
||||
variableStore.deleteVariable(variable.name, variable.scope);
|
||||
}
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
variableForm.value = {
|
||||
name: '',
|
||||
type: 'string',
|
||||
scope: 'process',
|
||||
description: '',
|
||||
isRequired: false
|
||||
};
|
||||
editingVariable.value = null;
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
showAddVariable.value = false;
|
||||
resetForm();
|
||||
};
|
||||
|
||||
const saveVariable = async (formData) => {
|
||||
try {
|
||||
// Create a new variable object
|
||||
const newVariable = {
|
||||
name: formData.name,
|
||||
type: formData.type,
|
||||
scope: formData.scope,
|
||||
description: formData.description,
|
||||
isRequired: formData.isRequired
|
||||
};
|
||||
|
||||
if (editingVariable.value) {
|
||||
// Update existing variable
|
||||
variableStore.updateVariable(
|
||||
editingVariable.value.name,
|
||||
newVariable,
|
||||
newVariable.scope
|
||||
);
|
||||
} else {
|
||||
// Add new variable
|
||||
variableStore.addVariable(newVariable);
|
||||
}
|
||||
|
||||
// Close modal and reset form
|
||||
closeModal();
|
||||
} catch (error) {
|
||||
console.error('Error saving variable:', error);
|
||||
// You might want to show an error message to the user here
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.variable-manager {
|
||||
@apply h-full flex flex-col;
|
||||
}
|
||||
|
||||
.variable-item {
|
||||
@apply transition-all duration-200;
|
||||
}
|
||||
|
||||
.variable-item:hover {
|
||||
@apply transform -translate-y-1;
|
||||
}
|
||||
</style>
|
255
doc/PROCESS_BUILDER_IMPROVEMENTS.md
Normal file
255
doc/PROCESS_BUILDER_IMPROVEMENTS.md
Normal file
@ -0,0 +1,255 @@
|
||||
# Process Builder Improvements
|
||||
|
||||
## Overview
|
||||
This document outlines the planned improvements for the Process Builder core components. The improvements are designed to be manageable and maintainable while adding essential functionality.
|
||||
|
||||
## Variable System
|
||||
|
||||
### 1. Global Variables
|
||||
```javascript
|
||||
{
|
||||
name: 'string',
|
||||
type: 'string|number|boolean|object|array',
|
||||
defaultValue: any,
|
||||
description: 'string',
|
||||
scope: 'global',
|
||||
isRequired: boolean,
|
||||
isReadOnly: boolean
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Process Variables
|
||||
```javascript
|
||||
{
|
||||
name: 'string',
|
||||
type: 'string|number|boolean|object|array',
|
||||
defaultValue: any,
|
||||
description: 'string',
|
||||
scope: 'process',
|
||||
isRequired: boolean,
|
||||
isReadOnly: boolean,
|
||||
direction: 'in|out|inout' // for process arguments
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Task/Form Arguments
|
||||
```javascript
|
||||
{
|
||||
name: 'string',
|
||||
type: 'string|number|boolean|object|array',
|
||||
defaultValue: any,
|
||||
description: 'string',
|
||||
direction: 'in|out|inout',
|
||||
isRequired: boolean,
|
||||
validation: {
|
||||
rules: [],
|
||||
customValidation: 'string' // custom validation script
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Core Components Improvements
|
||||
|
||||
### 1. Start Event
|
||||
```javascript
|
||||
{
|
||||
type: 'start',
|
||||
data: {
|
||||
description: 'Process start point',
|
||||
triggerType: 'manual', // manual, scheduled
|
||||
schedule: null, // for scheduled triggers
|
||||
variables: {
|
||||
input: [], // process input arguments
|
||||
output: [] // process output arguments
|
||||
},
|
||||
globalVariables: [] // global variables used in this process
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. End Event
|
||||
```javascript
|
||||
{
|
||||
type: 'end',
|
||||
data: {
|
||||
description: 'Process end point',
|
||||
resultType: 'success', // success, error
|
||||
variables: {
|
||||
input: [], // variables required for end event
|
||||
output: [] // variables to be returned
|
||||
},
|
||||
returnValues: [] // values to return to calling process
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Task
|
||||
```javascript
|
||||
{
|
||||
type: 'task',
|
||||
data: {
|
||||
description: 'A general task',
|
||||
assignee: '',
|
||||
taskType: 'manual', // manual, automated
|
||||
priority: 'medium', // low, medium, high
|
||||
dueDate: null,
|
||||
variables: {
|
||||
input: [], // task input arguments
|
||||
output: [] // task output arguments
|
||||
},
|
||||
notifications: {
|
||||
onAssign: true,
|
||||
onComplete: true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Form Task
|
||||
```javascript
|
||||
{
|
||||
type: 'form',
|
||||
data: {
|
||||
description: 'Form submission task',
|
||||
formId: null,
|
||||
formName: null,
|
||||
formSettings: {
|
||||
allowDraft: true,
|
||||
autoSave: true
|
||||
},
|
||||
variables: {
|
||||
input: [], // form input arguments
|
||||
output: [] // form output arguments
|
||||
},
|
||||
dataMapping: {
|
||||
input: [], // map process variables to form
|
||||
output: [] // map form to process variables
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Gateway
|
||||
```javascript
|
||||
{
|
||||
type: 'gateway',
|
||||
data: {
|
||||
description: 'Decision gateway',
|
||||
conditions: [],
|
||||
defaultPath: 'Default',
|
||||
gatewayType: 'exclusive', // exclusive, parallel
|
||||
variables: {
|
||||
input: [], // variables needed for conditions
|
||||
output: [] // variables to pass to next node
|
||||
},
|
||||
timeout: {
|
||||
enabled: false,
|
||||
duration: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## New Core Components
|
||||
|
||||
### 1. Script Task
|
||||
```javascript
|
||||
{
|
||||
type: 'script',
|
||||
data: {
|
||||
description: 'Execute custom script',
|
||||
scriptType: 'javascript',
|
||||
script: '',
|
||||
variables: {
|
||||
input: [], // script input arguments
|
||||
output: [] // script output arguments
|
||||
},
|
||||
timeout: 30 // seconds
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Priority
|
||||
|
||||
### Phase 1 - Essential Improvements
|
||||
1. Implement basic variable system
|
||||
- Global variables
|
||||
- Process variables
|
||||
- Task/Form arguments
|
||||
2. Add basic trigger types to Start Event
|
||||
3. Add result types to End Event
|
||||
4. Add task priorities and due dates
|
||||
5. Add form settings for drafts and auto-save
|
||||
|
||||
### Phase 2 - Enhanced Features
|
||||
1. Add variable validation system
|
||||
2. Add data mapping for forms
|
||||
3. Add script task component
|
||||
4. Add timeout handling
|
||||
5. Add notifications system
|
||||
|
||||
### Phase 3 - Advanced Features
|
||||
1. Add subprocess component
|
||||
2. Add advanced gateway conditions
|
||||
3. Add process templates
|
||||
4. Add process versioning
|
||||
5. Add process analytics
|
||||
|
||||
## Variable System Features
|
||||
|
||||
### 1. Variable Types
|
||||
- String
|
||||
- Number
|
||||
- Boolean
|
||||
- Object
|
||||
- Array
|
||||
- Date
|
||||
- File
|
||||
- Custom types
|
||||
|
||||
### 2. Variable Scopes
|
||||
- Global (accessible across all processes)
|
||||
- Process (accessible within a process)
|
||||
- Task/Form (accessible within a task/form)
|
||||
- Local (accessible within a script)
|
||||
|
||||
### 3. Variable Operations
|
||||
- Create/Delete
|
||||
- Read/Write
|
||||
- Copy/Move
|
||||
- Transform
|
||||
- Validate
|
||||
- Persist
|
||||
|
||||
### 4. Variable Passing
|
||||
- Process to Process
|
||||
- Task to Task
|
||||
- Form to Process
|
||||
- Script to Process
|
||||
- Gateway Conditions
|
||||
|
||||
## Notes
|
||||
- Keep improvements focused on essential functionality
|
||||
- Maintain backward compatibility
|
||||
- Ensure easy maintenance
|
||||
- Document all new features
|
||||
- Add proper validation
|
||||
- Include error handling
|
||||
- Implement proper variable scoping
|
||||
- Add variable type checking
|
||||
- Include variable persistence
|
||||
|
||||
## Future Considerations
|
||||
- Process templates
|
||||
- Process versioning
|
||||
- Process analytics
|
||||
- Advanced notifications
|
||||
- Custom validations
|
||||
- Process documentation
|
||||
- Process testing
|
||||
- Process deployment
|
||||
- Variable encryption
|
||||
- Variable versioning
|
||||
- Variable dependencies
|
||||
|
||||
Last updated: June 10, 2024
|
@ -1,11 +1,13 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, computed, shallowRef, onUnmounted } from 'vue';
|
||||
import { useProcessBuilderStore } from '~/stores/processBuilder';
|
||||
import { useVariableStore } from '~/stores/variableStore';
|
||||
import { useRouter } from 'vue-router';
|
||||
import ProcessFlowCanvas from '~/components/process-flow/ProcessFlowCanvas.vue';
|
||||
import ProcessBuilderComponents from '~/components/process-flow/ProcessBuilderComponents.vue';
|
||||
import FormSelector from '~/components/process-flow/FormSelector.vue';
|
||||
import GatewayConditionManager from '~/components/process-flow/GatewayConditionManager.vue';
|
||||
import VariableManager from '~/components/process-flow/VariableManager.vue';
|
||||
import { onBeforeRouteLeave } from 'vue-router';
|
||||
|
||||
// Define page meta
|
||||
@ -20,6 +22,7 @@ definePageMeta({
|
||||
// Initialize the store and router
|
||||
const processStore = useProcessBuilderStore();
|
||||
const router = useRouter();
|
||||
const variableStore = useVariableStore();
|
||||
|
||||
// Track selected node local state (syncs with store)
|
||||
// Using shallowRef to avoid making Vue components reactive
|
||||
@ -169,6 +172,15 @@ const nodeDefaultPath = computed({
|
||||
}
|
||||
});
|
||||
|
||||
// Computed for gateway available variables
|
||||
const gatewayAvailableVariables = computed(() => {
|
||||
return variableStore.getAllVariables.process.map(v => ({
|
||||
name: v.name,
|
||||
label: v.name, // or v.description || v.name
|
||||
type: v.type
|
||||
}));
|
||||
});
|
||||
|
||||
// Handle node selection
|
||||
const onNodeSelected = (node) => {
|
||||
selectedNodeData.value = JSON.parse(JSON.stringify(node));
|
||||
@ -560,136 +572,59 @@ const onConditionsUpdated = (conditions) => {
|
||||
</div>
|
||||
|
||||
<!-- Right Panel - Properties -->
|
||||
<div class="w-72 border-l border-gray-200 flex flex-col overflow-hidden">
|
||||
<div class="w-80 border-l border-gray-200 flex flex-col overflow-hidden">
|
||||
<div class="bg-gray-100 p-3 flex items-center justify-between border-b border-gray-200">
|
||||
<h2 class="text-sm font-medium text-gray-700">Properties</h2>
|
||||
</div>
|
||||
<div class="flex-1 overflow-y-auto p-4 bg-white">
|
||||
<!-- No selection state -->
|
||||
<div v-if="!selectedNodeData && !selectedEdgeData" class="text-gray-500 text-center py-8">
|
||||
<Icon name="material-symbols:touch-app" class="w-12 h-12 mx-auto mb-2" />
|
||||
<p>Select a node or connection to edit its properties</p>
|
||||
</div>
|
||||
|
||||
<!-- Node properties -->
|
||||
<div v-else-if="selectedNodeData" class="space-y-4">
|
||||
<h3 class="text-sm font-medium text-gray-700 mb-2">{{ selectedNodeData.type.charAt(0).toUpperCase() + selectedNodeData.type.slice(1) }} Node Properties</h3>
|
||||
|
||||
<!-- Common properties for all nodes -->
|
||||
<div class="space-y-3">
|
||||
<FormKit
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
<!-- Show variable manager when no node is selected -->
|
||||
<VariableManager v-if="!selectedNodeData" />
|
||||
|
||||
<!-- Show node properties when a node is selected -->
|
||||
<div v-else class="p-4 space-y-4">
|
||||
<!-- Node Label -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Label</label>
|
||||
<input
|
||||
v-model="nodeLabel"
|
||||
type="text"
|
||||
label="Label"
|
||||
placeholder="Node label"
|
||||
/>
|
||||
|
||||
<FormKit
|
||||
v-model="nodeDescription"
|
||||
type="textarea"
|
||||
label="Description"
|
||||
placeholder="Enter description"
|
||||
:rows="3"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Task specific properties -->
|
||||
<div v-if="selectedNodeData.type === 'task'" class="space-y-3">
|
||||
<FormKit
|
||||
|
||||
<!-- Node Description -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Description</label>
|
||||
<textarea
|
||||
v-model="nodeDescription"
|
||||
rows="2"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Node Type Specific Properties -->
|
||||
<div v-if="selectedNodeData.type === 'task'">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Assignee</label>
|
||||
<input
|
||||
v-model="nodeAssignee"
|
||||
type="text"
|
||||
label="Assignee"
|
||||
placeholder="Enter assignee"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Form specific properties -->
|
||||
<div v-if="selectedNodeData.type === 'form'" class="space-y-3">
|
||||
<FormSelector
|
||||
v-model="selectedNodeData.data.formId"
|
||||
@select="onFormSelected"
|
||||
/>
|
||||
|
||||
<!-- Form Selection for Form Nodes -->
|
||||
<div v-if="selectedNodeData.type === 'form'">
|
||||
<FormSelector @select="onFormSelected" />
|
||||
</div>
|
||||
|
||||
<!-- Script specific properties -->
|
||||
<div v-if="selectedNodeData.type === 'script'" class="space-y-3">
|
||||
<FormKit
|
||||
v-model="nodeLanguage"
|
||||
type="select"
|
||||
label="Language"
|
||||
:options="['JavaScript', 'Python', 'PHP']"
|
||||
/>
|
||||
|
||||
<FormKit
|
||||
v-if="selectedNodeData.data.script !== undefined"
|
||||
v-model="selectedNodeData.data.script"
|
||||
type="textarea"
|
||||
label="Script"
|
||||
placeholder="Enter script code"
|
||||
:rows="5"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Gateway specific properties -->
|
||||
<div v-if="selectedNodeData.type === 'gateway'" class="space-y-3">
|
||||
<FormKit
|
||||
v-model="nodeDefaultPath"
|
||||
type="text"
|
||||
label="Default Path Label"
|
||||
placeholder="Default"
|
||||
/>
|
||||
|
||||
|
||||
<!-- Gateway Conditions -->
|
||||
<div v-if="selectedNodeData.type === 'gateway'">
|
||||
<GatewayConditionManager
|
||||
v-model="nodeConditions"
|
||||
:gateway-id="selectedNodeData.id"
|
||||
@update:modelValue="handleConditionUpdate"
|
||||
:conditions="selectedNodeData.data.conditions"
|
||||
@update="onConditionsUpdated"
|
||||
:available-variables="gatewayAvailableVariables"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Delete button -->
|
||||
<div class="pt-4 border-t border-gray-200 mt-4">
|
||||
<RsButton @click="deleteNode" variant="danger" size="sm" class="w-full">
|
||||
<Icon name="material-symbols:delete" class="mr-1" />
|
||||
Delete Node
|
||||
</RsButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edge properties -->
|
||||
<div v-else-if="selectedEdgeData" class="space-y-4">
|
||||
<h3 class="text-sm font-medium text-gray-700 mb-2">Connection Properties</h3>
|
||||
|
||||
<div class="space-y-3">
|
||||
<FormKit
|
||||
:model-value="selectedEdgeData.label"
|
||||
@input="updateEdgeLabel"
|
||||
type="text"
|
||||
label="Label"
|
||||
placeholder="Connection label"
|
||||
/>
|
||||
|
||||
<div class="pt-4">
|
||||
<div class="mb-2 text-sm text-gray-500">Connection Details</div>
|
||||
<div class="p-3 bg-gray-50 rounded-md border border-gray-200 text-sm">
|
||||
<div class="mb-1">
|
||||
<span class="font-medium">From:</span>
|
||||
{{ selectedEdgeData.sourceNode?.label || selectedEdgeData.source }}
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-medium">To:</span>
|
||||
{{ selectedEdgeData.targetNode?.label || selectedEdgeData.target }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete button -->
|
||||
<div class="pt-4 border-t border-gray-200 mt-4">
|
||||
<RsButton @click="deleteEdge" variant="danger" size="sm" class="w-full">
|
||||
<Icon name="material-symbols:delete" class="mr-1" />
|
||||
Delete Connection
|
||||
</RsButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -136,10 +136,10 @@
|
||||
"$ref": "#/definitions/audit"
|
||||
}
|
||||
},
|
||||
"userrole": {
|
||||
"forms": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/userrole"
|
||||
"$ref": "#/definitions/form"
|
||||
}
|
||||
},
|
||||
"processes": {
|
||||
@ -148,17 +148,17 @@
|
||||
"$ref": "#/definitions/process"
|
||||
}
|
||||
},
|
||||
"forms": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/form"
|
||||
}
|
||||
},
|
||||
"assignedTasks": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/task"
|
||||
}
|
||||
},
|
||||
"userrole": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/userrole"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -453,8 +453,15 @@
|
||||
],
|
||||
"format": "date-time"
|
||||
},
|
||||
"process": {
|
||||
"$ref": "#/definitions/process"
|
||||
"assignee": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"form": {
|
||||
"anyOf": [
|
||||
@ -466,15 +473,8 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"assignee": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
"process": {
|
||||
"$ref": "#/definitions/process"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,10 +40,10 @@ model user {
|
||||
userCreatedDate DateTime? @db.DateTime(0)
|
||||
userModifiedDate DateTime? @db.DateTime(0)
|
||||
audit audit[]
|
||||
userrole userrole[]
|
||||
processes process[] @relation("ProcessCreator")
|
||||
forms form[] @relation("FormCreator")
|
||||
processes process[] @relation("ProcessCreator")
|
||||
assignedTasks task[] @relation("TaskAssignee")
|
||||
userrole userrole[]
|
||||
}
|
||||
|
||||
model role {
|
||||
@ -80,39 +80,33 @@ model userrole {
|
||||
@@index([userRoleUserID], map: "FK_userrole_user")
|
||||
}
|
||||
|
||||
// New models for Form Builder
|
||||
model form {
|
||||
formID Int @id @default(autoincrement())
|
||||
formUUID String @unique @db.VarChar(36)
|
||||
formName String @db.VarChar(255)
|
||||
formDescription String? @db.Text
|
||||
formComponents Json @db.Json
|
||||
formComponents Json
|
||||
formStatus String @default("active") @db.VarChar(50)
|
||||
formCreatedBy Int?
|
||||
formCreatedDate DateTime @default(now()) @db.DateTime(0)
|
||||
formModifiedDate DateTime? @updatedAt @db.DateTime(0)
|
||||
|
||||
// Relations
|
||||
creator user? @relation("FormCreator", fields: [formCreatedBy], references: [userID])
|
||||
formTasks task[] @relation("FormTask")
|
||||
|
||||
@@index([formCreatedBy], map: "FK_form_creator")
|
||||
}
|
||||
|
||||
// New models for Process Builder
|
||||
model process {
|
||||
processID Int @id @default(autoincrement())
|
||||
processUUID String @unique @db.VarChar(36)
|
||||
processName String @db.VarChar(255)
|
||||
processDescription String? @db.Text
|
||||
processDefinition Json @db.Json
|
||||
processDefinition Json
|
||||
processVersion Int @default(1)
|
||||
processStatus String @default("draft") @db.VarChar(50)
|
||||
processCreatedBy Int?
|
||||
processCreatedDate DateTime @default(now()) @db.DateTime(0)
|
||||
processModifiedDate DateTime? @updatedAt @db.DateTime(0)
|
||||
|
||||
// Relations
|
||||
creator user? @relation("ProcessCreator", fields: [processCreatedBy], references: [userID])
|
||||
tasks task[]
|
||||
|
||||
@ -125,18 +119,16 @@ model task {
|
||||
taskNodeId String @db.VarChar(255)
|
||||
taskName String @db.VarChar(255)
|
||||
taskType String @db.VarChar(50)
|
||||
taskData Json? @db.Json
|
||||
taskData Json?
|
||||
taskProcessId Int
|
||||
taskFormId Int?
|
||||
taskAssigneeId Int?
|
||||
taskStatus String @default("pending") @db.VarChar(50)
|
||||
taskCreatedDate DateTime @default(now()) @db.DateTime(0)
|
||||
taskModifiedDate DateTime? @updatedAt @db.DateTime(0)
|
||||
|
||||
// Relations
|
||||
process process @relation(fields: [taskProcessId], references: [processID])
|
||||
form form? @relation("FormTask", fields: [taskFormId], references: [formID])
|
||||
assignee user? @relation("TaskAssignee", fields: [taskAssigneeId], references: [userID])
|
||||
form form? @relation("FormTask", fields: [taskFormId], references: [formID])
|
||||
process process @relation(fields: [taskProcessId], references: [processID])
|
||||
|
||||
@@index([taskProcessId], map: "FK_task_process")
|
||||
@@index([taskFormId], map: "FK_task_form")
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { useVariableStore } from './variableStore';
|
||||
|
||||
export const useProcessBuilderStore = defineStore('processBuilder', {
|
||||
state: () => ({
|
||||
@ -63,35 +64,52 @@ export const useProcessBuilderStore = defineStore('processBuilder', {
|
||||
* Create a new process
|
||||
*/
|
||||
createProcess(name, description = '') {
|
||||
const newProcess = {
|
||||
id: uuidv4(),
|
||||
const process = {
|
||||
id: crypto.randomUUID(),
|
||||
name,
|
||||
description,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
nodes: [],
|
||||
edges: []
|
||||
edges: [],
|
||||
variables: {},
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
this.processes.push(newProcess);
|
||||
this.setCurrentProcess(newProcess.id);
|
||||
this.saveToHistory('Create process');
|
||||
this.processes.push(process);
|
||||
this.currentProcess = process;
|
||||
this.unsavedChanges = true;
|
||||
|
||||
return newProcess;
|
||||
// Clear any existing variables
|
||||
useVariableStore().clearProcessVariables();
|
||||
|
||||
return process;
|
||||
},
|
||||
|
||||
/**
|
||||
* Load a process
|
||||
*/
|
||||
loadProcess(processId) {
|
||||
const process = this.processes.find(p => p.id === processId);
|
||||
if (process) {
|
||||
this.currentProcess = JSON.parse(JSON.stringify(process)); // Deep clone
|
||||
this.selectedNodeId = null;
|
||||
this.selectedEdgeId = null;
|
||||
this.clearHistory();
|
||||
this.unsavedChanges = false;
|
||||
async loadProcess(processId) {
|
||||
try {
|
||||
// TODO: Implement API call to load process
|
||||
// For now, just load from local state
|
||||
const process = this.processes.find(p => p.id === processId);
|
||||
if (process) {
|
||||
this.currentProcess = process;
|
||||
|
||||
// Load variables into variable store
|
||||
if (process.variables) {
|
||||
const variableStore = useVariableStore();
|
||||
Object.entries(process.variables).forEach(([name, variable]) => {
|
||||
variableStore.addVariable(variable);
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('Error loading process:', error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
@ -112,14 +130,30 @@ export const useProcessBuilderStore = defineStore('processBuilder', {
|
||||
/**
|
||||
* Save the current process
|
||||
*/
|
||||
saveProcess() {
|
||||
async saveProcess() {
|
||||
if (!this.currentProcess) return;
|
||||
|
||||
const index = this.processes.findIndex(p => p.id === this.currentProcess.id);
|
||||
if (index !== -1) {
|
||||
this.currentProcess.updatedAt = new Date().toISOString();
|
||||
this.processes[index] = JSON.parse(JSON.stringify(this.currentProcess)); // Deep clone
|
||||
try {
|
||||
// Save process data
|
||||
const processData = {
|
||||
...this.currentProcess,
|
||||
variables: useVariableStore().getAllVariables.process
|
||||
};
|
||||
|
||||
// TODO: Implement API call to save process
|
||||
// For now, just update local state
|
||||
const index = this.processes.findIndex(p => p.id === this.currentProcess.id);
|
||||
if (index !== -1) {
|
||||
this.processes[index] = processData;
|
||||
} else {
|
||||
this.processes.push(processData);
|
||||
}
|
||||
|
||||
this.unsavedChanges = false;
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error saving process:', error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
|
96
stores/variableStore.js
Normal file
96
stores/variableStore.js
Normal file
@ -0,0 +1,96 @@
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
export const useVariableStore = defineStore('variables', {
|
||||
state: () => ({
|
||||
// Global variables accessible across all processes
|
||||
globalVariables: [],
|
||||
|
||||
// Current process variables
|
||||
processVariables: [],
|
||||
|
||||
// Variables for the currently selected node
|
||||
nodeVariables: {
|
||||
input: [],
|
||||
output: []
|
||||
}
|
||||
}),
|
||||
|
||||
getters: {
|
||||
// Get all variables
|
||||
getAllVariables: (state) => {
|
||||
return {
|
||||
global: state.globalVariables,
|
||||
process: state.processVariables,
|
||||
node: state.nodeVariables
|
||||
};
|
||||
},
|
||||
|
||||
// Get variable by name and scope
|
||||
getVariable: (state) => (name, scope = 'process') => {
|
||||
const variables = scope === 'global'
|
||||
? state.globalVariables
|
||||
: state.processVariables;
|
||||
return variables.find(v => v.name === name);
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
// Add a new variable
|
||||
addVariable(variable) {
|
||||
const { scope = 'process' } = variable;
|
||||
|
||||
if (scope === 'global') {
|
||||
this.globalVariables.push({
|
||||
...variable,
|
||||
scope: 'global'
|
||||
});
|
||||
} else {
|
||||
this.processVariables.push({
|
||||
...variable,
|
||||
scope: 'process'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Update an existing variable
|
||||
updateVariable(name, updates, scope = 'process') {
|
||||
const variables = scope === 'global'
|
||||
? this.globalVariables
|
||||
: this.processVariables;
|
||||
|
||||
const index = variables.findIndex(v => v.name === name);
|
||||
if (index !== -1) {
|
||||
variables[index] = {
|
||||
...variables[index],
|
||||
...updates
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Delete a variable
|
||||
deleteVariable(name, scope = 'process') {
|
||||
const variables = scope === 'global'
|
||||
? this.globalVariables
|
||||
: this.processVariables;
|
||||
|
||||
const index = variables.findIndex(v => v.name === name);
|
||||
if (index !== -1) {
|
||||
variables.splice(index, 1);
|
||||
}
|
||||
},
|
||||
|
||||
// Set node variables
|
||||
setNodeVariables(variables) {
|
||||
this.nodeVariables = variables;
|
||||
},
|
||||
|
||||
// Clear process variables (when switching processes)
|
||||
clearProcessVariables() {
|
||||
this.processVariables = [];
|
||||
this.nodeVariables = {
|
||||
input: [],
|
||||
output: []
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user