Skip to content
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
80d1ffa
initial file setup for the workflow graphical view
Dileepadari Jun 10, 2025
29f35db
initial setup done with vue.js rendering
Dileepadari Jun 11, 2025
3a86a6e
menu options added in the UI
Dileepadari Jun 12, 2025
3bfb0ad
menu options added in the UI
Dileepadari Jun 12, 2025
9f2a264
menu options added in the UI
Dileepadari Jun 13, 2025
f918892
menu options added in the UI
Dileepadari Jun 13, 2025
3a57671
create the improved version of the UI
Dileepadari Jun 17, 2025
638815f
panel created with some collisions
Dileepadari Jun 18, 2025
d7e9e98
layout changed and made it consistent
Dileepadari Jun 19, 2025
b199e86
custom css added and enhanced canvas
Dileepadari Jun 20, 2025
1e3ff7c
added some functions and cleaned up code
Dileepadari Jun 20, 2025
211d03f
added stages to the canvas
Dileepadari Jun 21, 2025
ef15204
adding the initial version of transitions
Dileepadari Jun 23, 2025
eaa0ec0
added the transitions and the placement logic and initial setup of fu…
Dileepadari Jun 24, 2025
2ec20b8
added all the required functions and the getters and setter using aja…
Dileepadari Jun 25, 2025
9a7ded3
made Api setup and all the required getters and initial setup for the…
Dileepadari Jun 26, 2025
0106cd4
Added Undo and Redo Logic
Dileepadari Jun 26, 2025
22af2dd
Cleared errors in Api calls
Dileepadari Jun 27, 2025
77febbd
added modals for the add stage and transitions
Dileepadari Jun 27, 2025
6729a5b
stages and transitions modals connected to existing logic
Dileepadari Jun 30, 2025
693efae
positions fixed and undo, redo fixed
Dileepadari Jul 1, 2025
f802d55
delete stage completely executed
Dileepadari Jul 2, 2025
e1937d3
transition deletion and edit added and layout enhanced
Dileepadari Jul 2, 2025
239deee
Added Accessibility needs and shortcuts (To be Enhanced)
Dileepadari Jul 2, 2025
48504bf
some enhancements done
Dileepadari Jul 3, 2025
fff2f64
Save functionality and other validations done
Dileepadari Jul 3, 2025
8187d7d
updated the auto saving for positions, translations updated and the a…
Dileepadari Jul 6, 2025
69a901a
modal adjusted and some enhancements done
Dileepadari Jul 7, 2025
03f3fdd
layout changed and made code modular
Dileepadari Jul 8, 2025
06a36ad
improved the delete logic
Dileepadari Jul 8, 2025
1c38d65
refactored code without delete functionality
Dileepadari Jul 9, 2025
439e5d9
Merge remote-tracking branch 'origin/gsoc-workflow' into gsoc-workflo…
bembelimen Jul 9, 2025
5153db2
the modal logic enhnaced using joomla.dialog
Dileepadari Jul 9, 2025
c20e368
validation added for the html respose of delete api
Dileepadari Jul 9, 2025
542ad2c
validation added for the html respose of delete api
Dileepadari Jul 9, 2025
2113502
shortcuts added and validated
Dileepadari Jul 9, 2025
bfb0410
undo and redo added for position ajax call
Dileepadari Jul 9, 2025
6bcd4e1
landmarks for keyboard navigation improved
Dileepadari Jul 9, 2025
53f199d
github linting styles adjusted
Dileepadari Jul 10, 2025
70b642a
linting and css issues cleared
Dileepadari Jul 10, 2025
f098b47
some more refactoring
Dileepadari Jul 10, 2025
ae05b98
ceared lint issues again
Dileepadari Jul 10, 2025
1958429
adjusted css alignments
Dileepadari Jul 10, 2025
14fc988
all linting issues cleared
Dileepadari Jul 10, 2025
a799a07
css issues cleared and tab logic enhanced
Dileepadari Jul 15, 2025
4e50ea1
fit view adjusted and some css edits done
Dileepadari Jul 15, 2025
bedd9e8
delete logic changed to json by overloading
Dileepadari Jul 15, 2025
5c66af1
language strings and validation resolved
Dileepadari Jul 15, 2025
c2cede8
some linting validation
Dileepadari Jul 16, 2025
56d2258
transition mode removed and positions bug fixed
Dileepadari Jul 17, 2025
fae346b
bugs cleared for positions and undo, fixed dot warnings
Dileepadari Jul 17, 2025
9e5f2b3
minimap minimize logic added
Dileepadari Jul 18, 2025
b4dc905
permissions in backend implemented
Dileepadari Jul 21, 2025
d0c04ad
all permissions logic implemented and transition add by drag implemented
Dileepadari Jul 21, 2025
ab0128b
Merge remote-tracking branch 'Joomla/6.0-dev' into gsoc-workflow-dileep
bembelimen Jul 24, 2025
cd2eaed
changes done for review comments
Dileepadari Jul 24, 2025
900d128
Merge branch 'gsoc-workflow-dileep' of https://github.com/joomla-proj…
Dileepadari Jul 24, 2025
2288738
changes done for review comments:linting
Dileepadari Jul 24, 2025
48e024a
minor fix in css
Dileepadari Jul 25, 2025
5a533ca
js seperated to files
Dileepadari Jul 28, 2025
b63d859
small correction
Dileepadari Jul 28, 2025
16e1dc1
transitions dropdown added without keyboard support
Dileepadari Jul 29, 2025
a446fc0
implemented all the changes regarding dropdowns
Dileepadari Aug 3, 2025
2bb7f9c
deprecated functions removed
Dileepadari Aug 4, 2025
80cc021
accessibility improved
Dileepadari Aug 4, 2025
2897829
code quality increased in js
Dileepadari Aug 4, 2025
d8b3d26
Merge pull request #15 from joomla-projects/gsoc-workflow-experiment
Dileepadari Aug 7, 2025
bc7a18f
accessibility improved
Dileepadari Aug 12, 2025
9bd6574
edge and stage layout changed
Dileepadari Aug 13, 2025
245572d
accessibilityy fixer script added
Dileepadari Aug 13, 2025
8402df4
css added
Dileepadari Aug 15, 2025
0a56846
color contrast isues cleared
Dileepadari Aug 15, 2025
0adc955
Merge remote-tracking branch 'Joomla/6.0-dev' into gsoc25/gsoc-workfl…
bembelimen Aug 22, 2025
53d68c1
some changes for options dropdown and semantics
Dileepadari Aug 11, 2025
fc54e8e
changes for the keyboard navigation
Dileepadari Aug 27, 2025
73b0ae4
sql changes
Dileepadari Aug 27, 2025
9fe999a
css lint fixed
Dileepadari Aug 27, 2025
fcb3192
changed shortcuts
Dileepadari Aug 27, 2025
df147bb
removed unused dependency
Dileepadari Aug 28, 2025
f1af875
Added visual graph feature for non Admins in article at run transitio…
Dileepadari Aug 31, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE `#__workflow_stages` ADD COLUMN `position` text NULL;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE "#__workflow_stages" ADD COLUMN "position" text;
26 changes: 26 additions & 0 deletions administrator/components/com_workflow/layouts/toolbar/redo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

/**
* @package Joomla.Administrator
* @subpackage com_workflow
*
* @copyright (C) 2025 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/

defined('_JEXEC') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;

Factory::getDocument()->getWebAssetManager()
->useScript('webcomponent.toolbar-button');

$title = Text::_('COM_WORKFLOW_REDO');
?>
<joomla-toolbar-button>
<button class="btn btn-info" onclick="WorkflowGraph.Event.fire('onClickRedoWorkflow')">
<span class="icon-redo icon-fw" aria-hidden="true"></span>
<?php echo $title; ?>
</button>
</joomla-toolbar-button>
26 changes: 26 additions & 0 deletions administrator/components/com_workflow/layouts/toolbar/undo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

/**
* @package Joomla.Administrator
* @subpackage com_workflow
*
* @copyright (C) 2025 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/

defined('_JEXEC') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;

Factory::getDocument()->getWebAssetManager()
->useScript('webcomponent.toolbar-button');

$title = Text::_('COM_WORKFLOW_UNDO');
?>
<joomla-toolbar-button>
<button class="btn btn-info" onclick="WorkflowGraph.Event.fire('onClickUndoWorkflow')">
<span class="icon-undo-2 icon-fw" aria-hidden="true"></span>
<?php echo $title; ?>
</button>
</joomla-toolbar-button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Workflow Graph Event bus - used for communication between joomla and vue
*/
export default class Event {
/**
* Workflow Event constructor
*/
constructor() {
this.events = {};
}

/**
* Fire an event
* @param event
* @param data
*/
fire(event, data = null) {
if (this.events[event]) {
this.events[event].forEach((fn) => fn(data));
}
}

/**
* Listen to events
* @param event
* @param callback
*/
listen(event, callback) {
this.events[event] = this.events[event] || [];
this.events[event].push(callback);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import Event from './Event.es6.js';

class WorkflowGraphApi {
constructor() {
const options = Joomla.getOptions('com_workflow', {});
if (!options.apiBaseUrl) {
throw new TypeError('Workflow API baseUrl is not defined');
}
this.baseUrl = options.apiBaseUrl;

if (!options.extension) {
throw new TypeError('Workflow API extension is not defined');
}
this.extension = options.extension;
this.csrfToken = Joomla.getOptions('csrf.token');
if (!this.csrfToken) {
console.warn('CSRF token not found');
}
}

/**
* Generic request method with better error handling
*/
async makeRequest(url, options = {}) {
const defaultOptions = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
};

// add csrf for all request as data [token] = 1
if (this.csrfToken) {
if (!options.data) {
options.data = {};
}
options.data[this.csrfToken] = '1';
}

const config = { ...defaultOptions, ...options };

if (options.data instanceof FormData) {
delete config.headers['Content-Type'];
}
return new Promise((resolve, reject) => {
Joomla.request({
url: `${this.baseUrl}${url}&extension=${this.extension}`,
...config,
onSuccess: (response) => {
resolve(response);
},
onError: (xhr) => {
let message = 'Network error';
try {
const errorData = JSON.parse(xhr.responseText);
message = errorData.message || message;
} catch (e) {
message = xhr.statusText || message;
}
reject(new Error(message));
}
});
});
}

async getWorkflow(id) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you work with some function comments to describe what the function does and what parameters are expected etc. (also for the other functions)

try {
const response = await this.makeRequest(`&task=graph.getWorkflow&id=${id}&format=json`);
const data = typeof response === 'string' ? JSON.parse(response) : response;

if (data.success === false) {
WorkflowGraph.Event.fire('onWorkflowError', { error: data.message || 'Failed to load workflow' });
return;
}

return data.data || data;
} catch (error) {
WorkflowGraph.Event.fire('onWorkflowError', { error: error.message });
throw error;
}
}

async getStages(workflowId) {
try {
const response = await this.makeRequest(`&task=graph.getStages&workflow_id=${workflowId}&format=json`);
const data = typeof response === 'string' ? JSON.parse(response) : response;

if (data.success === false) {
WorkflowGraph.Event.fire('onStagesError', { error: data.message || 'Failed to load stages' });
return;
}

return data.data || data;
} catch (error) {
WorkflowGraph.Event.fire('onStagesError', { error: error.message });
throw error;
}
}

async getTransitions(workflowId) {
try {
const response = await this.makeRequest(`&task=graph.getTransitions&workflow_id=${workflowId}&format=json`);
const data = typeof response === 'string' ? JSON.parse(response) : response;

if (data.success === false) {
WorkflowGraph.Event.fire('onTransitionsError', { error: data.message || 'Failed to load transitions' });
return;
}

return data.data || data;
} catch (error) {
WorkflowGraph.Event.fire('onTransitionsError', { error: error.message });
throw error;
}
}

async deleteStage(id, workflowId) {
try {
const formData = new FormData();
formData.append('cid[]', id);
formData.append('workflow_id', workflowId);

if (this.csrfToken) {
formData.append(this.csrfToken, '1');
}

const response = await this.makeRequest(`&task=stages.trash&format=raw`, {
method: 'POST',
data: formData,
processData: false,
contentType: false
});

if (response.success === false) {
WorkflowGraph.Event.fire('onStageError', { error: response.message || 'Failed to delete stage' });
return false;
}

return true;
} catch (error) {
WorkflowGraph.Event.fire('onStageError', { error: error.message });
throw error;
}
}

async deleteTransition(id, workflowId) {
try {
const formData = new FormData();
formData.append('cid[]', id);
formData.append('workflow_id', workflowId);

if (this.csrfToken) {
formData.append(this.csrfToken, '1');
}

await this.makeRequest(`&task=transitions.trash&format=raw`, {
method: 'POST',
data: formData,
processData: false,
contentType: false
});

WorkflowGraph.Event.fire('onTransitionDeleted', { id });
return true;
} catch (error) {
WorkflowGraph.Event.fire('onTransitionError', { error: error.message });
throw error;
}
}

// async updateStagePosition(stageId, position) {
// try {
// const stage = { id: stageId, position: position };
// return await this.saveStage(stage);
// } catch (error) {
// WorkflowGraph.WorkflowGraph.Event.fire('onStageError', { error: error.message });
// throw error;
// }
// }
}

export default new WorkflowGraphApi();
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<template>
<div class="d-flex flex-column flex-grow-1" style="min-height: 75vh" role="application" aria-label="Workflow Builder Application">
<div class="d-flex flex-column flex-grow-0">
<WorkflowTitlebar />
</div>
<div class="d-flex flex-grow-1 overflow-hidden">
<main
class="flex-grow-1 position-relative"
id="main-canvas"
role="main"
aria-label="Workflow Canvas"
>
<WorkflowCanvas />
</main>
</div>
</div>
</template>

<script>
import { onMounted } from 'vue';
import { useStore } from 'vuex';
import WorkflowTitlebar from "./titlebar/titlebar.vue";
import WorkflowCanvas from "./canvas/WorkflowCanvas.vue";

export default {
name: 'WorkflowGraphApp',
components: {
WorkflowCanvas,
WorkflowTitlebar,
},
setup() {
const store = useStore();

onMounted(() => {
// Extract workflow ID from the URL or from Joomla options
const options = Joomla.getOptions('com_workflow', {});
const workflowId = options.workflowId || parseInt(new URL(window.location.href).searchParams.get('id'), 10);

if (workflowId) {
store.dispatch('loadWorkflow', workflowId);
} else {
console.error('No workflow ID provided');
}

const tokenEl = document.querySelector('input[name="<?php echo JSession::getFormToken(); ?>"]')
if (tokenEl) {
tokenEl.name = Joomla.getOptions('csrf.token', '')
}
});
}
};
</script>
Loading