- Introduced a preview modal for process templates, allowing users to view detailed information and flow before selection. - Updated the template card styles for improved visual appeal and user interaction, including hover effects and background gradients. - Enhanced the layout and structure of the template display, ensuring better organization of template details and action buttons. - Added computed properties for dynamic node styling based on template definitions, improving the visual representation of nodes in the preview. - Implemented responsive design adjustments to ensure usability across different screen sizes.
458 lines
12 KiB
Vue
458 lines
12 KiB
Vue
<template>
|
|
<div class="process-components">
|
|
<!-- Search Bar -->
|
|
<div class="search-container p-3 mb-2">
|
|
<div class="relative">
|
|
<input
|
|
type="text"
|
|
v-model="searchQuery"
|
|
placeholder="Search..."
|
|
class="w-full px-3 py-2 pl-9 bg-white border border-gray-300 rounded text-gray-700 text-sm placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
/>
|
|
<Icon
|
|
name="material-symbols:search"
|
|
class="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Core Components -->
|
|
<div class="component-category mb-6">
|
|
<h3 class="text-gray-700 text-sm font-medium px-3 mb-2">Core Components</h3>
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2 px-2">
|
|
<div
|
|
v-for="component in getComponentsByCategory('Core')"
|
|
:key="component.type"
|
|
class="component-item rounded p-3 flex flex-col items-center justify-center cursor-grab hover:bg-gray-100 transition-colors border border-gray-200 touch-manipulation"
|
|
:class="{ 'hidden': !matchesSearch(component) }"
|
|
draggable="true"
|
|
@dragstart="onDragStart($event, component)"
|
|
@click="addComponent(component)"
|
|
>
|
|
<Icon :name="component.icon" class="mb-2 w-6 h-6 text-gray-600" />
|
|
<span class="text-xs text-gray-600 text-center leading-tight">{{ component.name }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Design Elements -->
|
|
<div class="component-category mb-6">
|
|
<h3 class="text-gray-700 text-sm font-medium px-3 mb-2">Design Elements</h3>
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2 px-2">
|
|
<div
|
|
v-for="shape in getComponentsByCategory('Shape')"
|
|
:key="shape.type"
|
|
class="component-item rounded p-3 flex flex-col items-center justify-center cursor-grab hover:bg-blue-50 transition-colors border border-blue-200 touch-manipulation"
|
|
:class="{ 'hidden': !matchesSearch(shape) }"
|
|
draggable="true"
|
|
@dragstart="onDragStart($event, shape)"
|
|
@click="addComponent(shape)"
|
|
>
|
|
<Icon :name="shape.icon" class="mb-2 w-6 h-6 text-blue-500" />
|
|
<span class="text-xs text-blue-600 text-center leading-tight">{{ shape.name }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
const emit = defineEmits(['add-component']);
|
|
const searchQuery = ref('');
|
|
|
|
// Define basic process components aligned with BPMN notation
|
|
const availableComponents = [
|
|
// Core components
|
|
{
|
|
type: 'start',
|
|
name: 'Start Point',
|
|
category: 'Core',
|
|
icon: 'material-symbols:play-circle-outline',
|
|
description: 'Initiates the process flow',
|
|
defaultProps: {
|
|
label: 'Start',
|
|
data: {
|
|
description: 'Process start point',
|
|
shape: 'circle',
|
|
backgroundColor: '#dcfce7',
|
|
borderColor: '#10b981',
|
|
textColor: '#065f46'
|
|
}
|
|
}
|
|
},
|
|
{
|
|
type: 'end',
|
|
name: 'End Point',
|
|
category: 'Core',
|
|
icon: 'material-symbols:stop-circle-outline',
|
|
description: 'Terminates the process flow',
|
|
defaultProps: {
|
|
label: 'End',
|
|
data: {
|
|
description: 'Process end point',
|
|
shape: 'circle',
|
|
backgroundColor: '#fee2e2',
|
|
borderColor: '#dc2626',
|
|
textColor: '#991b1b'
|
|
}
|
|
}
|
|
},
|
|
|
|
{
|
|
type: 'form',
|
|
name: 'Form Task',
|
|
category: 'Core',
|
|
icon: 'material-symbols:description-outline',
|
|
description: 'Form to be filled out',
|
|
defaultProps: {
|
|
label: 'Form Task',
|
|
data: {
|
|
description: 'Form submission task',
|
|
formId: null,
|
|
formName: null,
|
|
shape: 'rectangle',
|
|
backgroundColor: '#faf5ff',
|
|
borderColor: '#9333ea',
|
|
textColor: '#6b21a8'
|
|
}
|
|
}
|
|
},
|
|
{
|
|
type: 'api',
|
|
name: 'API Call',
|
|
category: 'Core',
|
|
icon: 'material-symbols:api',
|
|
description: 'Make external API calls',
|
|
defaultProps: {
|
|
label: 'API Call',
|
|
data: {
|
|
description: 'External API call',
|
|
apiMethod: 'GET',
|
|
apiUrl: '',
|
|
requestBody: '',
|
|
headers: '{ "Content-Type": "application/json" }',
|
|
outputVariable: 'apiResponse',
|
|
continueOnError: false,
|
|
errorVariable: 'apiError',
|
|
shape: 'rectangle',
|
|
backgroundColor: '#eff6ff',
|
|
borderColor: '#3b82f6',
|
|
textColor: '#1e40af'
|
|
}
|
|
}
|
|
},
|
|
{
|
|
type: 'gateway',
|
|
name: 'Decision Point',
|
|
category: 'Core',
|
|
icon: 'material-symbols:call-split',
|
|
description: 'Decision point for flow control',
|
|
defaultProps: {
|
|
label: 'Decision Point',
|
|
data: {
|
|
description: 'Decision point for branching the workflow',
|
|
conditions: [],
|
|
defaultPath: 'Default',
|
|
shape: 'diamond',
|
|
backgroundColor: '#fff7ed',
|
|
borderColor: '#f97316',
|
|
textColor: '#c2410c'
|
|
}
|
|
}
|
|
},
|
|
{
|
|
type: 'notification',
|
|
name: 'Notification',
|
|
category: 'Core',
|
|
icon: 'material-symbols:notifications-outline',
|
|
description: 'Send notifications to users',
|
|
defaultProps: {
|
|
label: 'Notification',
|
|
data: {
|
|
description: 'Send notification to users',
|
|
notificationType: 'info',
|
|
recipientType: 'user',
|
|
recipientUser: '',
|
|
recipientRole: '',
|
|
recipientVariable: '',
|
|
recipientEmail: '',
|
|
subject: '',
|
|
message: '',
|
|
priority: 'medium',
|
|
deliveryOptions: {
|
|
inApp: true,
|
|
email: false,
|
|
sms: false
|
|
},
|
|
expiration: {
|
|
enabled: false,
|
|
value: 24,
|
|
unit: 'hours'
|
|
},
|
|
shape: 'rectangle',
|
|
backgroundColor: '#f0f9ff',
|
|
borderColor: '#0ea5e9',
|
|
textColor: '#0284c7'
|
|
}
|
|
}
|
|
},
|
|
{
|
|
type: 'business-rule',
|
|
name: 'Business Rule',
|
|
category: 'Core',
|
|
icon: 'material-symbols:rule',
|
|
description: 'Apply business rules to data',
|
|
defaultProps: {
|
|
label: 'Business Rule',
|
|
data: {
|
|
description: 'Apply business rules',
|
|
ruleGroups: [],
|
|
priority: 'medium',
|
|
shape: 'rectangle',
|
|
backgroundColor: '#fdf4ff',
|
|
borderColor: '#a855f7',
|
|
textColor: '#7c3aed'
|
|
}
|
|
}
|
|
},
|
|
{
|
|
type: 'script',
|
|
name: 'Script Task',
|
|
category: 'Core',
|
|
icon: 'material-symbols:code',
|
|
description: 'Execute JavaScript to transform data',
|
|
defaultProps: {
|
|
label: 'Script Task',
|
|
data: {
|
|
description: 'Execute JavaScript code',
|
|
scriptCode: '',
|
|
scriptLanguage: 'javascript',
|
|
inputVariables: [],
|
|
outputVariables: [],
|
|
shape: 'rectangle',
|
|
backgroundColor: '#f9fafb',
|
|
borderColor: '#6b7280',
|
|
textColor: '#374151'
|
|
}
|
|
}
|
|
},
|
|
{
|
|
type: 'html',
|
|
name: 'HTML Content',
|
|
category: 'Core',
|
|
icon: 'material-symbols:code',
|
|
description: 'Display custom HTML content',
|
|
defaultProps: {
|
|
label: 'HTML Content',
|
|
data: {
|
|
description: 'Custom HTML content',
|
|
htmlCode: '<!-- Enter your HTML code here -->\n<div class="custom-html-content">\n <h2>Custom HTML Content</h2>\n <p>This is a custom HTML node that can display rich content.</p>\n</div>',
|
|
cssCode: '',
|
|
jsCode: '',
|
|
inputVariables: [],
|
|
outputVariables: [],
|
|
allowVariableAccess: true,
|
|
autoRefresh: false,
|
|
shape: 'rectangle',
|
|
backgroundColor: '#e0f2fe',
|
|
borderColor: '#0ea5e9',
|
|
textColor: '#0c4a6e'
|
|
}
|
|
}
|
|
},
|
|
{
|
|
type: 'subprocess',
|
|
name: 'Sub Process',
|
|
category: 'Core',
|
|
icon: 'material-symbols:hub-outline',
|
|
description: 'Execute another process as a sub-process',
|
|
defaultProps: {
|
|
label: 'Sub Process',
|
|
data: {
|
|
description: 'Executes another process',
|
|
subprocessId: null,
|
|
subprocessName: '',
|
|
shape: 'rectangle',
|
|
backgroundColor: '#f0fdfa',
|
|
borderColor: '#14b8a6',
|
|
textColor: '#134e4a'
|
|
}
|
|
}
|
|
},
|
|
|
|
// Design Elements / Shapes
|
|
{
|
|
type: 'swimlane-horizontal',
|
|
name: 'Horizontal Swimlane',
|
|
category: 'Shape',
|
|
icon: 'material-symbols:view-stream',
|
|
description: 'Horizontal swimlane for organizing process sections',
|
|
defaultProps: {
|
|
label: '',
|
|
data: {
|
|
description: '',
|
|
width: 600,
|
|
height: 150,
|
|
backgroundColor: '#f8fafc',
|
|
borderColor: '#e2e8f0',
|
|
textColor: '#475569',
|
|
isShape: true,
|
|
shapeType: 'swimlane-horizontal'
|
|
}
|
|
}
|
|
},
|
|
{
|
|
type: 'swimlane-vertical',
|
|
name: 'Vertical Swimlane',
|
|
category: 'Shape',
|
|
icon: 'material-symbols:view-column',
|
|
description: 'Vertical swimlane for organizing process sections',
|
|
defaultProps: {
|
|
label: '',
|
|
data: {
|
|
description: '',
|
|
width: 200,
|
|
height: 400,
|
|
backgroundColor: '#f8fafc',
|
|
borderColor: '#e2e8f0',
|
|
textColor: '#475569',
|
|
isShape: true,
|
|
shapeType: 'swimlane-vertical'
|
|
}
|
|
}
|
|
},
|
|
{
|
|
type: 'rectangle-shape',
|
|
name: 'Rectangle',
|
|
category: 'Shape',
|
|
icon: 'material-symbols:rectangle',
|
|
description: 'Rectangle shape for grouping and annotation',
|
|
defaultProps: {
|
|
label: '',
|
|
data: {
|
|
description: '',
|
|
width: 300,
|
|
height: 200,
|
|
backgroundColor: '#fefefe',
|
|
borderColor: '#d1d5db',
|
|
textColor: '#374151',
|
|
isShape: true,
|
|
shapeType: 'rectangle'
|
|
}
|
|
}
|
|
},
|
|
{
|
|
type: 'text-annotation',
|
|
name: 'Text Annotation',
|
|
category: 'Shape',
|
|
icon: 'material-symbols:text-fields',
|
|
description: 'Text annotation for adding notes and comments',
|
|
defaultProps: {
|
|
label: '',
|
|
data: {
|
|
description: '',
|
|
width: 200,
|
|
height: 80,
|
|
backgroundColor: '#fffbeb',
|
|
borderColor: '#fbbf24',
|
|
textColor: '#92400e',
|
|
isShape: true,
|
|
shapeType: 'text-annotation'
|
|
}
|
|
}
|
|
}
|
|
];
|
|
|
|
// Get components by category for rendering in sections
|
|
const getComponentsByCategory = (category) => {
|
|
return availableComponents.filter(component => component.category === category);
|
|
};
|
|
|
|
// Check if component matches search query
|
|
const matchesSearch = (component) => {
|
|
if (!searchQuery.value) return true;
|
|
|
|
const query = searchQuery.value.toLowerCase();
|
|
return (
|
|
component.name.toLowerCase().includes(query) ||
|
|
component.description.toLowerCase().includes(query) ||
|
|
component.type.toLowerCase().includes(query)
|
|
);
|
|
};
|
|
|
|
// Handle drag start event
|
|
const onDragStart = (event, component) => {
|
|
// Set the component data in the format expected by ProcessFlowCanvas
|
|
const componentData = {
|
|
type: component.type,
|
|
label: component.defaultProps.label,
|
|
data: component.defaultProps.data
|
|
};
|
|
|
|
// Set the drag data with text/plain format for better Mac compatibility
|
|
event.dataTransfer.effectAllowed = 'copy';
|
|
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
|
|
const addComponent = (component) => {
|
|
// Use same format as drag operation for consistency
|
|
const componentData = {
|
|
type: component.type,
|
|
label: component.defaultProps.label,
|
|
data: component.defaultProps.data
|
|
};
|
|
|
|
emit('add-component', componentData);
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.process-components {
|
|
@apply bg-white h-full;
|
|
}
|
|
|
|
.component-item {
|
|
@apply h-20;
|
|
min-height: 80px;
|
|
transition: all 0.15s ease-in-out;
|
|
}
|
|
|
|
.component-item:hover {
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.component-item:active {
|
|
transform: scale(0.97);
|
|
}
|
|
|
|
/* Mobile touch-friendly adjustments */
|
|
@media (max-width: 768px) {
|
|
.component-item {
|
|
@apply h-24;
|
|
min-height: 96px;
|
|
}
|
|
}
|
|
|
|
/* Touch device optimizations */
|
|
@media (hover: none) and (pointer: coarse) {
|
|
.component-item {
|
|
min-height: 44px; /* iOS recommended touch target */
|
|
}
|
|
|
|
.component-item:hover {
|
|
transform: none; /* Disable hover effects on touch devices */
|
|
}
|
|
|
|
.component-item:active {
|
|
transform: scale(0.95);
|
|
background-color: #f3f4f6;
|
|
}
|
|
}
|
|
</style> |