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
|
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.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
|
// Add a component directly via click
|
||||||
|
@ -269,10 +269,11 @@ const onDeleteKeyPress = () => {
|
|||||||
// Handle drop event
|
// Handle drop event
|
||||||
const onDrop = (event) => {
|
const onDrop = (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get the dragged component data
|
// 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;
|
if (!componentData) return;
|
||||||
|
|
||||||
// Get the Vue Flow wrapper element
|
// Get the Vue Flow wrapper element
|
||||||
@ -296,7 +297,6 @@ const onDrop = (event) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// console.log('Adding new node:', newNode);
|
|
||||||
addNodes([newNode]);
|
addNodes([newNode]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error handling drop:', error);
|
console.error('Error handling drop:', error);
|
||||||
@ -306,7 +306,8 @@ const onDrop = (event) => {
|
|||||||
// Handle drag over
|
// Handle drag over
|
||||||
const onDragOver = (event) => {
|
const onDragOver = (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.dataTransfer.dropEffect = 'move';
|
event.stopPropagation();
|
||||||
|
event.dataTransfer.dropEffect = 'copy';
|
||||||
};
|
};
|
||||||
</script>
|
</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>
|
<script setup>
|
||||||
import { ref, onMounted, computed, shallowRef, onUnmounted } from 'vue';
|
import { ref, onMounted, computed, shallowRef, onUnmounted } from 'vue';
|
||||||
import { useProcessBuilderStore } from '~/stores/processBuilder';
|
import { useProcessBuilderStore } from '~/stores/processBuilder';
|
||||||
|
import { useVariableStore } from '~/stores/variableStore';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import ProcessFlowCanvas from '~/components/process-flow/ProcessFlowCanvas.vue';
|
import ProcessFlowCanvas from '~/components/process-flow/ProcessFlowCanvas.vue';
|
||||||
import ProcessBuilderComponents from '~/components/process-flow/ProcessBuilderComponents.vue';
|
import ProcessBuilderComponents from '~/components/process-flow/ProcessBuilderComponents.vue';
|
||||||
import FormSelector from '~/components/process-flow/FormSelector.vue';
|
import FormSelector from '~/components/process-flow/FormSelector.vue';
|
||||||
import GatewayConditionManager from '~/components/process-flow/GatewayConditionManager.vue';
|
import GatewayConditionManager from '~/components/process-flow/GatewayConditionManager.vue';
|
||||||
|
import VariableManager from '~/components/process-flow/VariableManager.vue';
|
||||||
import { onBeforeRouteLeave } from 'vue-router';
|
import { onBeforeRouteLeave } from 'vue-router';
|
||||||
|
|
||||||
// Define page meta
|
// Define page meta
|
||||||
@ -20,6 +22,7 @@ definePageMeta({
|
|||||||
// Initialize the store and router
|
// Initialize the store and router
|
||||||
const processStore = useProcessBuilderStore();
|
const processStore = useProcessBuilderStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const variableStore = useVariableStore();
|
||||||
|
|
||||||
// Track selected node local state (syncs with store)
|
// Track selected node local state (syncs with store)
|
||||||
// Using shallowRef to avoid making Vue components reactive
|
// 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
|
// Handle node selection
|
||||||
const onNodeSelected = (node) => {
|
const onNodeSelected = (node) => {
|
||||||
selectedNodeData.value = JSON.parse(JSON.stringify(node));
|
selectedNodeData.value = JSON.parse(JSON.stringify(node));
|
||||||
@ -560,136 +572,59 @@ const onConditionsUpdated = (conditions) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Right Panel - Properties -->
|
<!-- 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">
|
<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>
|
<h2 class="text-sm font-medium text-gray-700">Properties</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1 overflow-y-auto p-4 bg-white">
|
<div class="flex-1 overflow-y-auto">
|
||||||
<!-- No selection state -->
|
<!-- Show variable manager when no node is selected -->
|
||||||
<div v-if="!selectedNodeData && !selectedEdgeData" class="text-gray-500 text-center py-8">
|
<VariableManager v-if="!selectedNodeData" />
|
||||||
<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>
|
<!-- Show node properties when a node is selected -->
|
||||||
</div>
|
<div v-else class="p-4 space-y-4">
|
||||||
|
<!-- Node Label -->
|
||||||
<!-- Node properties -->
|
<div>
|
||||||
<div v-else-if="selectedNodeData" class="space-y-4">
|
<label class="block text-sm font-medium text-gray-700 mb-1">Label</label>
|
||||||
<h3 class="text-sm font-medium text-gray-700 mb-2">{{ selectedNodeData.type.charAt(0).toUpperCase() + selectedNodeData.type.slice(1) }} Node Properties</h3>
|
<input
|
||||||
|
|
||||||
<!-- Common properties for all nodes -->
|
|
||||||
<div class="space-y-3">
|
|
||||||
<FormKit
|
|
||||||
v-model="nodeLabel"
|
v-model="nodeLabel"
|
||||||
type="text"
|
type="text"
|
||||||
label="Label"
|
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"
|
||||||
placeholder="Node label"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormKit
|
|
||||||
v-model="nodeDescription"
|
|
||||||
type="textarea"
|
|
||||||
label="Description"
|
|
||||||
placeholder="Enter description"
|
|
||||||
:rows="3"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Task specific properties -->
|
<!-- Node Description -->
|
||||||
<div v-if="selectedNodeData.type === 'task'" class="space-y-3">
|
<div>
|
||||||
<FormKit
|
<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"
|
v-model="nodeAssignee"
|
||||||
type="text"
|
type="text"
|
||||||
label="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"
|
||||||
placeholder="Enter assignee"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Form specific properties -->
|
<!-- Form Selection for Form Nodes -->
|
||||||
<div v-if="selectedNodeData.type === 'form'" class="space-y-3">
|
<div v-if="selectedNodeData.type === 'form'">
|
||||||
<FormSelector
|
<FormSelector @select="onFormSelected" />
|
||||||
v-model="selectedNodeData.data.formId"
|
|
||||||
@select="onFormSelected"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Script specific properties -->
|
<!-- Gateway Conditions -->
|
||||||
<div v-if="selectedNodeData.type === 'script'" class="space-y-3">
|
<div v-if="selectedNodeData.type === 'gateway'">
|
||||||
<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"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<GatewayConditionManager
|
<GatewayConditionManager
|
||||||
v-model="nodeConditions"
|
:conditions="selectedNodeData.data.conditions"
|
||||||
:gateway-id="selectedNodeData.id"
|
@update="onConditionsUpdated"
|
||||||
@update:modelValue="handleConditionUpdate"
|
:available-variables="gatewayAvailableVariables"
|
||||||
/>
|
/>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -136,10 +136,10 @@
|
|||||||
"$ref": "#/definitions/audit"
|
"$ref": "#/definitions/audit"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"userrole": {
|
"forms": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/userrole"
|
"$ref": "#/definitions/form"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"processes": {
|
"processes": {
|
||||||
@ -148,17 +148,17 @@
|
|||||||
"$ref": "#/definitions/process"
|
"$ref": "#/definitions/process"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"forms": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/form"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"assignedTasks": {
|
"assignedTasks": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/task"
|
"$ref": "#/definitions/task"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"userrole": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/userrole"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -453,8 +453,15 @@
|
|||||||
],
|
],
|
||||||
"format": "date-time"
|
"format": "date-time"
|
||||||
},
|
},
|
||||||
"process": {
|
"assignee": {
|
||||||
"$ref": "#/definitions/process"
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/user"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"form": {
|
"form": {
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
@ -466,15 +473,8 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"assignee": {
|
"process": {
|
||||||
"anyOf": [
|
"$ref": "#/definitions/process"
|
||||||
{
|
|
||||||
"$ref": "#/definitions/user"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "null"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,10 +40,10 @@ model user {
|
|||||||
userCreatedDate DateTime? @db.DateTime(0)
|
userCreatedDate DateTime? @db.DateTime(0)
|
||||||
userModifiedDate DateTime? @db.DateTime(0)
|
userModifiedDate DateTime? @db.DateTime(0)
|
||||||
audit audit[]
|
audit audit[]
|
||||||
userrole userrole[]
|
|
||||||
processes process[] @relation("ProcessCreator")
|
|
||||||
forms form[] @relation("FormCreator")
|
forms form[] @relation("FormCreator")
|
||||||
|
processes process[] @relation("ProcessCreator")
|
||||||
assignedTasks task[] @relation("TaskAssignee")
|
assignedTasks task[] @relation("TaskAssignee")
|
||||||
|
userrole userrole[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model role {
|
model role {
|
||||||
@ -80,39 +80,33 @@ model userrole {
|
|||||||
@@index([userRoleUserID], map: "FK_userrole_user")
|
@@index([userRoleUserID], map: "FK_userrole_user")
|
||||||
}
|
}
|
||||||
|
|
||||||
// New models for Form Builder
|
|
||||||
model form {
|
model form {
|
||||||
formID Int @id @default(autoincrement())
|
formID Int @id @default(autoincrement())
|
||||||
formUUID String @unique @db.VarChar(36)
|
formUUID String @unique @db.VarChar(36)
|
||||||
formName String @db.VarChar(255)
|
formName String @db.VarChar(255)
|
||||||
formDescription String? @db.Text
|
formDescription String? @db.Text
|
||||||
formComponents Json @db.Json
|
formComponents Json
|
||||||
formStatus String @default("active") @db.VarChar(50)
|
formStatus String @default("active") @db.VarChar(50)
|
||||||
formCreatedBy Int?
|
formCreatedBy Int?
|
||||||
formCreatedDate DateTime @default(now()) @db.DateTime(0)
|
formCreatedDate DateTime @default(now()) @db.DateTime(0)
|
||||||
formModifiedDate DateTime? @updatedAt @db.DateTime(0)
|
formModifiedDate DateTime? @updatedAt @db.DateTime(0)
|
||||||
|
|
||||||
// Relations
|
|
||||||
creator user? @relation("FormCreator", fields: [formCreatedBy], references: [userID])
|
creator user? @relation("FormCreator", fields: [formCreatedBy], references: [userID])
|
||||||
formTasks task[] @relation("FormTask")
|
formTasks task[] @relation("FormTask")
|
||||||
|
|
||||||
@@index([formCreatedBy], map: "FK_form_creator")
|
@@index([formCreatedBy], map: "FK_form_creator")
|
||||||
}
|
}
|
||||||
|
|
||||||
// New models for Process Builder
|
|
||||||
model process {
|
model process {
|
||||||
processID Int @id @default(autoincrement())
|
processID Int @id @default(autoincrement())
|
||||||
processUUID String @unique @db.VarChar(36)
|
processUUID String @unique @db.VarChar(36)
|
||||||
processName String @db.VarChar(255)
|
processName String @db.VarChar(255)
|
||||||
processDescription String? @db.Text
|
processDescription String? @db.Text
|
||||||
processDefinition Json @db.Json
|
processDefinition Json
|
||||||
processVersion Int @default(1)
|
processVersion Int @default(1)
|
||||||
processStatus String @default("draft") @db.VarChar(50)
|
processStatus String @default("draft") @db.VarChar(50)
|
||||||
processCreatedBy Int?
|
processCreatedBy Int?
|
||||||
processCreatedDate DateTime @default(now()) @db.DateTime(0)
|
processCreatedDate DateTime @default(now()) @db.DateTime(0)
|
||||||
processModifiedDate DateTime? @updatedAt @db.DateTime(0)
|
processModifiedDate DateTime? @updatedAt @db.DateTime(0)
|
||||||
|
|
||||||
// Relations
|
|
||||||
creator user? @relation("ProcessCreator", fields: [processCreatedBy], references: [userID])
|
creator user? @relation("ProcessCreator", fields: [processCreatedBy], references: [userID])
|
||||||
tasks task[]
|
tasks task[]
|
||||||
|
|
||||||
@ -125,18 +119,16 @@ model task {
|
|||||||
taskNodeId String @db.VarChar(255)
|
taskNodeId String @db.VarChar(255)
|
||||||
taskName String @db.VarChar(255)
|
taskName String @db.VarChar(255)
|
||||||
taskType String @db.VarChar(50)
|
taskType String @db.VarChar(50)
|
||||||
taskData Json? @db.Json
|
taskData Json?
|
||||||
taskProcessId Int
|
taskProcessId Int
|
||||||
taskFormId Int?
|
taskFormId Int?
|
||||||
taskAssigneeId Int?
|
taskAssigneeId Int?
|
||||||
taskStatus String @default("pending") @db.VarChar(50)
|
taskStatus String @default("pending") @db.VarChar(50)
|
||||||
taskCreatedDate DateTime @default(now()) @db.DateTime(0)
|
taskCreatedDate DateTime @default(now()) @db.DateTime(0)
|
||||||
taskModifiedDate DateTime? @updatedAt @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])
|
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([taskProcessId], map: "FK_task_process")
|
||||||
@@index([taskFormId], map: "FK_task_form")
|
@@index([taskFormId], map: "FK_task_form")
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import { useVariableStore } from './variableStore';
|
||||||
|
|
||||||
export const useProcessBuilderStore = defineStore('processBuilder', {
|
export const useProcessBuilderStore = defineStore('processBuilder', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
@ -63,35 +64,52 @@ export const useProcessBuilderStore = defineStore('processBuilder', {
|
|||||||
* Create a new process
|
* Create a new process
|
||||||
*/
|
*/
|
||||||
createProcess(name, description = '') {
|
createProcess(name, description = '') {
|
||||||
const newProcess = {
|
const process = {
|
||||||
id: uuidv4(),
|
id: crypto.randomUUID(),
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
updatedAt: new Date().toISOString(),
|
|
||||||
nodes: [],
|
nodes: [],
|
||||||
edges: []
|
edges: [],
|
||||||
|
variables: {},
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
updatedAt: new Date().toISOString()
|
||||||
};
|
};
|
||||||
|
|
||||||
this.processes.push(newProcess);
|
this.processes.push(process);
|
||||||
this.setCurrentProcess(newProcess.id);
|
this.currentProcess = process;
|
||||||
this.saveToHistory('Create process');
|
|
||||||
this.unsavedChanges = true;
|
this.unsavedChanges = true;
|
||||||
|
|
||||||
return newProcess;
|
// Clear any existing variables
|
||||||
|
useVariableStore().clearProcessVariables();
|
||||||
|
|
||||||
|
return process;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load a process
|
* Load a process
|
||||||
*/
|
*/
|
||||||
loadProcess(processId) {
|
async loadProcess(processId) {
|
||||||
const process = this.processes.find(p => p.id === processId);
|
try {
|
||||||
if (process) {
|
// TODO: Implement API call to load process
|
||||||
this.currentProcess = JSON.parse(JSON.stringify(process)); // Deep clone
|
// For now, just load from local state
|
||||||
this.selectedNodeId = null;
|
const process = this.processes.find(p => p.id === processId);
|
||||||
this.selectedEdgeId = null;
|
if (process) {
|
||||||
this.clearHistory();
|
this.currentProcess = process;
|
||||||
this.unsavedChanges = false;
|
|
||||||
|
// 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
|
* Save the current process
|
||||||
*/
|
*/
|
||||||
saveProcess() {
|
async saveProcess() {
|
||||||
if (!this.currentProcess) return;
|
if (!this.currentProcess) return;
|
||||||
|
|
||||||
const index = this.processes.findIndex(p => p.id === this.currentProcess.id);
|
try {
|
||||||
if (index !== -1) {
|
// Save process data
|
||||||
this.currentProcess.updatedAt = new Date().toISOString();
|
const processData = {
|
||||||
this.processes[index] = JSON.parse(JSON.stringify(this.currentProcess)); // Deep clone
|
...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;
|
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