diff --git a/Makefile b/Makefile index b18ba3e16..e564bee1a 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ REPLACE := perl -i -pe RODAN_PATH := ./rodan-main/code/rodan JOBS_PATH := $(RODAN_PATH)/jobs -PROD_TAG := v3.1.0 +PROD_TAG := v3.3.0 DOCKER_TAG := nightly diff --git a/nginx/Dockerfile b/nginx/Dockerfile index 678290475..4f4b687e0 100644 --- a/nginx/Dockerfile +++ b/nginx/Dockerfile @@ -1,5 +1,5 @@ ARG VERSION -FROM ddmal/rodan-main:${VERSION} as rodan-static +FROM ddmal/rodan-main:${VERSION} AS rodan-static RUN touch /code/Rodan/database.log /code/Rodan/rodan.log @@ -18,10 +18,15 @@ RUN export DJANGO_SECRET_KEY=localdev \ ########################################################### FROM nginx:1.19 +# prevents interactive prompts during package installation that would otherwise hang or break automated builds +ENV DEBIAN_FRONTEND=noninteractive + # Install OS dependencies -RUN apt-get -qq update \ - && apt-get -qq install openssl certbot unzip -y \ - && rm -rf /var/lib/apt/lists/* +RUN sed -i 's|http://deb.debian.org/debian|http://archive.debian.org/debian|g' /etc/apt/sources.list && \ + sed -i 's|http://security.debian.org/debian-security|http://archive.debian.org/debian-security|g' /etc/apt/sources.list && \ + apt-get -o Acquire::Check-Valid-Until=false update && \ + apt-get install -y openssl certbot unzip && \ + rm -rf /var/lib/apt/lists/* # Add configuration files. COPY ./config/nginx.conf /etc/nginx/nginx.conf diff --git a/production.yml b/production.yml index 403e51bc5..bbc6dbd71 100644 --- a/production.yml +++ b/production.yml @@ -3,7 +3,7 @@ version: "3.4" services: nginx: - image: "ddmal/nginx:v3.2.0" + image: "ddmal/nginx:v3.3.0" deploy: replicas: 1 resources: @@ -37,7 +37,7 @@ services: - "resources:/rodan/data" rodan-main: - image: "ddmal/rodan-main:v3.2.0" + image: "ddmal/rodan-main:v3.3.0" deploy: replicas: 1 resources: @@ -78,7 +78,7 @@ services: - "resources:/rodan/data" celery: - image: "ddmal/rodan-main:v3.2.0" + image: "ddmal/rodan-main:v3.3.0" deploy: replicas: 1 resources: @@ -109,7 +109,7 @@ services: - "resources:/rodan/data" py3-celery: - image: "ddmal/rodan-python3-celery:v3.2.0" + image: "ddmal/rodan-python3-celery:v3.3.0" deploy: replicas: 1 resources: @@ -139,7 +139,7 @@ services: - "resources:/rodan/data" gpu-celery: - image: "ddmal/rodan-gpu-celery:v3.2.0" + image: "ddmal/rodan-gpu-celery:v3.3.0" deploy: replicas: 1 resources: @@ -195,7 +195,7 @@ services: TZ: America/Toronto postgres: - image: "ddmal/postgres-plpython:v3.2.0" + image: "ddmal/postgres-plpython:v3.3.0" deploy: replicas: 1 endpoint_mode: dnsrr diff --git a/rodan-client/.prettierrc b/rodan-client/.prettierrc new file mode 100644 index 000000000..44cac6c17 --- /dev/null +++ b/rodan-client/.prettierrc @@ -0,0 +1,14 @@ +{ + "printWidth": 200, + "tabWidth": 4, + "useTabs": false, + "semi": true, + "singleQuote": true, + "quoteProps": "as-needed", + "trailingComma": "none", + "bracketSpacing": true, + "bracketSameLine": false, + "arrowParens": "avoid", + "endOfLine": "lf", + "singleAttributePerLine": false +} diff --git a/rodan-client/code/src/js/Controllers/ControllerProject.js b/rodan-client/code/src/js/Controllers/ControllerProject.js index e96609a57..5667e5691 100644 --- a/rodan-client/code/src/js/Controllers/ControllerProject.js +++ b/rodan-client/code/src/js/Controllers/ControllerProject.js @@ -1,4 +1,4 @@ -import $ from 'jquery'; +import $, { type } from 'jquery'; import _ from 'underscore'; import BaseController from './BaseController'; import BaseViewCollection from 'js/Views/Master/Main/BaseViewCollection'; @@ -8,36 +8,34 @@ import Project from 'js/Models/Project'; import Radio from 'backbone.radio'; import RODAN_EVENTS from 'js/Shared/RODAN_EVENTS'; import UserCollection from 'js/Collections/UserCollection'; +import ViewDeleteConfirm from '../Views/Master/Main/Shared/ViewDeleteConfirm'; import ViewProject from 'js/Views/Master/Main/Project/Individual/ViewProject'; import ViewProjectCollection from 'js/Views/Master/Main/Project/Collection/ViewProjectCollection'; import ViewUserCollectionItem from 'js/Views/Master/Main/User/Collection/ViewUserCollectionItem'; -import ViewWorkflowRunCollection from 'js/Views/Master/Main/WorkflowRun/Collection/ViewWorkflowRunCollection'; -import WorkflowRunCollection from 'js/Collections/WorkflowRunCollection'; +import ViewResourceCollection from 'js/Views/Master/Main/Resource/Collection/ViewResourceCollection'; +import ResourceCollection from 'js/Collections/ResourceCollection'; /** * Controller for Projects. */ -export default class ControllerProject extends BaseController -{ -/////////////////////////////////////////////////////////////////////////////////////// -// PUBLIC METHODS -/////////////////////////////////////////////////////////////////////////////////////// +export default class ControllerProject extends BaseController { + /////////////////////////////////////////////////////////////////////////////////////// + // PUBLIC METHODS + /////////////////////////////////////////////////////////////////////////////////////// /** * Initialize the instance. */ - initialize() - { + initialize() { this._activeProject = null; } -/////////////////////////////////////////////////////////////////////////////////////// -// PRIVATE METHODS - initialization -/////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////// + // PRIVATE METHODS - initialization + /////////////////////////////////////////////////////////////////////////////////////// /** * Initialize Radio. */ - _initializeRadio() - { + _initializeRadio() { // Events. Radio.channel('rodan').on(RODAN_EVENTS.EVENT__PROJECT_ADDED_USER_ADMIN, options => this._handleEventProjectAddedUserAdmin(options)); Radio.channel('rodan').on(RODAN_EVENTS.EVENT__PROJECT_ADDED_USER_WORKER, options => this._handleEventProjectAddedUserWorker(options)); @@ -51,25 +49,25 @@ export default class ControllerProject extends BaseController Radio.channel('rodan').on(RODAN_EVENTS.EVENT__PROJECT_USERS_SELECTED, options => this._handleEventProjectShowUsers(options)); // Requests. - Radio.channel('rodan').reply(RODAN_EVENTS.REQUEST__PROJECT_ADD_USER_ADMIN, (options) => this._handleRequestProjectAddUserAdmin(options)); - Radio.channel('rodan').reply(RODAN_EVENTS.REQUEST__PROJECT_ADD_USER_WORKER, (options) => this._handleRequestProjectAddUserWorker(options)); + Radio.channel('rodan').reply(RODAN_EVENTS.REQUEST__PROJECT_ADD_USER_ADMIN, options => this._handleRequestProjectAddUserAdmin(options)); + Radio.channel('rodan').reply(RODAN_EVENTS.REQUEST__PROJECT_ADD_USER_WORKER, options => this._handleRequestProjectAddUserWorker(options)); Radio.channel('rodan').reply(RODAN_EVENTS.REQUEST__PROJECT_GET_ACTIVE, () => this._handleRequestProjectActive()); Radio.channel('rodan').reply(RODAN_EVENTS.REQUEST__PROJECT_CREATE, options => this._handleRequestCreateProject(options)); Radio.channel('rodan').reply(RODAN_EVENTS.REQUEST__PROJECT_SET_ACTIVE, options => this._handleRequestSetActiveProject(options)); Radio.channel('rodan').reply(RODAN_EVENTS.REQUEST__PROJECT_SAVE, options => this._handleRequestProjectSave(options)); Radio.channel('rodan').reply(RODAN_EVENTS.REQUEST__PROJECT_DELETE, options => this._handleRequestProjectDelete(options)); + Radio.channel('rodan').reply(RODAN_EVENTS.REQUEST__PROJECT_DELETE_CONFIRM, options => this._handleRequestProjectDeleteConfirm(options)); Radio.channel('rodan').reply(RODAN_EVENTS.REQUEST__PROJECT_REMOVE_USER_ADMIN, options => this._handleRequestRemoveUserAdmin(options)); Radio.channel('rodan').reply(RODAN_EVENTS.REQUEST__PROJECT_REMOVE_USER_WORKER, options => this._handleRequestRemoveUserWorker(options)); } -/////////////////////////////////////////////////////////////////////////////////////// -// PRIVATE METHODS - Event handlers -/////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////// + // PRIVATE METHODS - Event handlers + /////////////////////////////////////////////////////////////////////////////////////// /** * Handle event Project show users. */ - _handleEventProjectShowUsers(options) - { + _handleEventProjectShowUsers(options) { Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_HIDE); // Make sure project is updated. @@ -80,56 +78,80 @@ export default class ControllerProject extends BaseController var workerUserCollection = new UserCollection(); // Get admins and workers for project. - var ajaxSettingsAdmins = {success: (response) => this._handleProjectGetAdminsSuccess(response, adminUserCollection), - error: (response) => Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__SYSTEM_HANDLE_ERROR, {response: response}), - type: 'GET', - dataType: 'json', - url: options.project.get('url') + 'admins/'}; - var ajaxSettingsWorkers = {success: (response) => this._handleProjectGetWorkersSuccess(response, workerUserCollection), - error: (response) => Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__SYSTEM_HANDLE_ERROR, {response: response}), - type: 'GET', - dataType: 'json', - url: options.project.get('url') + 'workers/'}; - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__SERVER_REQUEST_AJAX, {settings: ajaxSettingsAdmins}); - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__SERVER_REQUEST_AJAX, {settings: ajaxSettingsWorkers}); + var ajaxSettingsAdmins = { + success: response => this._handleProjectGetAdminsSuccess(response, adminUserCollection), + error: response => Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__SYSTEM_HANDLE_ERROR, { response: response }), + type: 'GET', + dataType: 'json', + url: options.project.get('url') + 'admins/' + }; + var ajaxSettingsWorkers = { + success: response => this._handleProjectGetWorkersSuccess(response, workerUserCollection), + error: response => Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__SYSTEM_HANDLE_ERROR, { response: response }), + type: 'GET', + dataType: 'json', + url: options.project.get('url') + 'workers/' + }; + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__SERVER_REQUEST_AJAX, { + settings: ajaxSettingsAdmins + }); + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__SERVER_REQUEST_AJAX, { + settings: ajaxSettingsWorkers + }); // Need collection for all users. var collection = new UserCollection(); collection.fetch(); - var userSelectionView = new BaseViewCollection({collection: collection, - el: '
', - template: _.template($('#template-main_user_selection').text()), - childView: BaseViewCollectionItem, - childViewContainer: 'select', - childViewOptions: {template: _.template($('#template-main_user_selection_item').text()), - tagName: 'option'}}); + var userSelectionView = new BaseViewCollection({ + collection: collection, + el: '
', + template: _.template($('#template-main_user_selection').text()), + childView: BaseViewCollectionItem, + childViewContainer: 'select', + childViewOptions: { + template: _.template($('#template-main_user_selection_item').text()), + tagName: 'option' + } + }); // Create view. - var projectAdminsView = new BaseViewCollection({collection: adminUserCollection, - template: _.template($('#template-main_user_collection').text()), - childView: ViewUserCollectionItem, - childViewOptions: {template: _.template($('#template-main_user_collection_item_remove').text()), - project: options.project, - admin: true}}); - var projectWorkersView = new BaseViewCollection({collection: workerUserCollection, - template: _.template($('#template-main_user_collection').text()), - childView: ViewUserCollectionItem, - childViewOptions: {template: _.template($('#template-main_user_collection_item_remove').text()), - project: options.project}}); - var view = new LayoutViewProjectUsers({viewusers: userSelectionView, - viewprojectadmins: projectAdminsView, - viewprojectworkers: projectWorkersView, - project: options.project}); + var projectAdminsView = new BaseViewCollection({ + collection: adminUserCollection, + template: _.template($('#template-main_user_collection').text()), + childView: ViewUserCollectionItem, + childViewOptions: { + template: _.template($('#template-main_user_collection_item_remove').text()), + project: options.project, + admin: true + } + }); + var projectWorkersView = new BaseViewCollection({ + collection: workerUserCollection, + template: _.template($('#template-main_user_collection').text()), + childView: ViewUserCollectionItem, + childViewOptions: { + template: _.template($('#template-main_user_collection_item_remove').text()), + project: options.project + } + }); + var view = new LayoutViewProjectUsers({ + viewusers: userSelectionView, + viewprojectadmins: projectAdminsView, + viewprojectworkers: projectWorkersView, + project: options.project + }); // Show modal. - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_SHOW, {content: view, title: 'Project Users'}); + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_SHOW, { + content: view, + title: 'Project Users' + }); } /** * Handle event Project generic response. */ - _handleEventProjectGenericResponse() - { + _handleEventProjectGenericResponse() { Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_HIDE); Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__GLOBAL_PROJECTS_LOAD, {}); } @@ -137,8 +159,7 @@ export default class ControllerProject extends BaseController /** * Handle event Project delete response. */ - _handleEventProjectDeleteResponse() - { + _handleEventProjectDeleteResponse() { Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_HIDE); Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__GLOBAL_PROJECTS_LOAD, {}); Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__PROJECT_SELECTED_COLLECTION); @@ -147,91 +168,138 @@ export default class ControllerProject extends BaseController /** * Handle request Project save. */ - _handleRequestProjectSave(options) - { - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_SHOW_IMPORTANT, {title: 'Saving Project', content: 'Please wait...'}); - options.project.save(options.fields, {patch: true, success: (model) => Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__PROJECT_SAVED, {project: model})}); + _handleRequestProjectSave(options) { + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_SHOW_IMPORTANT, { + title: 'Saving Project', + content: 'Please wait...' + }); + options.project.save(options.fields, { + patch: true, + success: model => + Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__PROJECT_SAVED, { + project: model + }) + }); } /** * Handle request Project create. */ - _handleRequestCreateProject(options) - { - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_SHOW_IMPORTANT, {title: 'Creating Project', content: 'Please wait...'}); - var project = new Project({creator: options.user}); - project.save({}, {success: (model) => Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__PROJECT_CREATED, {project: model})}); + _handleRequestCreateProject(options) { + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_SHOW_IMPORTANT, { + title: 'Creating Project', + content: 'Please wait...' + }); + var project = new Project({ creator: options.user }); + project.save( + {}, + { + success: model => + Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__PROJECT_CREATED, { + project: model + }) + } + ); } /** - * Handle request Project delete. + * Handle request Project delete confirm modal window. */ - _handleRequestProjectDelete(options) - { - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_SHOW_IMPORTANT, {title: 'Deleting Project', content: 'Please wait...'}); + _handleRequestProjectDelete(options) { this._activeProject = null; - options.project.destroy({success: (model) => Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__PROJECT_DELETED, {project: model})}); + + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_SHOW_IMPORTANT, { + title: 'Deleting Project', + content: 'Please wait...' + }); + this._activeProject = null; + options.project.destroy({ + success: () => Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__PROJECT_DELETED, {}) + }); + } + + /** + * Handle request Project delete confirm modal window. + */ + _handleRequestProjectDeleteConfirm(options) { + var view = new ViewDeleteConfirm({ + type: 'project', + names: options.project.get('name'), + toDelete: options.project + }); + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_SHOW, { + content: view, + title: 'Deleting Project' + }); } /** * Handle request set active Project. */ - _handleRequestSetActiveProject(options) - { + _handleRequestSetActiveProject(options) { this._activeProject = options.project; } /** * Handle item selection. */ - _handleEventItemSelected(options) - { + _handleEventItemSelected(options) { this._activeProject = options.project; - this._activeProject.fetch(); - // default collection inside project view is the workflowrun collection - var collection = new WorkflowRunCollection(); - collection.fetch({data: {project: this._activeProject.id}}); - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__UPDATER_SET_COLLECTIONS, {collections: [collection]}); - var viewProject = new ViewProject({model: this._activeProject}); - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MAINREGION_SHOW_VIEW, {view: viewProject, options: {project: this._activeProject}}); - viewProject.showCollection(new ViewWorkflowRunCollection({collection: collection})); + // Fetch project first and wait for it to complete + this._activeProject.fetch({ + success: () => { + // default collection inside project view is the resource collection + var collection = new ResourceCollection(); + collection.fetch({ data: { project: this._activeProject.id } }); + + // Set up view + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__UPDATER_SET_COLLECTIONS, { collections: [collection] }); + var viewProject = new ViewProject({ model: this._activeProject }); + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MAINREGION_SHOW_VIEW, { + view: viewProject, + options: { project: this._activeProject } + }); + + // Show resource collection by default and trigger resource selection event + viewProject.showCollection(new ViewResourceCollection({ collection: collection })); + Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__RESOURCE_SELECTED_COLLECTION, { project: this._activeProject }); + } + }); } /** * Handle collection selection. */ - _handleEventCollectionSelected() - { + _handleEventCollectionSelected() { var collection = Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__GLOBAL_PROJECT_COLLECTION); - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__UPDATER_SET_COLLECTIONS, {collections: [collection]}); - var view = new ViewProjectCollection({collection: collection}); - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MAINREGION_SHOW_VIEW, {view: view}); + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__UPDATER_SET_COLLECTIONS, { collections: [collection] }); + var view = new ViewProjectCollection({ collection: collection }); + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MAINREGION_SHOW_VIEW, { + view: view + }); } /** * Handle request for current active project. Returns null. */ - _handleRequestProjectActive() - { + _handleRequestProjectActive() { return this._activeProject; } /** * Handle project admins get success. */ - _handleProjectGetAdminsSuccess(response, collection) - { - collection.fetch({data: {username__in: response.join()}}); + _handleProjectGetAdminsSuccess(response, collection) { + collection.fetch({ data: { username__in: response.join() } }); } /** * Handle project workers get success. */ - _handleProjectGetWorkersSuccess(response, collection) - { - if (response.flat().length !== 0){ - collection.fetch({data: {username__in: response.join()}}); + _handleProjectGetWorkersSuccess(response, collection) { + if (response.flat().length !== 0) { + collection.fetch({ data: { username__in: response.join() } }); } } @@ -240,33 +308,38 @@ export default class ControllerProject extends BaseController * We have to use a custom AJAX call since modifying the users of a Project * has no endpoint at the moment. */ - _handleRequestRemoveUserAdmin(options) - { + _handleRequestRemoveUserAdmin(options) { var admins = options.project.get('admins'); - if (admins.length > 1) - { + if (admins.length > 1) { var userIndex = admins.indexOf(options.user.get('username')); - if (userIndex >= 0) - { + if (userIndex >= 0) { admins.splice(userIndex, 1); var usersSendObject = {}; - admins.map(function(value) { usersSendObject[value] = value; return value; }); - var ajaxSettings = {success: (response) => Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__PROJECT_REMOVED_USER_ADMIN, {project: options.project}), - error: (response) => Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__SYSTEM_HANDLE_ERROR, {response: response}), - type: 'PUT', - dataType: 'json', - data: usersSendObject, - url: options.project.get('url') + 'admins/'}; - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__SERVER_REQUEST_AJAX, {settings: ajaxSettings}); + admins.map(function (value) { + usersSendObject[value] = value; + return value; + }); + var ajaxSettings = { + success: response => + Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__PROJECT_REMOVED_USER_ADMIN, { + project: options.project + }), + error: response => Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__SYSTEM_HANDLE_ERROR, { response: response }), + type: 'PUT', + dataType: 'json', + data: usersSendObject, + url: options.project.get('url') + 'admins/' + }; + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__SERVER_REQUEST_AJAX, { settings: ajaxSettings }); + } else { + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_ERROR, { + content: 'An error occured trying to remove this User.' + }); } - else - { - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_ERROR, {content: 'An error occured trying to remove this User.'}); - } - } - else - { - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_ERROR, {content: 'At least one project admin, the creator, must exist.'}); + } else { + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_ERROR, { + content: 'At least one project admin, the creator, must exist.' + }); } } @@ -275,28 +348,33 @@ export default class ControllerProject extends BaseController * We have to use a custom AJAX call since modifying the users of a Project * has no endpoint at the moment. */ - _handleRequestRemoveUserWorker(options) - { + _handleRequestRemoveUserWorker(options) { var users = options.project.get('workers'); - if (users.length > 0) - { + if (users.length > 0) { var userIndex = users.indexOf(options.user.get('username')); - if (userIndex >= 0) - { + if (userIndex >= 0) { users.splice(userIndex, 1); var usersSendObject = {}; - users.map(function(value) { usersSendObject[value] = value; return value; }); - var ajaxSettings = {success: (response) => Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__PROJECT_REMOVED_USER_WORKER, {project: options.project}), - error: (response) => Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__SYSTEM_HANDLE_ERROR, {response: response}), - type: 'PUT', - dataType: 'json', - data: usersSendObject, - url: options.project.get('url') + 'workers/'}; - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__SERVER_REQUEST_AJAX, {settings: ajaxSettings}); - } - else - { - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_ERROR, {content: 'An error occured trying to remove this User.'}); + users.map(function (value) { + usersSendObject[value] = value; + return value; + }); + var ajaxSettings = { + success: response => + Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__PROJECT_REMOVED_USER_WORKER, { + project: options.project + }), + error: response => Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__SYSTEM_HANDLE_ERROR, { response: response }), + type: 'PUT', + dataType: 'json', + data: usersSendObject, + url: options.project.get('url') + 'workers/' + }; + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__SERVER_REQUEST_AJAX, { settings: ajaxSettings }); + } else { + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_ERROR, { + content: 'An error occured trying to remove this User.' + }); } } } @@ -304,63 +382,78 @@ export default class ControllerProject extends BaseController /** * Handle project removed user. */ - _handleEventProjectRemovedUser(options) - { + _handleEventProjectRemovedUser(options) { this._activeProject.fetch(); - Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__PROJECT_USERS_SELECTED, {project: this._activeProject}); + Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__PROJECT_USERS_SELECTED, { + project: this._activeProject + }); } /** * Handle request add admin. */ - _handleRequestProjectAddUserAdmin(options) - { + _handleRequestProjectAddUserAdmin(options) { var users = options.project.get('admins'); users.push(options.username); var usersSendObject = {}; - users.map(function(value) { usersSendObject[value] = value; return value; }); - var ajaxSettings = {success: (response) => Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__PROJECT_ADDED_USER_ADMIN, {project: options.project}), - error: (response) => Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__SYSTEM_HANDLE_ERROR, {response: response}), - type: 'PUT', - dataType: 'json', - data: usersSendObject, - url: options.project.get('url') + 'admins/'}; - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__SERVER_REQUEST_AJAX, {settings: ajaxSettings}); - } + users.map(function (value) { + usersSendObject[value] = value; + return value; + }); + var ajaxSettings = { + success: response => Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__PROJECT_ADDED_USER_ADMIN, { project: options.project }), + error: response => Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__SYSTEM_HANDLE_ERROR, { response: response }), + type: 'PUT', + dataType: 'json', + data: usersSendObject, + url: options.project.get('url') + 'admins/' + }; + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__SERVER_REQUEST_AJAX, { + settings: ajaxSettings + }); + } /** * Handle request add worker. */ - _handleRequestProjectAddUserWorker(options) - { + _handleRequestProjectAddUserWorker(options) { var users = options.project.get('workers'); users.push(options.username); var usersSendObject = {}; - users.map(function(value) { usersSendObject[value] = value; return value; }); - var ajaxSettings = {success: (response) => Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__PROJECT_ADDED_USER_WORKER, {project: options.project}), - error: (response) => Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__SYSTEM_HANDLE_ERROR, {response: response}), - type: 'PUT', - dataType: 'json', - data: usersSendObject, - url: options.project.get('url') + 'workers/'}; - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__SERVER_REQUEST_AJAX, {settings: ajaxSettings}); + users.map(function (value) { + usersSendObject[value] = value; + return value; + }); + var ajaxSettings = { + success: response => Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__PROJECT_ADDED_USER_WORKER, { project: options.project }), + error: response => Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__SYSTEM_HANDLE_ERROR, { response: response }), + type: 'PUT', + dataType: 'json', + data: usersSendObject, + url: options.project.get('url') + 'workers/' + }; + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__SERVER_REQUEST_AJAX, { + settings: ajaxSettings + }); } /** * Handle event added admin. */ - _handleEventProjectAddedUserAdmin() - { + _handleEventProjectAddedUserAdmin() { this._activeProject.fetch(); - Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__PROJECT_USERS_SELECTED, {project: this._activeProject}); - } + Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__PROJECT_USERS_SELECTED, { + project: this._activeProject + }); + } /** * Handle event added worker. */ - _handleEventProjectAddedUserWorker() - { + _handleEventProjectAddedUserWorker() { this._activeProject.fetch(); - Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__PROJECT_USERS_SELECTED, {project: this._activeProject}); + Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__PROJECT_USERS_SELECTED, { + project: this._activeProject + }); } } diff --git a/rodan-client/code/src/js/Controllers/ControllerResource.js b/rodan-client/code/src/js/Controllers/ControllerResource.js index 90a649c94..34bae35b2 100644 --- a/rodan-client/code/src/js/Controllers/ControllerResource.js +++ b/rodan-client/code/src/js/Controllers/ControllerResource.js @@ -5,6 +5,7 @@ import RODAN_EVENTS from 'js/Shared/RODAN_EVENTS'; import Radio from 'backbone.radio'; import Resource from 'js/Models/Resource'; import ResourceCollection from 'js/Collections/ResourceCollection'; +import ViewDeleteConfirm from '../Views/Master/Main/Shared/ViewDeleteConfirm'; import ViewResource from 'js/Views/Master/Main/Resource/Individual/ViewResource'; import ViewResourceMulti from 'js/Views/Master/Main/Resource/Individual/ViewResourceMulti'; import ViewResourceCollection from 'js/Views/Master/Main/Resource/Collection/ViewResourceCollection'; @@ -14,20 +15,18 @@ import ViewProject from '../Views/Master/Main/Project/Individual/ViewProject'; /** * Controller for Resources. */ -export default class ControllerResource extends BaseController -{ +export default class ControllerResource extends BaseController { initialize() { - this._selectedResources = new Set(); - this._baseSelectResource = null; + this._selectedResources = new Set(); + this._baseSelectResource = null; } -/////////////////////////////////////////////////////////////////////////////////////// -// PRIVATE METHODS -/////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////// + // PRIVATE METHODS + /////////////////////////////////////////////////////////////////////////////////////// /** * Initialize Radio. */ - _initializeRadio() - { + _initializeRadio() { // Events Radio.channel('rodan').on(RODAN_EVENTS.EVENT__RESOURCE_SELECTED_COLLECTION, options => this._handleEventCollectionSelected(options)); Radio.channel('rodan').on(RODAN_EVENTS.EVENT__RESOURCE_SELECTED, options => this._handleEventItemSelected(options)); @@ -39,6 +38,7 @@ export default class ControllerResource extends BaseController Radio.channel('rodan').reply(RODAN_EVENTS.REQUEST__RESOURCE_SHOWLAYOUTVIEW, options => this._handleCommandShowLayoutView(options)); Radio.channel('rodan').reply(RODAN_EVENTS.REQUEST__RESOURCE_CREATE, options => this._handleRequestResourceCreate(options)); Radio.channel('rodan').reply(RODAN_EVENTS.REQUEST__RESOURCE_DELETE, options => this._handleCommandResourceDelete(options)); + Radio.channel('rodan').reply(RODAN_EVENTS.REQUEST__RESOURCE_DELETE_CONFIRM, options => this._handleCommandResourceDeleteConfirm(options)); Radio.channel('rodan').reply(RODAN_EVENTS.REQUEST__RESOURCE_DOWNLOAD, options => this._handleRequestResourceDownload(options)); Radio.channel('rodan').reply(RODAN_EVENTS.REQUEST__RESOURCE_SAVE, options => this._handleCommandResourceSave(options)); Radio.channel('rodan').reply(RODAN_EVENTS.REQUEST__RESOURCE_VIEWER_ACQUIRE, options => this._handleRequestViewer(options)); @@ -50,35 +50,32 @@ export default class ControllerResource extends BaseController /** * Handle show LayoutView. */ - _handleCommandShowLayoutView(options) - { + _handleCommandShowLayoutView(options) { this._projectView = options.projectView; } /** * Handle collection selection. */ - _handleEventCollectionSelected(options) - { + _handleEventCollectionSelected(options) { this._collection = new ResourceCollection(); - this._collection.fetch({data: {project: options.project.id}}); + this._collection.fetch({ data: { project: options.project.id } }); - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__UPDATER_SET_COLLECTIONS, {collections: [this._collection]}); + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__UPDATER_SET_COLLECTIONS, { collections: [this._collection] }); const activeProject = Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__PROJECT_GET_ACTIVE); - this._projectView = new ViewProject({model: activeProject}); + this._projectView = new ViewProject({ model: activeProject }); - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MAINREGION_SHOW_VIEW, {view: this._projectView}); + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MAINREGION_SHOW_VIEW, { view: this._projectView }); - this._viewCollection = new ViewResourceCollection({collection: this._collection}); + this._viewCollection = new ViewResourceCollection({ collection: this._collection }); this._projectView.showCollection(this._viewCollection); } /** * Handle item selection. */ - _handleEventItemSelected(options) - { + _handleEventItemSelected(options) { if (!options.multiple && !options.range) { this._selectedResources.clear(); this._baseSelectResource = null; @@ -87,70 +84,83 @@ export default class ControllerResource extends BaseController if (options.multiple && this._selectedResources.has(options.resource)) { this._selectedResources.delete(options.resource); this._baseSelectResource = null; - } - else if (options.range && this._baseSelectResource !== null) { + } else if (options.range && this._baseSelectResource !== null) { let indexBase = this._collection.indexOf(this._baseSelectResource); let indexRes = this._collection.indexOf(options.resource); this._selectedResources.clear(); for (let n = Math.min(indexBase, indexRes); n <= Math.max(indexBase, indexRes); n++) { this._selectedResources.add(this._collection.at(n)); } - } - else { + } else { this._selectedResources.add(options.resource); this._baseSelectResource = options.resource; } if (this._selectedResources.size === 0) { this._projectView.clearCollectionItemInfoView(); - } - else if (this._selectedResources.size === 1) { - this._projectView.showCollectionItemInfo(new ViewResource({model: this._selectedResources.values().next().value})); - } - else { - this._projectView.showCollectionItemInfo(new ViewResourceMulti({models: this._selectedResources})); + } else if (this._selectedResources.size === 1) { + this._projectView.showCollectionItemInfo(new ViewResource({ model: this._selectedResources.values().next().value })); + } else { + this._projectView.showCollectionItemInfo(new ViewResourceMulti({ models: this._selectedResources })); } } /** * Handle command add Resource. */ - _handleRequestResourceCreate(options) - { - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_SHOW_IMPORTANT, {title: 'Creating Resource', content: 'Please wait...'}); + _handleRequestResourceCreate(options) { + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_SHOW_IMPORTANT, { title: 'Creating Resource', content: 'Please wait...' }); var resource = null; let opts = { - project: options.project.get('url'), - file: options.file, + project: options.project.get('url'), + file: options.file }; - if (options.resourcetype) - { + if (options.resourcetype) { opts['resource_type'] = options.resourcetype; } - if (options.label_names !== undefined) - { + if (options.label_names !== undefined) { opts['label_names'] = options.label_names; } resource = new Resource(opts); - var jqXHR = resource.save({}, {success: (model) => this._handleCreateSuccess(model, this._collection)}); - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__TRANSFERMANAGER_MONITOR_UPLOAD, {request: jqXHR, file: options.file}); + var jqXHR = resource.save({}, { success: model => this._handleCreateSuccess(model, this._collection) }); + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__TRANSFERMANAGER_MONITOR_UPLOAD, { request: jqXHR, file: options.file }); } /** * Handle command delete Resource. */ - _handleCommandResourceDelete(options) - { - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_SHOW_IMPORTANT, {title: 'Deleting Resource', content: 'Please wait...'}); + _handleCommandResourceDelete(options) { + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_SHOW_IMPORTANT, { title: 'Deleting Resource', content: 'Please wait...' }); this._projectView.clearCollectionItemInfoView(); - options.resource.destroy({success: (model) => this._handleDeleteSuccess(model, this._collection)}); + if (options.resource.size) { + options.resource.forEach(resource => resource.destroy({ success: model => this._handleDeleteSuccess(model, this._collection) })); + } else { + options.resource.destroy({ success: model => this._handleDeleteSuccess(model, this._collection) }); + } + } + + /** + * Handle command delete Resource confirm modal window. + */ + _handleCommandResourceDeleteConfirm(options) { + console.log(options.resource.size); + let names = options.resource.size ? [...options.resource].map(resource => resource.get('name')) : options.resource.get('name'); + console.log(names); + var view = new ViewDeleteConfirm({ + type: 'resource', + names: names, + toDelete: options.resource + }); + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_SHOW, { + content: view, + title: 'Deleting Resource' + }); } /** * Handle command download Resource. */ - _handleRequestResourceDownload(options) - { + _handleRequestResourceDownload(options) { var mimetype = options.resource.get('resource_type_full').mimetype; var ext = options.resource.get('resource_type_full').extension; var filename = options.resource.get('name') + '.' + ext; @@ -166,14 +176,12 @@ export default class ControllerResource extends BaseController /** * Handle command save Resource. */ - _handleCommandResourceSave(options) - { - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_SHOW_IMPORTANT, {title: 'Saving Resource', content: 'Please wait...'}); - options.resource.save(options.fields, {patch: true, success: (model) => Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__RESOURCE_SAVED, {resource: model})}); + _handleCommandResourceSave(options) { + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_SHOW_IMPORTANT, { title: 'Saving Resource', content: 'Please wait...' }); + options.resource.save(options.fields, { patch: true, success: model => Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__RESOURCE_SAVED, { resource: model }) }); } - _handleCurrentResources(options) - { + _handleCurrentResources(options) { try { if (this._collection_no_page['_lastData']['project'] === options.data.project) { return this._collection_no_page; @@ -189,8 +197,7 @@ export default class ControllerResource extends BaseController /** * Handle request Resources. */ - _handleRequestResources(options) - { + _handleRequestResources(options) { this._collection = new ResourceCollection(); this._collection.fetch(options); return this._collection; @@ -199,8 +206,7 @@ export default class ControllerResource extends BaseController /** * Handle request Resources. */ - _handleRequestResourcesNoPagination(options) - { + _handleRequestResourcesNoPagination(options) { options.data.no_page = true; options.async = false; this._collection_no_page = new ResourceCollection(); @@ -211,13 +217,12 @@ export default class ControllerResource extends BaseController /** * Handle request for Resource viewer. */ - _handleRequestViewer(options) - { + _handleRequestViewer(options) { var ajaxOptions = { url: options.resource.get('url') + 'acquire/', type: 'POST', dataType: 'json', - success: (response) => this._handleSuccessAcquire(response) + success: response => this._handleSuccessAcquire(response) }; $.ajax(ajaxOptions); } @@ -225,34 +230,30 @@ export default class ControllerResource extends BaseController /** * Handle acquire success. */ - _handleSuccessAcquire(response) - { + _handleSuccessAcquire(response) { window.open(response.working_url, '', '_blank'); } /** * Handle create success. */ - _handleCreateSuccess(resource, collection) - { + _handleCreateSuccess(resource, collection) { collection.add(resource); - Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__RESOURCE_CREATED, {resource: resource}); + Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__RESOURCE_CREATED, { resource: resource }); } /** * Handle delete success. */ - _handleDeleteSuccess(model, collection) - { + _handleDeleteSuccess(model, collection) { collection.remove(model); - Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__RESOURCE_DELETED, {resource: model}); + Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__RESOURCE_DELETED, { resource: model }); } /** * Handle generic success. */ - _handleSuccessGeneric(options) - { + _handleSuccessGeneric(options) { Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_HIDE); Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__GLOBAL_RESOURCELABELS_LOAD, {}); } diff --git a/rodan-client/code/src/js/Controllers/ControllerWorkflow.js b/rodan-client/code/src/js/Controllers/ControllerWorkflow.js index 5bf5099d1..7cb9faf7b 100644 --- a/rodan-client/code/src/js/Controllers/ControllerWorkflow.js +++ b/rodan-client/code/src/js/Controllers/ControllerWorkflow.js @@ -6,20 +6,19 @@ import ViewWorkflowCollection from 'js/Views/Master/Main/Workflow/Collection/Vie import Workflow from 'js/Models/Workflow'; import WorkflowCollection from 'js/Collections/WorkflowCollection'; import ViewProject from 'js/Views/Master/Main/Project/Individual/ViewProject'; +import ViewDeleteConfirm from '../Views/Master/Main/Shared/ViewDeleteConfirm'; /** * Controller for Workflows. */ -export default class ControllerWorkflow extends BaseController -{ -/////////////////////////////////////////////////////////////////////////////////////// -// PRIVATE METHODS - Initialization -/////////////////////////////////////////////////////////////////////////////////////// +export default class ControllerWorkflow extends BaseController { + /////////////////////////////////////////////////////////////////////////////////////// + // PRIVATE METHODS - Initialization + /////////////////////////////////////////////////////////////////////////////////////// /** * Initialize Radio. */ - _initializeRadio() - { + _initializeRadio() { // Events. Radio.channel('rodan').on(RODAN_EVENTS.EVENT__WORKFLOW_SELECTED_COLLECTION, options => this._handleEventCollectionSelected(options)); Radio.channel('rodan').on(RODAN_EVENTS.EVENT__WORKFLOW_SELECTED, options => this._handleEventItemSelected(options)); @@ -30,99 +29,106 @@ export default class ControllerWorkflow extends BaseController // Requests. Radio.channel('rodan').reply(RODAN_EVENTS.REQUEST__WORKFLOW_SAVE, options => this._handleRequestSaveWorkflow(options)); Radio.channel('rodan').reply(RODAN_EVENTS.REQUEST__WORKFLOW_DELETE, options => this._handleCommandDeleteWorkflow(options)); + Radio.channel('rodan').reply(RODAN_EVENTS.REQUEST__WORKFLOW_DELETE_CONFIRM, options => this._handleCommandDeleteWorkflowConfirm(options)); Radio.channel('rodan').reply(RODAN_EVENTS.REQUEST__WORKFLOW_IMPORT, options => this._handleCommandImportWorkflow(options)); Radio.channel('rodan').reply(RODAN_EVENTS.REQUEST__WORKFLOW_CREATE, options => this._handleCommandAddWorkflow(options)); Radio.channel('rodan').reply(RODAN_EVENTS.REQUEST__WORKFLOW_EXPORT, options => this._handleCommandExportWorkflow(options)); } -/////////////////////////////////////////////////////////////////////////////////////// -// PRIVATE METHODS - Radio handlers -/////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////// + // PRIVATE METHODS - Radio handlers + /////////////////////////////////////////////////////////////////////////////////////// /** * Handle collection selection. */ - _handleEventCollectionSelected(options) - { + _handleEventCollectionSelected(options) { this._collection = new WorkflowCollection(); - this._collection.fetch({data: {project: options.project.id}}); - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__UPDATER_SET_COLLECTIONS, {collections: [this._collection]}); + this._collection.fetch({ data: { project: options.project.id } }); + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__UPDATER_SET_COLLECTIONS, { collections: [this._collection] }); const activeProject = Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__PROJECT_GET_ACTIVE); - this._projectView = new ViewProject({model: activeProject}); - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MAINREGION_SHOW_VIEW, {view: this._projectView}); - this._viewCollection = new ViewWorkflowCollection({collection: this._collection}); + this._projectView = new ViewProject({ model: activeProject }); + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MAINREGION_SHOW_VIEW, { view: this._projectView }); + this._viewCollection = new ViewWorkflowCollection({ collection: this._collection }); this._projectView.showCollection(this._viewCollection); } /** * Handle item selection. */ - _handleEventItemSelected(options) - { - this._viewItem = new ViewWorkflow({model: options.workflow}); + _handleEventItemSelected(options) { + this._viewItem = new ViewWorkflow({ model: options.workflow }); this._projectView.showCollectionItemInfo(this._viewItem); } /** * Handle command delete workflow. */ - _handleCommandDeleteWorkflow(options) - { - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_SHOW_IMPORTANT, {title: 'Deleting Workflow', content: 'Please wait...'}); + _handleCommandDeleteWorkflow(options) { + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_SHOW_IMPORTANT, { title: 'Deleting Workflow', content: 'Please wait...' }); // Clear the individual view (if there). - if (this._viewItem !== null && options.workflow === this._viewItem.model) - { + if (this._viewItem !== null && options.workflow === this._viewItem.model) { this._projectView.clearCollectionItemInfoView(); } - options.workflow.destroy({success: (model) => this._handleDeleteSuccess(model, this._collection)}); + options.workflow.destroy({ success: model => this._handleDeleteSuccess(model, this._collection) }); + } + + /** + * Handle command delete workflow confirm modal window. + */ + _handleCommandDeleteWorkflowConfirm(options) { + var view = new ViewDeleteConfirm({ + type: 'workflow', + names: options.workflow.get('name'), + toDelete: options.workflow + }); + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_SHOW, { + content: view, + title: 'Deleting Workflow' + }); } /** * Handle command add workflow. */ - _handleCommandAddWorkflow(options) - { - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_SHOW_IMPORTANT, {title: 'Creating Workflow', content: 'Please wait...'}); - var workflow = new Workflow({project: options.project.get('url'), name: 'untitled'}); - workflow.save({}, {success: (model) => this._handleCreateSuccess(model, this._collection)}); + _handleCommandAddWorkflow(options) { + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_SHOW_IMPORTANT, { title: 'Creating Workflow', content: 'Please wait...' }); + var workflow = new Workflow({ project: options.project.get('url'), name: 'untitled' }); + workflow.save({}, { success: model => this._handleCreateSuccess(model, this._collection) }); } /** * Handle save workflow. */ - _handleRequestSaveWorkflow(options) - { - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_SHOW_IMPORTANT, {title: 'Saving Workflow', content: 'Please wait...'}); - options.workflow.save(options.fields, {patch: true, success: (model) => Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__WORKFLOW_SAVED, {workflow: model})}); + _handleRequestSaveWorkflow(options) { + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_SHOW_IMPORTANT, { title: 'Saving Workflow', content: 'Please wait...' }); + options.workflow.save(options.fields, { patch: true, success: model => Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__WORKFLOW_SAVED, { workflow: model }) }); } /** * Handle export workflow. */ - _handleCommandExportWorkflow(options) - { - options.workflow.sync('read', options.workflow, {data: {export: true}, success: (result) => this._handleExportSuccess(result, options.workflow)}); + _handleCommandExportWorkflow(options) { + options.workflow.sync('read', options.workflow, { data: { export: true }, success: result => this._handleExportSuccess(result, options.workflow) }); } /** * Handle import workflow. */ - _handleCommandImportWorkflow(options) - { - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_SHOW_IMPORTANT, {title: 'Importing Workflow', content: 'Please wait...'}); + _handleCommandImportWorkflow(options) { + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_SHOW_IMPORTANT, { title: 'Importing Workflow', content: 'Please wait...' }); var fileReader = new FileReader(); - fileReader.onerror = (event) => this._handleFileReaderError(event); - fileReader.onload = (event) => this._handleFileReaderLoaded(event, options.project); + fileReader.onerror = event => this._handleFileReaderError(event); + fileReader.onload = event => this._handleFileReaderLoaded(event, options.project); fileReader.readAsText(options.file); } -/////////////////////////////////////////////////////////////////////////////////////// -// PRIVATE METHODS - FileReader handlers -/////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////// + // PRIVATE METHODS - FileReader handlers + /////////////////////////////////////////////////////////////////////////////////////// /** * Handle FileReader error. */ - _handleFileReaderError(event) - { + _handleFileReaderError(event) { // TODO error console.error(event); } @@ -130,57 +136,51 @@ export default class ControllerWorkflow extends BaseController /** * Handle FileReader loaded. */ - _handleFileReaderLoaded(event, project) - { - var workflow = new Workflow({project: project.get('url'), serialized: JSON.parse(event.target.result)}); - workflow.save({}, {success: (model) => this._handleImportSuccess(model, this._collection)}); + _handleFileReaderLoaded(event, project) { + var workflow = new Workflow({ project: project.get('url'), serialized: JSON.parse(event.target.result) }); + workflow.save({}, { success: model => this._handleImportSuccess(model, this._collection) }); } -/////////////////////////////////////////////////////////////////////////////////////// -// PRIVATE METHODS - REST handlers -/////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////// + // PRIVATE METHODS - REST handlers + /////////////////////////////////////////////////////////////////////////////////////// /** * Handle create success. */ - _handleCreateSuccess(model, collection) - { + _handleCreateSuccess(model, collection) { collection.add(model); - Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__WORKFLOW_CREATED, {workflow: model}); + Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__WORKFLOW_CREATED, { workflow: model }); } /** * Handle delete success. */ - _handleDeleteSuccess(model, collection) - { + _handleDeleteSuccess(model, collection) { collection.remove(model); - Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__WORKFLOW_DELETED, {workflow: model}); + Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__WORKFLOW_DELETED, { workflow: model }); } /** * Handle export success. */ - _handleExportSuccess(result, model) - { + _handleExportSuccess(result, model) { var data = JSON.stringify(result); - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__DOWNLOAD_START, {data: data, filename: model.get('name'), mimetype: 'application/json'}); + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__DOWNLOAD_START, { data: data, filename: model.get('name'), mimetype: 'application/json' }); } /** * Handle import success. */ - _handleImportSuccess(model, collection) - { + _handleImportSuccess(model, collection) { collection.add(model, {}); - Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__WORKFLOW_CREATED, {workflow: model}); + Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__WORKFLOW_CREATED, { workflow: model }); //Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__WORKFLOWBUILDER_VALIDATE_WORKFLOW, {workflow: model}); } /** * Handle generic success. */ - _handleSuccessGeneric(options) - { + _handleSuccessGeneric(options) { Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_HIDE); } -} \ No newline at end of file +} diff --git a/rodan-client/code/src/js/Items/BaseItem.js b/rodan-client/code/src/js/Items/BaseItem.js index 4f39c3cf7..e40fbf72a 100644 --- a/rodan-client/code/src/js/Items/BaseItem.js +++ b/rodan-client/code/src/js/Items/BaseItem.js @@ -9,18 +9,15 @@ let itemMap = null; /** * Base Item in WorkflowBuilder */ -class BaseItem extends paper.Path -{ -/////////////////////////////////////////////////////////////////////////////////////// -// PUBLIC STATIC METHODS -/////////////////////////////////////////////////////////////////////////////////////// +class BaseItem extends paper.Path { + /////////////////////////////////////////////////////////////////////////////////////// + // PUBLIC STATIC METHODS + /////////////////////////////////////////////////////////////////////////////////////// /** * Returns associated item given a URL. */ - static getAssociatedItem(url) - { - if (!itemMap) - { + static getAssociatedItem(url) { + if (!itemMap) { itemMap = {}; } return itemMap[url]; @@ -29,10 +26,8 @@ class BaseItem extends paper.Path /** * Associates given item with URL. */ - static associateItemWithUrl(item, url) - { - if (!itemMap) - { + static associateItemWithUrl(item, url) { + if (!itemMap) { itemMap = {}; } itemMap[url] = item; @@ -41,14 +36,11 @@ class BaseItem extends paper.Path /** * Removes item from map with he provided URL. */ - static removeItemFromMap(url) - { - if (!itemMap) - { + static removeItemFromMap(url) { + if (!itemMap) { itemMap = {}; } - if (itemMap[url]) - { + if (itemMap[url]) { delete itemMap[url]; } } @@ -56,14 +48,11 @@ class BaseItem extends paper.Path /** * Update all items. */ - static updateItems() - { - if (!itemMap) - { + static updateItems() { + if (!itemMap) { itemMap = {}; } - for (var url in itemMap) - { + for (var url in itemMap) { var item = itemMap[url]; item.update(); } @@ -72,17 +61,15 @@ class BaseItem extends paper.Path /** * Clears the map. */ - static clearMap() - { + static clearMap() { itemMap = {}; } /** * Returns context menu data for multiple items of this class. */ - static getContextMenuDataMultiple() - { - return [{channel: 'rodan-client_gui', label: 'Cancel', radiorequest: GUI_EVENTS.REQUEST__WORKFLOWBUILDER_GUI_HIDE_CONTEXTMENU}]; + static getContextMenuDataMultiple() { + return [{ channel: 'rodan-client_gui', label: 'Cancel', radiorequest: GUI_EVENTS.REQUEST__WORKFLOWBUILDER_GUI_HIDE_CONTEXTMENU }]; } /** @@ -90,31 +77,34 @@ class BaseItem extends paper.Path * @param {{x: number, y: number}} appearance - The coordinates stored in the database * @returns {{x: number, y: number}} The paper.js project coordinates */ - static appearanceToProject(appearance) - { + static appearanceToProject(appearance) { const multiplier = Rodan.Configuration.PLUGINS['rodan-client-wfbgui'].DATABASE_COORDINATES_MULTIPLIER; - return { x: appearance.x * multiplier, y: appearance.y * multiplier }; + return { + x: appearance.x * multiplier, + y: appearance.y * multiplier + }; } /** * Converts coordinates from the paper.js project coordinates to the database coordinates. * @param {{x: number, y: number}} coordinates - The paper.js project coordinates * @returns {{x: number, y: number}} The coordinates stored in the database - */ - static projectToAppearance(coordinates) - { + */ + static projectToAppearance(coordinates) { const multiplier = Rodan.Configuration.PLUGINS['rodan-client-wfbgui'].DATABASE_COORDINATES_MULTIPLIER; - return { x: coordinates.x / multiplier, y: coordinates.y / multiplier }; + return { + x: coordinates.x / multiplier, + y: coordinates.y / multiplier + }; } -/////////////////////////////////////////////////////////////////////////////////////// -// PUBLIC METHODS -/////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////// + // PUBLIC METHODS + /////////////////////////////////////////////////////////////////////////////////////// /** * Constructor. */ - constructor(options) - { + constructor(options) { super(options.segments); this._initializeRadio(options); this._initializeAppearance(options); @@ -126,19 +116,16 @@ class BaseItem extends paper.Path /** * Returns associated model. */ - getModel() - { + getModel() { return this._model; } /** * Returns context menu data for single item of this class. */ - getContextMenuDataSingle() - { + getContextMenuDataSingle() { var menuItems = []; - if (this.menuItems) - { + if (this.menuItems) { menuItems = this.menuItems; } return menuItems; @@ -147,20 +134,17 @@ class BaseItem extends paper.Path /** * Return true if this item can be moved by itself. */ - isMoveable() - { + isMoveable() { return true; } /** * Moves the item. */ - move(delta) - { + move(delta) { this.position.x += delta.x; this.position.y += delta.y; - if (this._text !== null) - { + if (this._text !== null) { this._text.position = this.bounds.center; } this._hasMoved = true; @@ -169,11 +153,9 @@ class BaseItem extends paper.Path /** * Set position. */ - setPosition(point) - { + setPosition(point) { this.position = point; - if (this._text !== null) - { + if (this._text !== null) { this._text.position = this.bounds.center; } this._hasMoved = true; @@ -182,8 +164,7 @@ class BaseItem extends paper.Path /** * Set visibility. */ - setVisible(visible) - { + setVisible(visible) { this.visible = visible; this._text.visible = this._useText && this.visible; } @@ -191,8 +172,7 @@ class BaseItem extends paper.Path /** * Destroy. */ - destroy() - { + destroy() { BaseItem.removeItemFromMap(this._modelURL); this._text.remove(); this.remove(); @@ -201,17 +181,18 @@ class BaseItem extends paper.Path /** * Updates the position to the server. */ - updatePositionToServer() - { - if (this.isMoveable() && this._hasMoved) - { + updatePositionToServer() { + if (this.isMoveable() && this._hasMoved) { // If an ID exists, we know it exists on the server, so we can patch it. // Else if we haven't tried saving it before, do it. This should create // a new model on the server. - if (this._modelId || !this._coordinateSetSaveAttempted) - { + if (this._modelId || !this._coordinateSetSaveAttempted) { this._coordinateSetSaveAttempted = true; - const appearance = BaseItem.projectToAppearance(this.position); + const topLeft = { + x: this.bounds.topLeft.x, + y: this.bounds.topLeft.y + }; + const appearance = BaseItem.projectToAppearance(topLeft); this._model.set({ appearance }); this._model.save(); this._hasMoved = false; @@ -222,33 +203,28 @@ class BaseItem extends paper.Path /** * Gets coordinates from server. */ - loadCoordinates() - { - + loadCoordinates() { this._handleCoordinateLoadSuccess(this._model); } /** * Returns associated model ID. */ - getModelID() - { + getModelID() { return this._modelId; } /** * Returns associated model URL. */ - getModelURL() - { + getModelURL() { return this._modelURL; } /** * Highlights this object. */ - setHighlight(highlighted) - { + setHighlight(highlighted) { this.strokeColor = highlighted ? Rodan.Configuration.PLUGINS['rodan-client-wfbgui'].STROKE_COLOR_SELECTED : Rodan.Configuration.PLUGINS['rodan-client-wfbgui'].STROKE_COLOR; this.strokeWidth = highlighted ? Rodan.Configuration.PLUGINS['rodan-client-wfbgui'].STROKE_WIDTH_SELECTED : Rodan.Configuration.PLUGINS['rodan-client-wfbgui'].STROKE_WIDTH; } @@ -256,14 +232,10 @@ class BaseItem extends paper.Path /** * Gets description. */ - getDescription() - { - if (this._description && this._description !== '') - { + getDescription() { + if (this._description && this._description !== '') { return this._description; - } - else - { + } else { return 'no description available'; } } @@ -271,70 +243,61 @@ class BaseItem extends paper.Path /** * Sets temporary color. */ - setTemporaryColor(color) - { + setTemporaryColor(color) { this._temporaryColor = color; } /** * Clears temporary color. */ - clearTemporaryColor() - { + clearTemporaryColor() { this._temporaryColor = null; } -/////////////////////////////////////////////////////////////////////////////////////// -// ABSTRACT METHODS -/////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////// + // ABSTRACT METHODS + /////////////////////////////////////////////////////////////////////////////////////// /** * Abstract method. Update. */ - update() - { + update() { // TODO - better way to do abstract methods console.error('This must be defined in sub-class.'); } -/////////////////////////////////////////////////////////////////////////////////////// -// PRIVATE METHODS - Backbone event handlers -/////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////// + // PRIVATE METHODS - Backbone event handlers + /////////////////////////////////////////////////////////////////////////////////////// /** * Handle event model sync. */ - _handleEventModelSync(options) - { + _handleEventModelSync(options) { this._model = options.model; - switch (options.options.task) - { - case 'save': - { + switch (options.options.task) { + case 'save': { this._text.content = this._model.get('name'); this._description = this._model.getDescription(); break; } - case 'destroy': - { + case 'destroy': { this.destroy(); break; } - default: - { + default: { break; } } } -/////////////////////////////////////////////////////////////////////////////////////// -// PRIVATE METHODS -/////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////// + // PRIVATE METHODS + /////////////////////////////////////////////////////////////////////////////////////// /** * Initialize hover. */ - _initializeHover(options) - { + _initializeHover(options) { this._timerEvent = null; this._popup = new paper.PointText(new paper.Point(0, 0)); } @@ -342,8 +305,7 @@ class BaseItem extends paper.Path /** * Initialize appearance. */ - _initializeAppearance(options) - { + _initializeAppearance(options) { this.strokeColor = Rodan.Configuration.PLUGINS['rodan-client-wfbgui'].STROKE_COLOR; this.strokeJoin = 'round'; this.strokeWidth = Rodan.Configuration.PLUGINS['rodan-client-wfbgui'].STROKE_WIDTH; @@ -354,9 +316,8 @@ class BaseItem extends paper.Path /** * Initialize model binding. */ - _initializeModelBinding(options) - { - this._model = options.model ? options.model: null; + _initializeModelBinding(options) { + this._model = options.model ? options.model : null; this._modelId = options.model ? options.model.id : null; this._modelURL = options.model ? options.model.get('url') : null; this._description = options.model ? options.model.getDescription() : null; @@ -369,8 +330,7 @@ class BaseItem extends paper.Path /** * Initialize event handlers. */ - _initializeInputEventHandlers(options) - { + _initializeInputEventHandlers(options) { this.onMouseDown = event => this._handleMouseEvent(event); this.onMouseUp = event => this._handleMouseEvent(event); this.onClick = event => this._handleMouseEvent(event); @@ -388,9 +348,8 @@ class BaseItem extends paper.Path /** * Initialize text. */ - _initializeText(options) - { - this._useText = (options.hasOwnProperty('text') && options.text === true); + _initializeText(options) { + this._useText = options.hasOwnProperty('text') && options.text === true; this._text = new paper.PointText(new paper.Point(0, 0)); this._text.justification = 'center'; this._text.fillColor = Rodan.Configuration.PLUGINS['rodan-client-wfbgui'].STROKE_COLOR; @@ -398,8 +357,7 @@ class BaseItem extends paper.Path this._text.content = ''; this._text.position = this.bounds.center; this.addChild(this._text); - if (options.model) - { + if (options.model) { this._text.content = options.model.get('name'); } } @@ -407,12 +365,10 @@ class BaseItem extends paper.Path /** * Initialize radio. */ - _initializeRadio(options) - { + _initializeRadio(options) { this.guiChannel = Radio.channel('rodan-client_gui'); this.rodanChannel = Radio.channel('rodan'); - if (options && options.model) - { + if (options && options.model) { this.rodanChannel.on(Rodan.RODAN_EVENTS.EVENT__MODEL_SYNC + options.model.get('url'), options => this._handleEventModelSync(options)); } } @@ -420,23 +376,20 @@ class BaseItem extends paper.Path /** * Handle coordinate load success. */ - _handleCoordinateLoadSuccess(model) - { - var appearance = model.get("appearance"); - if (appearance) - { + _handleCoordinateLoadSuccess(model) { + var appearance = model.get('appearance'); + if (appearance) { const { x, y } = BaseItem.appearanceToProject(appearance); - this.position = new paper.Point(x, y); + this.bounds.topLeft = new paper.Point(x, y); + this.position = this.bounds.center; } } /** * Shows popup. */ - _showPopup(event) - { - if ($('div#canvas-tooltip')) - { + _showPopup(event) { + if ($('div#canvas-tooltip')) { var description = this.getDescription(); var tooltip = $('div#canvas-tooltip'); tooltip.css('visibility', 'visible'); @@ -449,10 +402,8 @@ class BaseItem extends paper.Path /** * Hide popup. */ - _hidePopup() - { - if ($('div#canvas-tooltip')) - { + _hidePopup() { + if ($('div#canvas-tooltip')) { $('div#canvas-tooltip').css('visibility', 'hidden'); } } @@ -460,55 +411,46 @@ class BaseItem extends paper.Path /** * Handle mouse event. */ - _handleMouseEvent(event) - { + _handleMouseEvent(event) { // We do this because paperjs doesn't bubble up events. // This line guarantees that events caught by the TEXT actually get to the parent base item. event.target = this; - switch (event.type) - { - case 'mouseenter': - { + switch (event.type) { + case 'mouseenter': { this._timerEvent = setTimeout(() => this._showPopup(event), Rodan.Configuration.PLUGINS['rodan-client-wfbgui'].HOVER_TIME); paper.handleMouseEvent(event); break; } - case 'mouseleave': - { + case 'mouseleave': { this._hidePopup(); clearTimeout(this._timerEvent); paper.handleMouseEvent(event); break; } - case 'mouseup': - { + case 'mouseup': { this._handleMouseUp(event); break; } - case 'mousedown': - { + case 'mousedown': { this._handleMouseDown(event); break; } - case 'click': - { + case 'click': { this._handleClick(event); break; } - case 'doubleclick': - { + case 'doubleclick': { this._handleDoubleClick(event); break; } - default: - { + default: { paper.handleMouseEvent(event); break; } @@ -518,8 +460,7 @@ class BaseItem extends paper.Path /** * Handle mouse enter. */ - _handleMouseEnter(mouseEvent) - { + _handleMouseEnter(mouseEvent) { this._timerEvent = setTimeout(() => this._showPopup(mouseEvent), Rodan.Configuration.PLUGINS['rodan-client-wfbgui'].HOVER_TIME); paper.handleMouseEvent(mouseEvent); } @@ -527,44 +468,39 @@ class BaseItem extends paper.Path /** * Handle mouse leave. */ - _handleMouseLeave(mouseEvent) - { + _handleMouseLeave(mouseEvent) { this._hidePopup(); clearTimeout(this._timerEvent); - paper.handleMouseEvent(mouseEvent); + paper.handleMouseEvent(mouseEvent); } /** * Handle mouse up. */ - _handleMouseUp(mouseEvent) - { + _handleMouseUp(mouseEvent) { paper.handleMouseEvent(mouseEvent); } /** * Handle mouse down. */ - _handleMouseDown(mouseEvent) - { + _handleMouseDown(mouseEvent) { paper.handleMouseEvent(mouseEvent); } /** * Handle mouse click. */ - _handleClick(mouseEvent) - { + _handleClick(mouseEvent) { paper.handleMouseEvent(mouseEvent); } /** * Handle mouse double click. */ - _handleDoubleClick(mouseEvent) - { + _handleDoubleClick(mouseEvent) { paper.handleMouseEvent(mouseEvent); } } -export default BaseItem; \ No newline at end of file +export default BaseItem; diff --git a/rodan-client/code/src/js/Shared/RODAN_EVENTS.js b/rodan-client/code/src/js/Shared/RODAN_EVENTS.js index f60176304..e8c3f2417 100644 --- a/rodan-client/code/src/js/Shared/RODAN_EVENTS.js +++ b/rodan-client/code/src/js/Shared/RODAN_EVENTS.js @@ -21,27 +21,24 @@ let _instance = null; /** * Backbone.Radio events use in the client. Do not instantiate this class. */ -class RODAN_EVENTS -{ +class RODAN_EVENTS { /** @ignore */ - constructor() - { - if (_instance) - { + constructor() { + if (_instance) { throw new Error('this class cannot be instantiated more than once'); } _instance = this; /** @ignore */ - this.REQUEST__RESOURCE_SHOWLAYOUTVIEW = 'REQUEST__RESOURCE_SHOWLAYOUTVIEW'; // Show LayoutView for Resource control (outside of the primary Resources view). This tells the ControllerResource which LayoutView to reference upon events. Takes {layoutView: LayoutView}. + this.REQUEST__RESOURCE_SHOWLAYOUTVIEW = 'REQUEST__RESOURCE_SHOWLAYOUTVIEW'; // Show LayoutView for Resource control (outside of the primary Resources view). This tells the ControllerResource which LayoutView to reference upon events. Takes {layoutView: LayoutView}. /** @ignore */ - this.REQUEST__RUNJOB_SHOWLAYOUTVIEW = 'REQUEST__RUNJOB_SHOWLAYOUTVIEW'; // Show LayoutView for RunJob control (outside of the primary RunJobs view). This tells the ControllerRunJob which LayoutView to reference upon events. Takes {layoutView: LayoutView}. + this.REQUEST__RUNJOB_SHOWLAYOUTVIEW = 'REQUEST__RUNJOB_SHOWLAYOUTVIEW'; // Show LayoutView for RunJob control (outside of the primary RunJobs view). This tells the ControllerRunJob which LayoutView to reference upon events. Takes {layoutView: LayoutView}. /** @ignore */ - this.EVENT__SERVER_WENTAWAY = 'EVENT__SERVER_WENTAWAY'; // Called on server disconnect. No pass. + this.EVENT__SERVER_WENTAWAY = 'EVENT__SERVER_WENTAWAY'; // Called on server disconnect. No pass. /** @ignore */ - this.EVENT__SERVER_PANIC = 'EVENT__SERVER_PANIC'; // Called when the app suspects that something went wrong. + this.EVENT__SERVER_PANIC = 'EVENT__SERVER_PANIC'; // Called when the app suspects that something went wrong. /** @ignore */ - this.REQUEST__SYSTEM_HANDLE_ERROR = 'REQUEST__SYSTEM_HANDLE_ERROR'; // Sends error to error handler. Takes {model: BaseModel, response: HTTP response, option: associated options}. + this.REQUEST__SYSTEM_HANDLE_ERROR = 'REQUEST__SYSTEM_HANDLE_ERROR'; // Sends error to error handler. Takes {model: BaseModel, response: HTTP response, option: associated options}. /////////////////////////////////////////////////////////////////////////////////////// // Authentication @@ -89,9 +86,9 @@ class RODAN_EVENTS // General /////////////////////////////////////////////////////////////////////////////////////// /** Request "API" information to be show. */ - this.REQUEST__SHOW_API = 'REQUEST__SHOW_API', - /** Request last 100 Radio events. Returns [{name (string), event (string), options (object)}]. */ - this.REQUEST__LOG = 'REQUEST__LOG'; + (this.REQUEST__SHOW_API = 'REQUEST__SHOW_API'), + /** Request last 100 Radio events. Returns [{name (string), event (string), options (object)}]. */ + (this.REQUEST__LOG = 'REQUEST__LOG'); /** Request "About" information be shown. */ this.REQUEST__SHOW_ABOUT = 'REQUEST__SHOW_ABOUT'; /** Request "Help" page be shown. */ @@ -100,13 +97,13 @@ class RODAN_EVENTS this.REQUEST__SHOW_NAVIGATION_PAGINATION = 'REQUEST__SHOW_NAVIGATION_PAGINATION'; /** Request update navigation pagination */ this.REQUEST__UPDATE_NAVIGATION_PAGINATION = 'REQUEST__UPDATE_NAVIGATION_PAGINATION'; - /** Request pagination from navigation bar for first */ + /** Request pagination from navigation bar for first */ this.REQUEST__NAVIGATION_PAGINATION_FIRST = 'REQUEST__NAVIGATION_PAGINATION_FIRST'; - /** Request pagination from navigation bar for previous */ + /** Request pagination from navigation bar for previous */ this.REQUEST__NAVIGATION_PAGINATION_PREVIOUS = 'REQUEST__NAVIGATION_PAGINATION_PREVIOUS'; - /** Request pagination from navigation bar for next */ + /** Request pagination from navigation bar for next */ this.REQUEST__NAVIGATION_PAGINATION_NEXT = 'REQUEST__NAVIGATION_PAGINATION_NEXT'; - /** Request pagination from navigation bar for last */ + /** Request pagination from navigation bar for last */ this.REQUEST__NAVIGATION_PAGINATION_LAST = 'REQUEST__NAVIGATION_PAGINATION_LAST'; /////////////////////////////////////////////////////////////////////////////////////// @@ -181,19 +178,19 @@ class RODAN_EVENTS // Project /////////////////////////////////////////////////////////////////////////////////////// /** Triggered when User has been added as Project admin. Sends {project: Project}. */ - this.EVENT__PROJECT_ADDED_USER_ADMIN = 'EVENT__PROJECT_ADDED_USER_ADMIN', - /** Triggered when User has been added as Project worker. Sends {project: Project}. */ - this.EVENT__PROJECT_ADDED_USER_WORKER = 'EVENT__PROJECT_ADDED_USER_WORKER', - /** Triggered when Project has been created. Sends {project: Project}. */ - this.EVENT__PROJECT_CREATED = 'EVENT__PROJECT_CREATED'; + (this.EVENT__PROJECT_ADDED_USER_ADMIN = 'EVENT__PROJECT_ADDED_USER_ADMIN'), + /** Triggered when User has been added as Project worker. Sends {project: Project}. */ + (this.EVENT__PROJECT_ADDED_USER_WORKER = 'EVENT__PROJECT_ADDED_USER_WORKER'), + /** Triggered when Project has been created. Sends {project: Project}. */ + (this.EVENT__PROJECT_CREATED = 'EVENT__PROJECT_CREATED'); /** Triggered when Project has been deleted. Sends {project: Project}. */ this.EVENT__PROJECT_DELETED = 'EVENT__PROJECT_DELETED'; /** Triggered when User has been removed as Project admin. Sends {project: Project}. */ - this.EVENT__PROJECT_REMOVED_USER_ADMIN = 'EVENT__PROJECT_REMOVED_USER_ADMIN', - /** Triggered when User has been removed as Project worker. Sends {project: Project}. */ - this.EVENT__PROJECT_REMOVED_USER_WORKER = 'EVENT__PROJECT_REMOVED_USER_WORKER', - /** Triggered when Project has been saved. Sends {project: Project}. */ - this.EVENT__PROJECT_SAVED = 'EVENT__PROJECT_SAVED'; + (this.EVENT__PROJECT_REMOVED_USER_ADMIN = 'EVENT__PROJECT_REMOVED_USER_ADMIN'), + /** Triggered when User has been removed as Project worker. Sends {project: Project}. */ + (this.EVENT__PROJECT_REMOVED_USER_WORKER = 'EVENT__PROJECT_REMOVED_USER_WORKER'), + /** Triggered when Project has been saved. Sends {project: Project}. */ + (this.EVENT__PROJECT_SAVED = 'EVENT__PROJECT_SAVED'); /** Triggered when the user selects an individual Project. Sends {project: Project}. */ this.EVENT__PROJECT_SELECTED = 'EVENT__PROJECT_SELECTED'; /** Triggered when the user selects to see all available Projects. */ @@ -201,21 +198,23 @@ class RODAN_EVENTS /** Triggered when Project admin interface has been selected. Takes {project: Project}. */ this.EVENT__PROJECT_USERS_SELECTED = 'EVENT__PROJECT_USERS_SELECTED'; /** Request a User be added as Project admin. Takes {project: Project, username: string} */ - this.REQUEST__PROJECT_ADD_USER_ADMIN = 'REQUEST__PROJECT_ADD_USER_ADMIN', - /** Request a User be added as Project worker. Takes {project: Project, username: string} */ - this.REQUEST__PROJECT_ADD_USER_WORKER = 'REQUEST__PROJECT_ADD_USER_WORKER', - /** Request a Project be created. Takes {creator: User}. */ - this.REQUEST__PROJECT_CREATE = 'REQUEST__PROJECT_CREATE'; + (this.REQUEST__PROJECT_ADD_USER_ADMIN = 'REQUEST__PROJECT_ADD_USER_ADMIN'), + /** Request a User be added as Project worker. Takes {project: Project, username: string} */ + (this.REQUEST__PROJECT_ADD_USER_WORKER = 'REQUEST__PROJECT_ADD_USER_WORKER'), + /** Request a Project be created. Takes {creator: User}. */ + (this.REQUEST__PROJECT_CREATE = 'REQUEST__PROJECT_CREATE'); /** Request a Project be deleted. Takes {project: Project}. */ this.REQUEST__PROJECT_DELETE = 'REQUEST__PROJECT_DELETE'; + /** Request a Project delete confirm modal window. Takes {project: Project}. */ + this.REQUEST__PROJECT_DELETE_CONFIRM = 'REQUEST__PROJECT_DELETE_CONFIRM'; /** Request currently active/open Project. Returns Project (or null). */ this.REQUEST__PROJECT_GET_ACTIVE = 'REQUEST__PROJECT_GET_ACTIVE'; /** Request a User be removed as Project admin. Takes {project: Project, user: User} */ - this.REQUEST__PROJECT_REMOVE_USER_ADMIN = 'REQUEST__PROJECT_REMOVE_USER_ADMIN', - /** Request a User be removed as Project worker. Takes {project: Project, user: User} */ - this.REQUEST__PROJECT_REMOVE_USER_WORKER = 'REQUEST__PROJECT_REMOVE_USER_WORKER', - /** Request a Project be saved/updated. Takes {project: Project, fields: {object with attributes to change}}. */ - this.REQUEST__PROJECT_SAVE = 'REQUEST__PROJECT_SAVE'; + (this.REQUEST__PROJECT_REMOVE_USER_ADMIN = 'REQUEST__PROJECT_REMOVE_USER_ADMIN'), + /** Request a User be removed as Project worker. Takes {project: Project, user: User} */ + (this.REQUEST__PROJECT_REMOVE_USER_WORKER = 'REQUEST__PROJECT_REMOVE_USER_WORKER'), + /** Request a Project be saved/updated. Takes {project: Project, fields: {object with attributes to change}}. */ + (this.REQUEST__PROJECT_SAVE = 'REQUEST__PROJECT_SAVE'); /** Request a Project be set as active Project. Takes {project: Project}. */ this.REQUEST__PROJECT_SET_ACTIVE = 'REQUEST__PROJECT_SET_ACTIVE'; @@ -236,6 +235,8 @@ class RODAN_EVENTS this.REQUEST__RESOURCE_CREATE = 'REQUEST__RESOURCE_CREATE'; /** Request a Resource be deleted. Takes {resource: Resource}. */ this.REQUEST__RESOURCE_DELETE = 'REQUEST__RESOURCE_DELETE'; + /** Request a Resource delete confirm modal window. Takes {resource: Resource}. */ + this.REQUEST__RESOURCE_DELETE_CONFIRM = 'REQUEST__RESOURCE_DELETE_CONFIRM'; /** Request a Resource be downloaded. Takes {resource: Resource}. */ this.REQUEST__RESOURCE_DOWNLOAD = 'REQUEST__RESOURCE_DOWNLOAD'; /** Request a Resource be saved/updated. Takes {resource: Resource, fields: {object with attributes to change}}. */ @@ -407,9 +408,11 @@ class RODAN_EVENTS this.REQUEST__WORKFLOW_CREATE = 'REQUEST__WORKFLOW_CREATE'; /** Request a Workflow be deleted. Takes {workflow: Workflow}. */ this.REQUEST__WORKFLOW_DELETE = 'REQUEST__WORKFLOW_DELETE'; + /** Request a Workflow delete confirm modal window. Takes {workflow: Workflow}. */ + this.REQUEST__WORKFLOW_DELETE_CONFIRM = 'REQUEST__WORKFLOW_DELETE_CONFIRM'; /** Request a Workflow be exported. Takes {workflow: Workflow}. */ this.REQUEST__WORKFLOW_EXPORT = 'REQUEST__WORKFLOW_EXPORT'; - /** Request a Workflow be imported. Takes {}. */ + /** Request a Workflow be imported. Takes {project: Project, file: File}. */ this.REQUEST__WORKFLOW_IMPORT = 'REQUEST__WORKFLOW_IMPORT'; /** Request a Workflow be saved/updated. Takes {workflow: Workflow, fields: {object with attributes to change}}. */ this.REQUEST__WORKFLOW_SAVE = 'REQUEST__WORKFLOW_SAVE'; @@ -559,37 +562,32 @@ class RODAN_EVENTS // version it requires. /////////////////////////////////////////////////////////////////////////////////////// /** @ignore **/ - this.VERSION__COMPATIBILITY = - { - 'EVENT__PROJECT_USERS_SELECTED': '1.1.5' + this.VERSION__COMPATIBILITY = { + EVENT__PROJECT_USERS_SELECTED: '1.1.5' }; } /** @ignore **/ - enforceVersionCompatibility() - { + enforceVersionCompatibility() { var serverVersionString = Radio.channel('rodan').request(this.REQUEST__SERVER_GET_VERSION); var serverVersion = serverVersionString.split('.').map(Number); - for (var event in this.VERSION__COMPATIBILITY) - { - if (this[event]) - { + for (var event in this.VERSION__COMPATIBILITY) { + if (this[event]) { var requiredVersionString = this.VERSION__COMPATIBILITY[event]; var requiredVersion = requiredVersionString.split('.').map(Number); if ( // 1) Rodan Major version is smaller - requiredVersion[0] > serverVersion[0] - // 2) Rodan Minor version is smaller, and Major - || (requiredVersion[1] > serverVersion[1] && requiredVersion[0] > serverVersion[0]) + requiredVersion[0] > serverVersion[0] || + // 2) Rodan Minor version is smaller, and Major + (requiredVersion[1] > serverVersion[1] && requiredVersion[0] > serverVersion[0]) || // 3) Rodan Patch version is smaller, and minor, and Major - || (requiredVersion[2] > serverVersion[2] && requiredVersion[1] > serverVersion[1] && requiredVersion[0] > serverVersion[0]) - ) - { + (requiredVersion[2] > serverVersion[2] && requiredVersion[1] > serverVersion[1] && requiredVersion[0] > serverVersion[0]) + ) { var requiresEvent = 'EVENT__REQUIRES_RODAN_VERSION_' + serverVersionString; this[event] = requiresEvent; var messageString = 'This feature requires Rodan Server v' + requiredVersionString + '. The Rodan Server is currently v' + serverVersionString + '.'; messageString += ' (' + event + ')'; - var modalOptions = {content: messageString}; + var modalOptions = { content: messageString }; Radio.channel('rodan').on(requiresEvent, () => Radio.channel('rodan').request(this.REQUEST__MODAL_ERROR, modalOptions)); Radio.channel('rodan').reply(requiresEvent, () => Radio.channel('rodan').request(this.REQUEST__MODAL_ERROR, modalOptions)); } diff --git a/rodan-client/code/src/js/Views/LayoutViewWorkflowBuilder.js b/rodan-client/code/src/js/Views/LayoutViewWorkflowBuilder.js index f3b949b68..e0fa0c780 100644 --- a/rodan-client/code/src/js/Views/LayoutViewWorkflowBuilder.js +++ b/rodan-client/code/src/js/Views/LayoutViewWorkflowBuilder.js @@ -254,6 +254,7 @@ LayoutViewWorkflowBuilder.prototype.ui = { settingsDropdownToggle: '#settings-dropdown-toggle', }; LayoutViewWorkflowBuilder.prototype.events = { + 'click @ui.canvasWorkspace': '_hideDropdowns', 'click @ui.buttonZoomIn': '_handleButtonZoomIn', 'click @ui.buttonZoomOut': '_handleButtonZoomOut', 'click @ui.buttonZoomReset': '_handleButtonZoomReset', diff --git a/rodan-client/code/src/js/Views/Master/Main/Project/Individual/ViewProject.js b/rodan-client/code/src/js/Views/Master/Main/Project/Individual/ViewProject.js index d6898d31b..0004c0cca 100644 --- a/rodan-client/code/src/js/Views/Master/Main/Project/Individual/ViewProject.js +++ b/rodan-client/code/src/js/Views/Master/Main/Project/Individual/ViewProject.js @@ -3,18 +3,19 @@ import _ from 'underscore'; import RODAN_EVENTS from 'js/Shared/RODAN_EVENTS'; import Marionette from 'backbone.marionette'; import Radio from 'backbone.radio'; +import ViewResourceCollection from 'js/Views/Master/Main/Resource/Collection/ViewResourceCollection'; +import ViewWorkflowCollection from 'js/Views/Master/Main/Workflow/Collection/ViewWorkflowCollection'; +import ViewWorkflowRunCollection from 'js/Views/Master/Main/WorkflowRun/Collection/ViewWorkflowRunCollection'; +import ViewRunJobCollection from 'js/Views/Master/Main/RunJob/Collection/ViewRunJobCollection'; /** * Project view. */ -export default class ViewProject extends Marionette.View -{ - +export default class ViewProject extends Marionette.View { /** * Initializes the instance. */ - initialize() - { + initialize() { this.setElement('
'); this.addRegions({ regionCollection: '#region-collection-container', @@ -28,120 +29,112 @@ export default class ViewProject extends Marionette.View * * @param {Marionette.View} view Collection view to show */ - showCollection(view) - { + showCollection(view) { this.showChildView('regionCollection', view); + + // Update tab status based on view type + $('.project-nav-bar-btn').removeClass('active'); + if (view instanceof ViewResourceCollection) { + $('#resource_count').addClass('active'); + } else if (view instanceof ViewWorkflowCollection) { + $('#workflow_count').addClass('active'); + } else if (view instanceof ViewWorkflowRunCollection) { + $('#button-workflow_runs').addClass('active'); + } else if (view instanceof ViewRunJobCollection) { + $('#button-runjobs').addClass('active'); + } } /** - * Show an item view. - * - * @param {Marionette.View} view item view to show - */ + * Show an item view. + * + * @param {Marionette.View} view item view to show + */ // showProjectInfo(view) // { // this.showChildView('regionProjectInfo', view); // } /** - * Show an item view. This is for the secondary item view. - * - * @param {Marionette.View} view item view to show - */ + * Show an item view. This is for the secondary item view. + * + * @param {Marionette.View} view item view to show + */ showCollectionItemInfo(view) { this.showChildView('regionCollectionItemInfo', view); } /** - * Clears item view. - */ - clearCollectionItemInfoView() - { + * Clears item view. + */ + clearCollectionItemInfoView() { this.getRegion('regionCollectionItemInfo').empty(); } -/////////////////////////////////////////////////////////////////////////////////////// -// PRIVATE METHODS -/////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////// + // PRIVATE METHODS + /////////////////////////////////////////////////////////////////////////////////////// /** * Handle save button. */ - _handleButtonSave() - { - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__PROJECT_SAVE, - { - project: this.model, - fields: { - name: _.escape(this.ui.textName.val()), - description: _.escape(this.ui.textDescription.val()) - } - } - ); + _handleButtonSave() { + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__PROJECT_SAVE, { + project: this.model, + fields: { + name: _.escape(this.ui.textName.val()), + description: _.escape(this.ui.textDescription.val()) + } + }); } /** * Handle delete button. */ - _handleButtonDelete() - { - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__PROJECT_DELETE, {project: this.model}); + _handleButtonDelete() { + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__PROJECT_DELETE_CONFIRM, { project: this.model }); } - + /** - * Handle RunJob button. - */ - _handleButtonRunJobs() - { - Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__RUNJOB_SELECTED_COLLECTION, {project: this.model}); - $('.project-nav-bar-btn').removeClass('active'); - $('#button-runjobs').addClass('active'); + * Handle RunJob button. + */ + _handleButtonRunJobs() { + Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__RUNJOB_SELECTED_COLLECTION, { project: this.model }); } /** - * Handle click resource count. - */ - _handleClickResourceCount() - { - Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__RESOURCE_SELECTED_COLLECTION, {project: this.model}); - $('.project-nav-bar-btn').removeClass('active'); - $('#resource_count').addClass('active'); + * Handle click resource count. + */ + _handleClickResourceCount() { + Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__RESOURCE_SELECTED_COLLECTION, { project: this.model }); } /** - * Handle click workflow count. - */ - _handleClickWorkflowCount() - { - Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__WORKFLOW_SELECTED_COLLECTION, {view: this, project: this.model}); - $('.project-nav-bar-btn').removeClass('active'); - $('#workflow_count').addClass('active'); + * Handle click workflow count. + */ + _handleClickWorkflowCount() { + Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__WORKFLOW_SELECTED_COLLECTION, { view: this, project: this.model }); } /** * Handle button WorkflowRuns. */ - _handleButtonWorkflowRuns() - { - Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__WORKFLOWRUN_SELECTED_COLLECTION, {project: this.model}); - $('.project-nav-bar-btn').removeClass('active'); - $('#button-workflow_runs').addClass('active'); + _handleButtonWorkflowRuns() { + Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__WORKFLOWRUN_SELECTED_COLLECTION, { project: this.model }); } /** - * Handle click button ResourceLists. - */ - _handleButtonResourceLists() - { - Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__RESOURCELIST_SELECTED_COLLECTION, {project: this.model}); + * Handle click button ResourceLists. + */ + _handleButtonResourceLists() { + Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__RESOURCELIST_SELECTED_COLLECTION, { project: this.model }); $('.project-nav-bar-btn').removeClass('active'); $('#resource_count').addClass('active'); } /** - * Handle button Project users. - */ - _handleButtonProjectUsers() - { - Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__PROJECT_USERS_SELECTED, {project: this.model}); + * Handle button Project users. + */ + _handleButtonProjectUsers() { + Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__PROJECT_USERS_SELECTED, { project: this.model }); $('.project-nav-bar-btn').removeClass('active'); $('#button-project_users').addClass('active'); } diff --git a/rodan-client/code/src/js/Views/Master/Main/Resource/Individual/ViewResource.js b/rodan-client/code/src/js/Views/Master/Main/Resource/Individual/ViewResource.js index 15c27de37..5db6f83db 100644 --- a/rodan-client/code/src/js/Views/Master/Main/Resource/Individual/ViewResource.js +++ b/rodan-client/code/src/js/Views/Master/Main/Resource/Individual/ViewResource.js @@ -10,20 +10,20 @@ import ViewResourceTypeCollectionItem from 'js/Views/Master/Main/ResourceType/Vi /** * Resource view. */ -export default class ViewResource extends Marionette.CollectionView -{ -/////////////////////////////////////////////////////////////////////////////////////// -// PUBLIC METHODS -/////////////////////////////////////////////////////////////////////////////////////// +export default class ViewResource extends Marionette.CollectionView { + /////////////////////////////////////////////////////////////////////////////////////// + // PUBLIC METHODS + /////////////////////////////////////////////////////////////////////////////////////// /** * Initializes the instance. */ - initialize() - { + initialize() { /** @ignore */ this.collection = Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__GLOBAL_RESOURCETYPE_COLLECTION); - this.collection.each(function(model) { model.unset('selected'); }); - var resourceType = this.collection.findWhere({url: this.model.get('resource_type')}); + this.collection.each(function (model) { + model.unset('selected'); + }); + var resourceType = this.collection.findWhere({ url: this.model.get('resource_type') }); resourceType.set('selected', 'selected'); this.setElement('
'); // set container element } @@ -31,8 +31,7 @@ export default class ViewResource extends Marionette.CollectionView /** * Initialize buttons after render. */ - onRender() - { + onRender() { var disabledDelete = this.model.get('origin') !== null; $(this.ui.buttonDelete).attr('disabled', disabledDelete); var disabledDownload = this.model.get('download') === null; @@ -48,64 +47,61 @@ export default class ViewResource extends Marionette.CollectionView /** * Initialize label field after it's attached to the DOM */ - onAttach() - { + onAttach() { tagsInput(this.ui.resourceLabels[0]); } /** * Destroy callback. */ - onDestroy() - { + onDestroy() { this.collection = null; } -/////////////////////////////////////////////////////////////////////////////////////// -// PRIVATE METHODS -/////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////// + // PRIVATE METHODS + /////////////////////////////////////////////////////////////////////////////////////// /** * Handle button save. */ - _handleClickButtonSave() - { - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__RESOURCE_SAVE, {resource: this.model, - fields: {resource_type: this.ui.selectResourceType.find(':selected').val(), - name: _.escape(this.ui.resourceName.val()), - description: _.escape(this.ui.resourceDescription.val()), - label_names: _.escape(this.ui.resourceLabels.val())}}); + _handleClickButtonSave() { + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__RESOURCE_SAVE, { + resource: this.model, + fields: { + resource_type: this.ui.selectResourceType.find(':selected').val(), + name: _.escape(this.ui.resourceName.val()), + description: _.escape(this.ui.resourceDescription.val()), + label_names: _.escape(this.ui.resourceLabels.val()) + } + }); } /** * Handle button delete. */ - _handleClickButtonDelete() - { - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__RESOURCE_DELETE, {resource: this.model}); + _handleClickButtonDelete() { + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__RESOURCE_DELETE_CONFIRM, { resource: this.model }); } /** * Handle button download. */ - _handleClickDownload() - { - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__RESOURCE_DOWNLOAD, {resource: this.model}); + _handleClickDownload() { + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__RESOURCE_DOWNLOAD, { resource: this.model }); } /** * Handle button view. */ - _handleClickView() - { - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__RESOURCE_VIEWER_ACQUIRE, {resource: this.model}); + _handleClickView() { + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__RESOURCE_VIEWER_ACQUIRE, { resource: this.model }); } - _handleDblClickTag(evt) - { + _handleDblClickTag(evt) { let labels = Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__GLOBAL_RESOURCELABEL_COLLECTION); - let model = labels.findWhere({name: evt.target.textContent}); + let model = labels.findWhere({ name: evt.target.textContent }); if (model) { - let view = new ViewResourceLabel({model: model}); + let view = new ViewResourceLabel({ model: model }); Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_SHOW, { content: view }); @@ -113,7 +109,7 @@ export default class ViewResource extends Marionette.CollectionView } } ViewResource.prototype.modelEvents = { - 'all': 'render' + all: 'render' }; ViewResource.prototype.ui = { buttonSave: '#button-main_resource_individual_save', diff --git a/rodan-client/code/src/js/Views/Master/Main/Resource/Individual/ViewResourceMulti.js b/rodan-client/code/src/js/Views/Master/Main/Resource/Individual/ViewResourceMulti.js index 30897d18f..919f3b45b 100644 --- a/rodan-client/code/src/js/Views/Master/Main/Resource/Individual/ViewResourceMulti.js +++ b/rodan-client/code/src/js/Views/Master/Main/Resource/Individual/ViewResourceMulti.js @@ -9,8 +9,7 @@ import ViewResourceTypeCollectionItem from 'js/Views/Master/Main/ResourceType/Vi /** * Resource Multi-Select View */ -export default class ViewResourceMulti extends Marionette.CollectionView -{ +export default class ViewResourceMulti extends Marionette.CollectionView { constructor(options) { super(options); this._models = options.models; @@ -30,7 +29,7 @@ export default class ViewResourceMulti extends Marionette.CollectionView } else if (rtUrl !== modelResourceTypeURL) { this.isSameType = false; } - + if (labels === undefined) { labels = modelLabels; this.labelNames = model.get('labels'); @@ -40,15 +39,16 @@ export default class ViewResourceMulti extends Marionette.CollectionView } this.collection = Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__GLOBAL_RESOURCETYPE_COLLECTION); - this.collection.each(function(model) { model.unset('selected'); }); - var resourceType = this.collection.findWhere({url: rtUrl}); + this.collection.each(function (model) { + model.unset('selected'); + }); + var resourceType = this.collection.findWhere({ url: rtUrl }); resourceType.set('selected', 'selected'); } /** * Initialize buttons after render. */ - onRender() - { + onRender() { var disabledDelete = false; var disabledDownload = false; for (var model of this._models) { @@ -69,11 +69,15 @@ export default class ViewResourceMulti extends Marionette.CollectionView $(this.ui.buttonView).attr('disabled', true); } - onAttach() - { + onAttach() { if (this.isSameLabel) { - this.ui.labelInput[0].setAttribute('value', _.map(this.labelNames, (label) => { return label.name; })); - tagsInput(this.ui.labelInput[0]); + this.ui.labelInput[0].setAttribute( + 'value', + _.map(this.labelNames, label => { + return label.name; + }) + ); + tagsInput(this.ui.labelInput[0]); } } @@ -84,34 +88,31 @@ export default class ViewResourceMulti extends Marionette.CollectionView }; } -/////////////////////////////////////////////////////////////////////////////////////// -// PRIVATE METHODS -/////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////// + // PRIVATE METHODS + /////////////////////////////////////////////////////////////////////////////////////// /** * Handle button delete. */ - _handleClickButtonDelete() - { - for (var model of this._models) { - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__RESOURCE_DELETE, {resource: model}); - } + _handleClickButtonDelete() { + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__RESOURCE_DELETE_CONFIRM, { resource: this._models }); } - _handleClickButtonDownload() - { + _handleClickButtonDownload() { let modelArray = [...this._models.values()]; - let uuids = _.map(modelArray, val => { return val.id; }); + let uuids = _.map(modelArray, val => { + return val.id; + }); let baseUrl = Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__SERVER_GET_ROUTE, 'resource-archive'); let a = document.createElement('a'); a.download = 'Archive.zip'; - a.href = baseUrl + '?' + $.param({resource_uuid: uuids}, true); + a.href = baseUrl + '?' + $.param({ resource_uuid: uuids }, true); document.body.append(a); a.click(); a.remove(); } - _handleClickButtonSave() - { + _handleClickButtonSave() { let fields = { resource_type: this.ui.selectResourceType.find(':selected').val() }; @@ -119,12 +120,11 @@ export default class ViewResourceMulti extends Marionette.CollectionView fields['label_names'] = this.ui.labelInput.val(); } for (var model of this._models) { - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__RESOURCE_SAVE, {resource: model, fields: fields}); + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__RESOURCE_SAVE, { resource: model, fields: fields }); } } } -ViewResourceMulti.prototype.modelEvents = { -}; +ViewResourceMulti.prototype.modelEvents = {}; ViewResourceMulti.prototype.ui = { buttonSave: '#button-main_resource_individual_save', buttonDelete: '#button-main_resource_individual_delete', diff --git a/rodan-client/code/src/js/Views/Master/Main/Shared/ViewDeleteConfirm.js b/rodan-client/code/src/js/Views/Master/Main/Shared/ViewDeleteConfirm.js new file mode 100644 index 000000000..6e28082c6 --- /dev/null +++ b/rodan-client/code/src/js/Views/Master/Main/Shared/ViewDeleteConfirm.js @@ -0,0 +1,78 @@ +import $ from 'jquery'; +import _ from 'underscore'; +import Marionette from 'backbone.marionette'; +import Radio from 'backbone.radio'; +import RODAN_EVENTS from 'js/Shared/RODAN_EVENTS'; + +/** + * Project admin view. + */ +export default class ViewDeleteConfirm extends Marionette.View { + /////////////////////////////////////////////////////////////////////////////////////// + // PUBLIC METHODS + /////////////////////////////////////////////////////////////////////////////////////// + /** + * Initializes the instance. + * + * @param {object} options Marionette.View options object; + */ + initialize(options) { + this._type = options.type; + this._names = options.names; + this._toDelete = options.toDelete; + this.setElement('
'); + } + + /** + * Serialize data to be passed to the template. + * + * @returns {object} Data to be used in the template. + */ + serializeData() { + return { + type: this._type, + names: this._names + }; + } + + /////////////////////////////////////////////////////////////////////////////////////// + // PRIVATE METHODS + /////////////////////////////////////////////////////////////////////////////////////// + /** + * Handle button cancel delete. + */ + _handleCancelDelete() { + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_HIDE); + } + + /** + * Handle button confirm delete. + */ + _handleConfirmDelete() { + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_HIDE); + + switch (this._type) { + case 'project': + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__PROJECT_DELETE, { project: this._toDelete }); + break; + case 'resource': + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__RESOURCE_DELETE, { resource: this._toDelete }); + break; + case 'workflow': + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__WORKFLOW_DELETE, { workflow: this._toDelete }); + break; + default: + throw new Error('Invalid type: ' + this._type); + } + } +} + +ViewDeleteConfirm.prototype.template = _.template($('#template-main_shared_delete_confirm').text()); +ViewDeleteConfirm.prototype.ui = { + buttonCancelDelete: '#button-cancel_delete', + buttonConfirmDelete: '#button-confirm_delete' +}; +ViewDeleteConfirm.prototype.events = { + 'click @ui.buttonCancelDelete': '_handleCancelDelete', + 'click @ui.buttonConfirmDelete': '_handleConfirmDelete' +}; diff --git a/rodan-client/code/src/js/Views/Master/Main/Workflow/Individual/ViewWorkflow.js b/rodan-client/code/src/js/Views/Master/Main/Workflow/Individual/ViewWorkflow.js index 29ead9ef1..399904866 100644 --- a/rodan-client/code/src/js/Views/Master/Main/Workflow/Individual/ViewWorkflow.js +++ b/rodan-client/code/src/js/Views/Master/Main/Workflow/Individual/ViewWorkflow.js @@ -7,66 +7,60 @@ import Radio from 'backbone.radio'; /** * Workflow view. */ -export default class ViewWorkflow extends Marionette.View -{ - +export default class ViewWorkflow extends Marionette.View { initialize() { this.setElement('
'); } -/////////////////////////////////////////////////////////////////////////////////////// -// PRIVATE METHODS -/////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////// + // PRIVATE METHODS + /////////////////////////////////////////////////////////////////////////////////////// /** * Handle button run workflow. */ - _handleButtonRunWorkflow() - { - Radio.channel('rodan').trigger(RODAN_EVENTS.REQUEST__WORKFLOWBUILDER_CREATE_WORKFLOWRUN, {workflow: this.model}); + _handleButtonRunWorkflow() { + Radio.channel('rodan').trigger(RODAN_EVENTS.REQUEST__WORKFLOWBUILDER_CREATE_WORKFLOWRUN, { workflow: this.model }); } /** * Handle button delete workflow. */ - _handleButtonDeleteWorkflow() - { - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__WORKFLOW_DELETE, {workflow: this.model}); + _handleButtonDeleteWorkflow() { + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__WORKFLOW_DELETE_CONFIRM, { workflow: this.model }); } /** * Handle button edit workflow. */ - _handleButtonEditWorkflow() - { - Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__WORKFLOWBUILDER_SELECTED, {workflow: this.model}); + _handleButtonEditWorkflow() { + Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__WORKFLOWBUILDER_SELECTED, { workflow: this.model }); } /** * Handle button copy workflow. */ - _handleButtonCopyWorkflow() - { - } + _handleButtonCopyWorkflow() {} /** * Handle button export workflow. */ - _handleButtonExport() - { - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__WORKFLOW_EXPORT, {workflow: this.model}); + _handleButtonExport() { + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__WORKFLOW_EXPORT, { workflow: this.model }); } /** * Handle save button. */ - _handleButtonSave() - { + _handleButtonSave() { Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_HIDE); - Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__WORKFLOW_SAVE, {workflow: this.model, fields: {name: _.escape(this.ui.textName.val()), description: _.escape(this.ui.textDescription.val())}}); + Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__WORKFLOW_SAVE, { + workflow: this.model, + fields: { name: _.escape(this.ui.textName.val()), description: _.escape(this.ui.textDescription.val()) } + }); } } ViewWorkflow.prototype.modelEvents = { - 'all': 'render' - }; + all: 'render' +}; ViewWorkflow.prototype.ui = { runWorkflowButton: '#button-run_workflow', deleteWorkflowButton: '#button-delete_workflow', @@ -77,7 +71,7 @@ ViewWorkflow.prototype.ui = { buttonSave: '#button-save_workflow', textName: '#text-workflow_name', textDescription: '#text-workflow_description' - }; +}; ViewWorkflow.prototype.events = { 'click @ui.runWorkflowButton': '_handleButtonRunWorkflow', 'click @ui.deleteWorkflowButton': '_handleButtonDeleteWorkflow', @@ -86,5 +80,5 @@ ViewWorkflow.prototype.events = { 'click @ui.buttonSaveData': '_handleButtonSave', 'click @ui.buttonSave': '_handleButtonSave', 'click @ui.exportWorkflowButton': '_handleButtonExport' - }; +}; ViewWorkflow.prototype.template = _.template($('#template-main_workflow_individual').text()); diff --git a/rodan-client/code/styles/default.css b/rodan-client/code/styles/default.css index cf307085a..b852bf772 100644 --- a/rodan-client/code/styles/default.css +++ b/rodan-client/code/styles/default.css @@ -26,7 +26,7 @@ * General parameters *************************************************************************************/ - /* Set values for variables used throughout the stylesheet */ +/* Set values for variables used throughout the stylesheet */ :root { /* Navigation tree area */ --navigation-node-color: #939393; @@ -41,8 +41,8 @@ --secondary-app-color: #589ed5; --text-color-navigation: white; --app-font: Montserrat, sans-serif; - --btn-remove-color: #E76B6B; - --btn-save-color: #EB9E3E; + --btn-remove-color: #e76b6b; + --btn-save-color: #eb9e3e; /* Navigation area */ --background-color-navigation: #000000; @@ -75,7 +75,6 @@ --font-weight: 100; } - /* ------------------------------------ */ /* General styles /* ------------------------------------ */ @@ -86,12 +85,10 @@ * PLEASE MAKE SURE YOU KNOW WHAT YOU'RE DOING! */ - * { box-sizing: border-box; } -.text-small -{ +.text-small { font-size: x-small; } body { @@ -103,7 +100,8 @@ body { margin: 0px; overflow: hidden; } -input, select { +input, +select { border: 1px solid #cecece; border-radius: 3px; font-family: Montserrat, sans-serif; @@ -121,7 +119,7 @@ textarea { border-radius: 3px; outline: none; } - + /* Table styles */ table { user-select: none; @@ -137,7 +135,8 @@ thead { position: sticky; top: 0px; } -th, td { +th, +td { cursor: pointer; padding: 10px; } @@ -283,12 +282,16 @@ tbody > tr:hover { background-color: white; color: var(--secondary-app-color); } -.btn.btn-delete, .btn.btn-remove, .btn.btn-logout { +.btn.btn-delete, +.btn.btn-remove, +.btn.btn-logout { color: white; background-color: var(--btn-remove-color); border: 1px solid var(--btn-remove-color); } -.btn.btn-delete:hover, .btn.btn-remove:hover, .btn.btn-logout:hover { +.btn.btn-delete:hover, +.btn.btn-remove:hover, +.btn.btn-logout:hover { color: var(--btn-remove-color); background-color: white; } @@ -301,15 +304,13 @@ tbody > tr:hover { background-color: white; color: var(--btn-save-color); } -:disabled -{ +:disabled { color: white !important; background: #dddddd !important; border: 1px solid #dddddd !important; cursor: not-allowed !important; } -:disabled:hover -{ +:disabled:hover { background: #dddddd !important; border: 1px solid #dddddd !important; color: white !important; @@ -363,14 +364,10 @@ tbody > tr:hover { background-color: var(--primary-app-color); } - /* ------------------------------------ */ /* General styles end /* ------------------------------------ */ - - - #app { width: 100%; height: 100%; @@ -498,22 +495,20 @@ tbody > tr:hover { color: white; } .btn.main-navbar-btn#button-navigation_logout { - color: #E76B6B; + color: #e76b6b; } .btn.main-navbar-btn#button-navigation_logout:hover { - background-color: #E76B6B; + background-color: #e76b6b; color: white; } - /* /////////////////////////////////////////////////////////////////////////////////////// // Main region /////////////////////////////////////////////////////////////////////////////////////////*/ /* Main region general styles */ -#region-main -{ - Background-repeat: no-repeat; +#region-main { + background-repeat: no-repeat; background-position: center center; width: 83.33333333%; overflow-y: auto; @@ -531,7 +526,7 @@ tbody > tr:hover { .region-main-section.region-main-header { width: 100%; height: 40px; - justify-content: space-between; + justify-content: space-between; border-bottom: 2px solid #e5e5e5; } #region-main-header { @@ -734,7 +729,6 @@ tbody > tr:hover { width: 98%; } - /* Project and collection details panels */ .detail-panels-container { width: 25%; @@ -748,7 +742,8 @@ tbody > tr:hover { justify-content: space-between; /* height: 50%; */ } -#region-project-details-panel, #region-collection-item-details-panel { +#region-project-details-panel, +#region-collection-item-details-panel { height: 50%; } .details-panel-title-section { @@ -808,12 +803,12 @@ tbody > tr:hover { #region-main_workflowrun_individual_resources { padding-top: 20px; } -#region-main_workflowrun_individual_resources, #region-main_workflowrun_individual_runjobs { +#region-main_workflowrun_individual_resources, +#region-main_workflowrun_individual_runjobs { height: fit-content; max-height: 82%; } - /*////////////////////////////////////////////////////////////////////////////////////// // Canvas /////////////////////////////////////////////////////////////////////////////////////// */ @@ -847,16 +842,14 @@ tbody > tr:hover { gap: 3vw; font-size: 13px; } -#canvas-wrap -{ +#canvas-wrap { /* flex-grow: 0.9; */ overflow: hidden; padding: 0px; height: 100%; /* height: -webkit-fill-available; */ } -#canvas-tooltip -{ +#canvas-tooltip { position: fixed; background-color: #ffff88; margin-left: 3px; @@ -868,8 +861,7 @@ tbody > tr:hover { visibility: hidden; display: inline-block; } -canvas#canvas-workspace -{ +canvas#canvas-workspace { background-color: var(--background-color-workflowbuilder-workspace); padding: 0px; } @@ -884,8 +876,7 @@ canvas[resize] { display: flex; align-items: center; } -.horizontal-center -{ +.horizontal-center { width: 100%; text-align: center; } @@ -899,14 +890,12 @@ div.gui { display: flex; flex-direction: column; } -div#main_workflowbuilder -{ +div#main_workflowbuilder { background-color: white; height: 100%; overflow: auto; } -.instruction -{ +.instruction { font-style: italic; } @@ -915,8 +904,12 @@ div#main_workflowbuilder /* ------------------------------------ */ @keyframes fade-in { - from {opacity: 0;} - to {opacity: 1;} + from { + opacity: 0; + } + to { + opacity: 1; + } } /* general modal styles */ @@ -959,8 +952,7 @@ div#main_workflowbuilder .modal-close { cursor: pointer; } -.modal-body -{ +.modal-body { max-height: 600px; padding: 20px; box-sizing: border-box; @@ -976,14 +968,13 @@ div#main_workflowbuilder font-size: 13px; font-weight: bold; } -.modal-footer -{ +.modal-footer { padding: 10px; max-height: 40px; height: fit-content; } -.modal-footer-error, .modal-input-error -{ +.modal-footer-error, +.modal-input-error { color: #aa0000; } .modal-body-section-left { @@ -1032,24 +1023,24 @@ div#main_workflowbuilder .table-modal { overflow-y: scroll; } -.table-modal>tbody { +.table-modal > tbody { overflow: scroll; } -.table-modal>thead>tr>th, -.table-modal>tbody>tr>th { +.table-modal > thead > tr > th, +.table-modal > tbody > tr > th { max-width: 70px; word-wrap: break-word; } -.table-modal>thead>tr>td, -.table-modal>tbody>tr>td { +.table-modal > thead > tr > td, +.table-modal > tbody > tr > td { max-width: 70px; word-wrap: break-word; border-bottom: 1px solid rgb(215 215 215); } -.table-modal>thead>tr>th:first-child, -.table-modal>tbody>tr>th:first-child, -.table-modal>thead>tr>td:first-child, -.table-modal>tbody>tr>td:first-child { +.table-modal > thead > tr > th:first-child, +.table-modal > tbody > tr > th:first-child, +.table-modal > thead > tr > td:first-child, +.table-modal > tbody > tr > td:first-child { max-width: 180px; } .tbody-scroll { @@ -1064,14 +1055,17 @@ div#main_workflowbuilder } #workflowjob-settings { - } .this-is-my-class { background-color: green; } - +/* Delete confirm modal */ +#delete-confirm-modal-body-wrapper { + align-items: flex-start; + padding: 0 2em; +} /* ------------ */ /* Table styles */ @@ -1080,8 +1074,7 @@ div#main_workflowbuilder /** * Make text in table unselectable. This helps make multiple selection look better. */ -table -{ +table { -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; @@ -1091,47 +1084,41 @@ table } /* Table controls */ -.table-hover tbody tr:hover td, .table-hover tbody tr:hover th, .table tr.clickable-row.active:hover td -{ +.table-hover tbody tr:hover td, +.table-hover tbody tr:hover th, +.table tr.clickable-row.active:hover td { background-color: #7eb0dd; } -.table tr.clickable-row.active td -{ +.table tr.clickable-row.active td { background-color: var(--secondary-app-color); } -.table-control span -{ - display:inline-block; +.table-control span { + display: inline-block; font-size: var(--size-font); font-weight: bold; } -div.table-control span -{ - display:inline-block; +div.table-control span { + display: inline-block; width: 50%; } -div.table-control span.align-right -{ +div.table-control span.align-right { text-align: right; } -div.table-control p -{ - font-size:0; /* Fixes inline block spacing */ +div.table-control p { + font-size: 0; /* Fixes inline block spacing */ } /* Sortable tables */ -th.sort-ascending .glyphicon .glyphicon-arrow-up -{ +th.sort-ascending .glyphicon .glyphicon-arrow-up { font-family: Arial, Helvetica, sans-serif; } /* Scrollable tables */ -table.scroll -{ +table.scroll { display: block; height: 400px; width: 100%; @@ -1170,4 +1157,56 @@ table.scroll } */ } -.tags-input{display:inline-block;padding:0 2px;background:#FFF;border:1px solid #CCC;width:16em;border-radius:2px;box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.tags-input .tag{display:inline-block;background:#EEE;color:#444;padding:0 4px;margin:2px;border:1px solid #CCC;border-radius:2px;font:inherit;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:pointer;transition:all .1s ease}.tags-input .tag.selected{background-color:#777;border-color:#777;color:#EEE}.tags-input .tag.dupe{-webkit-transform:scale3d(1.2,1.2,1.2);transform:scale3d(1.2,1.2,1.2);background-color:#FCC;border-color:#700}.tags-input input{-webkit-appearance:none!important;-moz-appearance:none!important;appearance:none!important;display:inline-block!important;padding:3px;margin:0!important;background:0 0!important;border:none!important;box-shadow:none!important;font:inherit!important;font-size:100%!important;outline:0!important}.tags-input .selected~input{opacity:.3} \ No newline at end of file +.tags-input { + display: inline-block; + padding: 0 2px; + background: #fff; + border: 1px solid #ccc; + width: 16em; + border-radius: 2px; + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); +} +.tags-input .tag { + display: inline-block; + background: #eee; + color: #444; + padding: 0 4px; + margin: 2px; + border: 1px solid #ccc; + border-radius: 2px; + font: inherit; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + cursor: pointer; + transition: all 0.1s ease; +} +.tags-input .tag.selected { + background-color: #777; + border-color: #777; + color: #eee; +} +.tags-input .tag.dupe { + -webkit-transform: scale3d(1.2, 1.2, 1.2); + transform: scale3d(1.2, 1.2, 1.2); + background-color: #fcc; + border-color: #700; +} +.tags-input input { + -webkit-appearance: none !important; + -moz-appearance: none !important; + appearance: none !important; + display: inline-block !important; + padding: 3px; + margin: 0 !important; + background: 0 0 !important; + border: none !important; + box-shadow: none !important; + font: inherit !important; + font-size: 100% !important; + outline: 0 !important; +} +.tags-input .selected ~ input { + opacity: 0.3; +} diff --git a/rodan-client/code/templates/Views/Master/Main/Project/Individual/template-main_project_individual.html b/rodan-client/code/templates/Views/Master/Main/Project/Individual/template-main_project_individual.html index cc6ef5e97..c86f1021d 100644 --- a/rodan-client/code/templates/Views/Master/Main/Project/Individual/template-main_project_individual.html +++ b/rodan-client/code/templates/Views/Master/Main/Project/Individual/template-main_project_individual.html @@ -9,10 +9,10 @@
- - + - + +
@@ -36,7 +36,7 @@
- +
diff --git a/rodan-client/code/templates/Views/Master/Main/shared/template-main_shared_delete_confirm.html b/rodan-client/code/templates/Views/Master/Main/shared/template-main_shared_delete_confirm.html new file mode 100644 index 000000000..5777faeda --- /dev/null +++ b/rodan-client/code/templates/Views/Master/Main/shared/template-main_shared_delete_confirm.html @@ -0,0 +1,22 @@ + +
+ <% if (Array.isArray(names)) { %> +

Are you sure you want to delete these <%= type %>s?

+
    + <% names.forEach(function(item) { %> +
  • <%= item %>
  • + <% }); %> +
+ <% } else { %> +

Are you sure you want to delete this <%= type %>?

+
    +
  • <%= names %>
  • +
+ <% } %> +

This item will be deleted immediately. You can't undo this action.

+
+ + +
+
+ diff --git a/rodan-main/code/rodan/__init__.py b/rodan-main/code/rodan/__init__.py index f0f12b45a..32e7e1b2f 100644 --- a/rodan-main/code/rodan/__init__.py +++ b/rodan-main/code/rodan/__init__.py @@ -17,7 +17,7 @@ # Get version: import rodan; rodan.__version__ # Version numbers also appear in the API. __title__ = "Rodan" -__version__ = "v3.2.0" +__version__ = "v3.3.0" __copyright__ = "Copyright 2011-2022 Distributed Digital Music Archives & Libraries Lab" # If changing this line, also change rodan-main/Dockerfile __build_hash__ = "local" diff --git a/rodan-main/code/rodan/jobs/MEI_encoding/build_mei_file.py b/rodan-main/code/rodan/jobs/MEI_encoding/build_mei_file.py index 5095e920f..ff74d2330 100644 --- a/rodan-main/code/rodan/jobs/MEI_encoding/build_mei_file.py +++ b/rodan-main/code/rodan/jobs/MEI_encoding/build_mei_file.py @@ -20,6 +20,8 @@ # from state_machine import SylMachine #---> for testing locally +staffDef_lines = 4 + try: from rodan.jobs.MEI_encoding import __version__ except ImportError: @@ -98,7 +100,6 @@ def neume_to_lyric_alignment( are, strictly speaking, not part of the MEI for the syllable; that is handled in the method that actually encodes the MEI. """ - dummy_syl = {"syl": "", "ul": [0, 0], "lr": [0, 0]} # if there's no syl information then make fake syllables for testing. this method makes one # large syllable covering an entire staff line. @@ -108,13 +109,11 @@ def neume_to_lyric_alignment( grouped_glyphs = [ list(g) for k, g in groupby(glyphs, key=lambda x: int(x["staff"])) ] + dummy_syl = {"syl": "", "ul": [0, 0], "lr": [0, 0]} pairs = [(g, dummy_syl) for g in grouped_glyphs] return pairs - glyphs_pos = 0 - num_glyphs = len(glyphs) - pairs = [] starts = [] last_used = 0 @@ -149,10 +148,9 @@ def neume_to_lyric_alignment( starts.append(glyphs.index(nearest_glyph)) last_used = max(starts) - # if there are unassigned "orphan" glyphs at the beginning of the page, assign them all to a - # dummy syl_box so they can be detected later - if not starts[0] == 0: - pairs.append((glyphs[: starts[0]], dummy_syl)) + # if there are unassigned "orphan" glyphs at the beginning of the page, + # force them to be assigned to the first syllable + starts[0] = 0 starts.append(len(glyphs)) for i in range(len(starts) - 1): @@ -179,7 +177,7 @@ def generate_base_document(column_split_info: Optional[dict]): mei = new_el("mei") mei.set("xmlns", "http://www.music-encoding.org/ns/mei") - mei.set("meiversion", "5.0.0-dev") + mei.set("meiversion", "5.1") meiHead = new_el("meiHead", mei) @@ -210,7 +208,7 @@ def generate_base_document(column_split_info: Optional[dict]): staffDef = new_el("staffDef", staffGrp) staffDef.set("n", "1") - staffDef.set("lines", "4") + staffDef.set("lines", str(staffDef_lines)) staffDef.set("notationtype", "neume") staffDef.set("clef.line", "4") staffDef.set("clef.shape", "C") @@ -250,7 +248,8 @@ def create_primitive_element(xml: Element, glyph: dict, idx: int, surface: Eleme # ncs, custos do not have a @line attribute. this is a bit of a hack... if xml.tag == "clef": - attribs["line"] = str(int(float(glyph["strt_pos"]))) + #To resolve Rodan issue #1276 (https://github.com/DDMAL/Rodan/issues/1276), moves clef position to fix wrong note offsets due to different numbers of lines than 4 + attribs["line"] = str(int(float(glyph["strt_pos"])) + (staffDef_lines - 4)) attribs["oct"] = str(glyph["octave"]) attribs["pname"] = str(glyph["note"]) @@ -275,6 +274,9 @@ def glyph_to_element( Currently the assumption is that no MEI information in the given classifier is more than one level deep - that is, everything is either a single element (clef, custos) or the child of a single element (neumes). THIS IS NOT TRUE FOR ALL NEUMATIC NOTATION TYPES! + + UPDATE 2025.02: the given classifier can have 2 levels of depth, to handle cases like liquescent. + TODO: consider to convert to recursive function to handle arbitrary depth. """ name = str(glyph["name"]) try: @@ -320,6 +322,10 @@ def glyph_to_element( for i in range(len(ncs)): try: el = create_primitive_element(ncs[i], glyph, i, surface) + if list(ncs[i]): + for child in ncs[i]: + child_el = new_el(child.tag) + el.append(child_el) except IndexError: print( "Width column indicates {} neume components but gets {} neume components from input for classifier {}".format( @@ -656,9 +662,8 @@ def build_mei( col = bbox_to_col_num(bb, column_split_info["split_ranges"], height) bb = translate_bbox(bb, column_split_info["split_ranges"], height, col) - zoneId = generate_zone(surface, bb) - - machine = SylMachine(syl_box["syl"], zoneId) + # Pass both surface and layer + machine = SylMachine(syl_box.get("syl", ""), bb, surface, layer) # find the last neume index last_neume_index = 0 @@ -710,9 +715,6 @@ def build_mei( sb.set("n", str(next_staff + 1)) machine.read(sb.tag, sb) - # add the mei from the state machine to the layer - layer.extend(machine.layer) - return meiDoc @@ -797,26 +799,6 @@ def compare_neumes(nl: Element, nr: Element): return meiDoc -def removeEmptySyl(meiDoc: ET.ElementTree): - """ - Removes all empty syllables from the layer - """ - - layers = list((meiDoc.getroot()).iter("layer")) - layer = layers[0] # only one layer so this gets the corresponding element - - # this could be cleaner - for i in list(layer): - if i.tag == "syllable": - if len(list(i)) == 1: - if (list(i)[0].tag == "syl") & ( - (i.get("xml:precedes") is None) & (i.get("xml:follows") is None) - ): - layer.remove(i) - - return meiDoc - - def reformat_staves(staves: List[dict]): """ Reformats the bounding box information from the pitch finding JSON. @@ -840,6 +822,9 @@ def process( width_multiplier parameter for merging neume components. """ staves = reformat_staves(jsomr["staves"]) + if staves is not None: + global staffDef_lines + staffDef_lines = staves[0]["num_lines"] glyphs = jsomr["glyphs"] syl_boxes = syls["syl_boxes"] if syls is not None else None median_line_spacing = syls["median_line_spacing"] if syls is not None else None @@ -859,8 +844,6 @@ def process( if width_mult > 0: meiDoc = merge_nearby_neume_components(meiDoc, width_mult=width_mult) - meiDoc = removeEmptySyl(meiDoc) - tree = ET.ElementTree(meiDoc.getroot()) return ET.tostring(tree.getroot(), encoding="utf8").decode("utf8") diff --git a/rodan-main/code/rodan/jobs/MEI_encoding/state_machine.py b/rodan-main/code/rodan/jobs/MEI_encoding/state_machine.py index 5071b97c8..9f00aea89 100644 --- a/rodan-main/code/rodan/jobs/MEI_encoding/state_machine.py +++ b/rodan-main/code/rodan/jobs/MEI_encoding/state_machine.py @@ -1,12 +1,15 @@ from enum import Enum import xml.etree.ElementTree as ET from uuid import uuid4 +from rodan.jobs.MEI_encoding import build_mei_file as bm + class Inputs(Enum): NEUME = 0 IN_SYL = 1 OUT_SYL = 2 + class States(Enum): NO_NEUME = 0 FIRST_NEUME = 1 @@ -14,22 +17,23 @@ class States(Enum): BREAK_OUT = 3 PROCEEDS_FOLLOWS = 4 + class SylMachine: - - def __init__(self,text,zoneId): - # a list of elements that represent this syllable, and all elements outside of it generated - # from glyphs that were associated with this syllable. THIS IS NOT AN ELEMENT IT IS A LIST - self.layer = [] - # The initial element for this syllable. It will contain a , and can contain , - # , , and . Only added to the layer once a neume has been seen + + def __init__(self, text, bb, surface, layer): + self.surface = surface + self.bb = bb + self.layer = layer + + # The initial element for this syllable self.curr_syl = ET.Element("syllable") - self.curr_syl.set("xml:id", 'm-'+str(uuid4())) + self.curr_syl.set("xml:id", "m-" + str(uuid4())) - # Create the syl tag, has the text - syl = ET.SubElement(self.curr_syl,"syl") - syl.set("xml:id", 'm-'+str(uuid4())) - syl.text = text - syl.set('facs', '#' + zoneId) + # Create the syl tag, has the text but don't set facs yet + self.syl = ET.SubElement(self.curr_syl, "syl") + self.syl.set("xml:id", "m-" + str(uuid4())) + self.syl.text = text + # Only set facs when/if the syllable gets added to layer # initial state is NO_NEUME self.prev_state = States.NO_NEUME @@ -44,80 +48,90 @@ def __init__(self,text,zoneId): # contains which state should be moved to from some state given some input. # # Ex: When you are at state NEUME_ADDED and see OUT_SYL, the transition should - # be to BREAK_OUT. As such, + # be to BREAK_OUT. As such, # self.adjacency_matrix[States.NEUME_ADDED.value][Inputs.OUT_SYL.value] = States.BREAK_OUT # The first dimension here grabs the current state, returning a list of transitions. Indexing it by # the input provides the next state. self.adjacency_matrix = [] # transitions for the NO_NEUME state - no_neume_transitions = [None,None,None] + no_neume_transitions = [None, None, None] no_neume_transitions[Inputs.NEUME.value] = States.FIRST_NEUME no_neume_transitions[Inputs.IN_SYL.value] = States.NO_NEUME no_neume_transitions[Inputs.OUT_SYL.value] = States.NO_NEUME self.adjacency_matrix.append(no_neume_transitions) # transitions for the FIRST_NEUME state - first_neume_transitions = [None,None,None] + first_neume_transitions = [None, None, None] first_neume_transitions[Inputs.NEUME.value] = States.ADD_INSIDE first_neume_transitions[Inputs.IN_SYL.value] = States.ADD_INSIDE first_neume_transitions[Inputs.OUT_SYL.value] = States.BREAK_OUT self.adjacency_matrix.append(first_neume_transitions) # transitions for the ADD_INISDE state - add_inside_transitions = [None,None,None] + add_inside_transitions = [None, None, None] add_inside_transitions[Inputs.NEUME.value] = States.ADD_INSIDE add_inside_transitions[Inputs.IN_SYL.value] = States.ADD_INSIDE add_inside_transitions[Inputs.OUT_SYL.value] = States.BREAK_OUT self.adjacency_matrix.append(add_inside_transitions) # transitions for the BREAK_OUT state - break_out_transitions = [None,None,None] + break_out_transitions = [None, None, None] break_out_transitions[Inputs.NEUME.value] = States.PROCEEDS_FOLLOWS break_out_transitions[Inputs.IN_SYL.value] = States.BREAK_OUT break_out_transitions[Inputs.OUT_SYL.value] = States.BREAK_OUT self.adjacency_matrix.append(break_out_transitions) # transitions for the PROCEEDS_FOLLOWS state - proceeds_follows_transitions = [None,None,None] + proceeds_follows_transitions = [None, None, None] proceeds_follows_transitions[Inputs.NEUME.value] = States.ADD_INSIDE proceeds_follows_transitions[Inputs.IN_SYL.value] = States.ADD_INSIDE proceeds_follows_transitions[Inputs.OUT_SYL.value] = States.BREAK_OUT self.adjacency_matrix.append(proceeds_follows_transitions) - self.state_mapper = [self.no_neume,self.first_neume,self.add_inside,self.break_out,self.proceeds_follows] + self.state_mapper = [ + self.no_neume, + self.first_neume, + self.add_inside, + self.break_out, + self.proceeds_follows, + ] # function for NO_NEUME state - def no_neume(self,element): + def no_neume(self, element): self.layer.append(element) - + # function for FIRST_NEUME state - def first_neume(self,element): + def first_neume(self, element): + # Generate zone only when we know syllable will be used + zoneId = bm.generate_zone(self.surface, self.bb) + self.syl.set("facs", "#" + zoneId) + self.curr_syl.append(element) self.layer.append(self.curr_syl) - + # function for ADD_INSIDE state - def add_inside(self,element): + def add_inside(self, element): self.curr_syl.append(element) # function for BREAK_OUT state - def break_out(self,element): + def break_out(self, element): self.layer.append(element) # function for PROCEEDS_FOLLOWS state - def proceeds_follows(self,element): + def proceeds_follows(self, element): new_syllable = ET.Element("syllable") - new_syllable.set("xml:id", 'm-'+str(uuid4())) + new_syllable.set("xml:id", "m-" + str(uuid4())) - self.curr_syl.set("precedes", '#' + new_syllable.get('xml:id')) - new_syllable.set("follows", "#" + self.curr_syl.get('xml:id')) + self.curr_syl.set("precedes", "#" + new_syllable.get("xml:id")) + new_syllable.set("follows", "#" + self.curr_syl.get("xml:id")) self.curr_syl = new_syllable self.curr_syl.append(element) self.layer.append(self.curr_syl) # maps an element to an input enum type - def elm_to_input(self,tag): + def elm_to_input(self, tag): if tag == "neume": return Inputs.NEUME elif tag in self.can_be_in_syllable: @@ -131,7 +145,7 @@ def read(self, tag, element): next_state = self.adjacency_matrix[self.prev_state.value][input.value] self.state_mapper[next_state.value](element) self.prev_state = next_state - + # reads an element, and adds it outside the syllable - def read_outside_syllable(self,element): + def read_outside_syllable(self, element): self.layer.append(element) diff --git a/rodan-main/code/rodan/jobs/aquitanian_ref_line_finding/__init__.py b/rodan-main/code/rodan/jobs/aquitanian_ref_line_finding/__init__.py new file mode 100644 index 000000000..ae9dfbfb8 --- /dev/null +++ b/rodan-main/code/rodan/jobs/aquitanian_ref_line_finding/__init__.py @@ -0,0 +1,8 @@ +import logging + +import rodan +from rodan.jobs import module_loader + +__version__ = "0.0.2" +logger = logging.getLogger("rodan") +module_loader("rodan.jobs.aquitanian_ref_line_finding.aquitanian_ref_line_finding") diff --git a/rodan-main/code/rodan/jobs/aquitanian_ref_line_finding/aquitanian_ref_line_finding.py b/rodan-main/code/rodan/jobs/aquitanian_ref_line_finding/aquitanian_ref_line_finding.py new file mode 100644 index 000000000..e996cac25 --- /dev/null +++ b/rodan-main/code/rodan/jobs/aquitanian_ref_line_finding/aquitanian_ref_line_finding.py @@ -0,0 +1,231 @@ +from rodan.jobs.base import RodanTask +import cv2 +import numpy as np +import math +from PIL import Image +import json +import json.encoder + +#Helper function for distance formula +def dist(pt1, pt2): + return math.sqrt(((pt1[0] - pt2[0])**2) + ((pt1[1] - pt2[1])**2)) + +#Helper function to find the matching y value given an x value and 2 points of a line +def coords(x1, y1, x2, y2, new_x): + slope = (y2 - y1) / (x2 - x1) + b = (-1 * (slope * x1)) + y1 + return (slope * new_x) + b + +#Handles each line segment by adding extra space, drawing a bounding box, and drawing a line through the center +#Returns the 2 coords for the line +def process_section(sect): + #add extra space around the line segments + old_h, old_w, c = sect.shape + new_h = old_h + 100 + new_w = old_w + 100 + result = np.full((new_h, new_w, c), (255, 255, 255), dtype=np.uint8) + x_center = (new_w - old_w) // 2 + y_center = (new_h - old_h) // 2 + result[y_center:y_center+old_h, x_center:x_center+old_w] = sect + + #preprocess image, draw bounding box around + gray = cv2.cvtColor(result, cv2.COLOR_BGR2GRAY) + blur = cv2.GaussianBlur(gray, (1, 1), 0) + thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1] + kernal = cv2.getStructuringElement(cv2.MORPH_RECT, (8, 1)) + dilate = cv2.dilate(thresh, kernal, iterations=7) + conts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + + conts = conts[0] if len(conts) == 2 else conts[1] + conts = sorted(conts, key=lambda x: cv2.boundingRect(x)[0]) + + ret = [] + + #draw line through the bounding box + for c in conts: + rect = cv2.minAreaRect(c) + box = cv2.boxPoints(rect) + box = np.int0(box) + result = cv2.drawContours(result,[box],0,(0,255,0),2) + origin = box[0] + new_pts = sorted(box, key=lambda x: dist(x, origin)) + pt0 = new_pts[0] + pt1 = new_pts[1] + pt2 = new_pts[2] + pt3 = new_pts[3] + + mid1 = [int((pt0[0] + pt1[0]) / 2) - 50, int((pt0[1] + pt1[1]) / 2) - 50] + mid2 = [int((pt2[0] + pt3[0]) / 2) - 50, int((pt2[1] + pt3[1]) / 2) - 50] + ret = [mid1, mid2] + + return ret + +#Convert results into a JSOMR format +#Each reference line has 5 lines: 2 ledger lines below, the original line, and 2 ledger lines above +def to_json(img, data, neume_size): + h, w, _ = img.shape + staves = [] + for i in range(0, len(data)): + cur = data[i] + box = cur[0] + x, y, width, height = box + lines = cur[1] + up_ledger_2 = [[x[0], x[1] - (2 * neume_size)] for x in lines] + up_ledger_1 = [[x[0], x[1] - neume_size] for x in lines] + down_ledger_1 = [[x[0], x[1] + neume_size] for x in lines] + down_ledger_2 = [[x[0], x[1] + (2 * neume_size)] for x in lines] + staves.append({ + "staff_no": i+1, + "bounding_box":{ + "ncols": width, + "nrows": height + (4 * neume_size), + "ulx": x, + "uly": y - (2 * neume_size) + }, + "num_lines": 1, + "line_positions": [up_ledger_2, up_ledger_1, lines, down_ledger_1, down_ledger_2] + }) + return { + "page":{ + "resolution": 0.0, + "bounding_box":{ + "ncols": w, + "nrows": h, + "ulx": 0, + "uly": 0 + } + }, + "staves": staves + } + +class AquitanianReferenceLineFinding(RodanTask): + name = "Aquitanian Reference Line Finding" + author = "Deanna Chun" + description = "Trace single Aquitanian reference lines" + settings = { + 'title': 'Settings', + 'type': 'object', + 'job_queue': 'Python3', + 'required': ['Slices', 'Neume Height'], + 'properties': { + 'Slices': { + 'type': 'integer', + 'default': 8, + 'minimum': 1, + 'maximum': 24, + 'description': 'Number of divisions per single reference line' + }, + 'Neume Height': { + 'type': 'integer', + 'default': 50, + 'minimum': 1, + 'maximum': 500, + 'description': "Neume Height multiplied by 3 (for generating ledger lines)" + } + } + } + + enabled = True + category = "Staff Detection" + interactive = False + input_port_types = [{ + 'name': 'Image containing staves (RGB, greyscale, or onebit)', + 'resource_types': ['image/rgb+png', 'image/onebit+png', 'image/greyscale+png'], + 'minimum': 1, + 'maximum': 1, + 'is_list': False + }] + + output_port_types = [{ + 'name': 'JSOMR', + 'resource_types': ['application/json'], + 'minimum': 1, + 'maximum': 1, + 'is_list': False + }, + { + 'name': 'Overlayed Lines', + 'resource_types': ['image/rgb+png'], + 'minimum': 0, + 'maximum': 1 + }] + + # Overall Workflow + # 1. Draw bounding boxes around each reference line + # 2. Split each bounding box into a number of sections given by slices + # 3. Add extra space around each line segment, then draws another bounding box (not necessarily rectangular) + # 4. Draw a line through the center of the line segment bounding box + # 5. Link each segment together + # 6. After all lines are found, convert into JSOMR format + def run_my_task(self, inputs, settings, outputs): + input_path = inputs["Image containing staves (RGB, greyscale, or onebit)"][0]["resource_path"] + overlay = "Overlayed Lines" in outputs + slices = settings['Slices'] + + img = cv2.imread(input_path) + + #Image preprocessing to set up bounding boxes + gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + blur = cv2.GaussianBlur(gray, (1, 1), 0) + thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1] + kernal = cv2.getStructuringElement(cv2.MORPH_RECT, (8, 1)) + dilate = cv2.dilate(thresh, kernal, iterations=7) + conts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + + conts = conts[0] if len(conts) == 2 else conts[1] + conts = sorted(conts, key=lambda x: cv2.boundingRect(x)[0]) + + ret = [] + + #Split each bounding box into sections, then connect the line segments + for c in conts: + x, y, w, h = cv2.boundingRect(c) + part = w // slices + last = [] + lines = [] + for i in range(0, slices): + img_sect = img[y:y+h, x+(part*i):x+(part*(i+1))] + line = process_section(img_sect) + if line != []: + line[0][0] += x+(part*i) + line[1][0] += x+(part*i) + line[0][1] += y + line[1][1] += y + + #normalize lines to bounds + new_y1 = int(coords(line[0][0], line[0][1], line[1][0], line[1][1], x+(part*i))) + new_y2 = int(coords(line[0][0], line[0][1], line[1][0], line[1][1], x+(part*(i+1)))) + line[0] = [x+(part*i), new_y1] + line[1] = [x+(part*(i+1)), new_y2] + + #make sure line segments connect together + if last != []: + line[0] = last + last = line[1] + lines.append(line[0]) + if i == (slices - 1): + lines.append(line[1]) + + #draw line + if overlay: + cv2.line(img, tuple(line[0]), tuple(line[1]), (255, 0, 0), 2) + #save bounding box and line points + ret.append(([x, y, w, h], lines)) + + #sort staff lines based on y height + ret = sorted(ret, key=lambda x: x[0][1]) + neume_size = settings['Neume Height'] + + #convert data into jsomr format + jsomr = to_json(img, ret, neume_size) + + outfile_path = outputs['JSOMR'][0]['resource_path'] + with open(outfile_path, "w") as outfile: + outfile.write(json.dumps(jsomr)) + + if overlay: + outfile_path2 = outputs["Overlayed Lines"][0]["resource_path"] + overlay_save = Image.fromarray(img) + overlay_save.save(outfile_path2, 'PNG') + + return True diff --git a/rodan-main/code/rodan/jobs/aquitanian_ref_line_finding/resource_types.yaml b/rodan-main/code/rodan/jobs/aquitanian_ref_line_finding/resource_types.yaml new file mode 100644 index 000000000..277698da9 --- /dev/null +++ b/rodan-main/code/rodan/jobs/aquitanian_ref_line_finding/resource_types.yaml @@ -0,0 +1,12 @@ +- mimetype: image/rgb+png + description: RGB PNG image + extension: png +- mimetype: image/onebit+png + description: One-bit (black and white) PNG image + extension: png +- mimetype: image/greyscale+png + description: Greyscale PNG image + extension: png +- mimetype: application/json + description: JSON + extension: json \ No newline at end of file diff --git a/rodan-main/code/rodan/jobs/column_split/base.py b/rodan-main/code/rodan/jobs/column_split/base.py index 9a900f865..5013e2959 100644 --- a/rodan-main/code/rodan/jobs/column_split/base.py +++ b/rodan-main/code/rodan/jobs/column_split/base.py @@ -35,6 +35,7 @@ class ColumnSplit(RodanTask): {'name': 'Background Layer', 'minimum': 0, 'maximum': 1, 'resource_types': ['image/rgba+png']}, {'name': 'Music Notes Layer', 'minimum': 1, 'maximum': 1, 'resource_types': ['image/rgba+png']}, {'name': 'Text Layer', 'minimum': 1, 'maximum': 1, 'resource_types': ['image/rgba+png']}, + {'name': 'RGB Image', 'minimum': 0, 'maximum': 1, 'resource_types': ['image/rgb+png']}, {'name': 'Layer 4', 'minimum': 0, 'maximum': 1, 'resource_types': ['image/rgba+png']}, {'name': 'Layer 5', 'minimum': 0, 'maximum': 1, 'resource_types': ['image/rgba+png']}, {'name': 'Layer 6', 'minimum': 0, 'maximum': 1, 'resource_types': ['image/rgba+png']}, @@ -49,6 +50,7 @@ class ColumnSplit(RodanTask): {'name': 'Background Layer', 'minimum': 0, 'maximum': 1, 'resource_types': ['image/rgba+png']}, {'name': 'Music Notes Layer', 'minimum': 1, 'maximum': 1, 'resource_types': ['image/rgba+png']}, {'name': 'Text Layer', 'minimum': 1, 'maximum': 1, 'resource_types': ['image/rgba+png']}, + {'name': 'RGB Image', 'minimum': 0, 'maximum': 1, 'resource_types': ['image/rgb+png']}, {'name': 'All Layers', 'minimum': 0, 'maximum': 1, 'resource_types': ['image/rgba+png']}, {'name': 'Layer 5', 'minimum': 0, 'maximum': 1, 'resource_types': ['image/rgba+png']}, {'name': 'Layer 6', 'minimum': 0, 'maximum': 1, 'resource_types': ['image/rgba+png']}, @@ -113,7 +115,8 @@ def run_my_task(self, inputs, settings, outputs): layer = inputs[key][0]['resource_path'] img = cv.imread(layer,cv.IMREAD_UNCHANGED) layer_stacked = get_stacked_image(img,ranges) - layers.append(img) + if key != 'RGB Image': + layers.append(img) if key in outputs: outfile = outputs[key][0]['resource_path'] cv.imwrite(outfile+".png",layer_stacked) diff --git a/rodan-main/code/rodan/jobs/column_split/column_split.py b/rodan-main/code/rodan/jobs/column_split/column_split.py index f5fc4cd4b..1f753540c 100644 --- a/rodan-main/code/rodan/jobs/column_split/column_split.py +++ b/rodan-main/code/rodan/jobs/column_split/column_split.py @@ -96,6 +96,8 @@ def get_split_ranges(img,splits): return ranges # takes ranges in x, and stacks them vertically +# works for both RGB and RGBA images by adding white pixels regardless of amt of channels - +# constant_values=255 pads with (255, 255, 255) for 3, (255, 255, 255, 255) for 4 def get_stacked_image(img,ranges): chunks = [] max = 0 diff --git a/rodan-main/code/rodan/jobs/text_alignment/requirements.txt b/rodan-main/code/rodan/jobs/text_alignment/requirements.txt index 6865939bf..95de7747a 100644 --- a/rodan-main/code/rodan/jobs/text_alignment/requirements.txt +++ b/rodan-main/code/rodan/jobs/text_alignment/requirements.txt @@ -1,8 +1,11 @@ #numpy==1.17.3; python_version > "3.4" -git+https://github.com/timothydereuse/calamari@a058d76#egg=calamari_ocr; python_version > "3.4" -git+https://github.com/DDMAL/tfaip@7fb7f49#egg=tfaip; python_version > "3.4" +# Specify the tensorflow url link to prevent docker hub build failure due to hash mismatch +https://files.pythonhosted.org/packages/63/b2/2822fad63bdaedf3da841443daf92775d6fff16aba7199415e06f8d8c50f/tensorflow-2.5.1-cp37-cp37m-manylinux2010_x86_64.whl; python_version > "3.4" +# Install calamari and tfaip from our DDMAL fork +https://github.com/DDMAL/calamari/archive/refs/tags/DDMAL-v1.0.0.zip#egg=calamari_ocr; python_version > "3.4" +https://github.com/DDMAL/tfaip/archive/refs/tags/v1.0.0.zip#egg=tfaip; python_version > "3.4" paiargparse==1.1.2; python_version > "3.4" scipy==1.7.3; python_version > "3.4" Unidecode==1.0.22; python_version > "3.4" scikit-image==0.17.2; python_version > "3.4" -keras==2.5.0rc0; python_version > "3.4" +keras==2.5.0rc0; python_version > "3.4" \ No newline at end of file diff --git a/rodan-main/code/rodan/registerJobs.yaml b/rodan-main/code/rodan/registerJobs.yaml index df418c0d1..a034858de 100644 --- a/rodan-main/code/rodan/registerJobs.yaml +++ b/rodan-main/code/rodan/registerJobs.yaml @@ -5,6 +5,9 @@ "rodan.jobs.labeler" : [ "Labeler" ] } "RODAN_PYTHON3_JOBS": { + "rodan.jobs.aquitanian_ref_line_finding":{ + "rodan.jobs.aquitanian_ref_line_finding.aquitanian_ref_line_finding": ["AquitanianReferenceLineFinding"] + }, "rodan.jobs.helloworld": { "rodan.jobs.helloworld.helloworld" : ["HelloWorld"], "rodan.jobs.helloworld.helloworld" : ["HelloWorldMultiPort"], diff --git a/rodan-main/code/rodan/test/files/mei-encoding-test.mei b/rodan-main/code/rodan/test/files/mei-encoding-test.mei index e46f054d9..e73c18864 100644 --- a/rodan-main/code/rodan/test/files/mei-encoding-test.mei +++ b/rodan-main/code/rodan/test/files/mei-encoding-test.mei @@ -1,2 +1,2 @@ -MEI Encoding Output (1.0.0)
tersanctasspeciosafuitnominepinosavirgoransvelutrosaEtinterdispinosaodoremsauropropriodumdoctorisquemluitetperfecitinstudioperossarehabuitRestitutadeprimotumuloetreceptacumcordisbiloperdarisxpicta
\ No newline at end of file +MEI Encoding Output (1.0.0)
IntersanctasspeciosafuitnominepinosavirgoransvelutrosaEtinterdispinosaodoremsauropropriodumdoctorisquemluitetperfecitinstudioperossarehabuitRestitutadeprimotumuloetreceptacumcordisbiloperdarisxpicta
\ No newline at end of file