diff --git a/microdraw/src/atlas_ui.js b/microdraw/src/atlas_ui.js index 397710fbf1ccde6b0cbc59a47f6b2a3f7d40d8e2..40f86f18c9a2542c3bf40cbd3803d9456dd56a80 100644 --- a/microdraw/src/atlas_ui.js +++ b/microdraw/src/atlas_ui.js @@ -120,6 +120,7 @@ var app = new Vue({ enable_auto_rename: true, annotation_import: new AnnotationImport(), annotation_filter: null, + sort_tasks_by_name: false, task_wizard: { loading: false, section_data: [], @@ -134,12 +135,12 @@ var app = new Vue({ let annotations = []; for (let section_item in this.task_wizard.section_data) { section_item = this.task_wizard.section_data[section_item]; - if (! section_item.selected) { + if (!section_item.selected) { continue; } for (let annotation in section_item["annotations"]) { annotation = section_item["annotations"][annotation]; - if (! this.filterAnnotation(this.annotation_filter, annotation.name)) { + if (!this.filterAnnotation(this.annotation_filter, annotation.name)) { // Filter out annotation continue; } @@ -150,10 +151,20 @@ var app = new Vue({ } } return annotations; - } + }, + sorted_tasks: function() { + if (this.selected_project === null) { + // No tasks + return []; + } + if (this.sort_tasks_by_name) { + return this.selected_project.tasks.toSorted((a, b) => a.name.localeCompare(b.name)); + } + return this.selected_project.tasks; + }, }, methods: { - getProjects: function () { + getProjects: function() { // Get username let user = undefined; if (typeof test_user !== "undefined") { @@ -171,43 +182,43 @@ var app = new Vue({ url: endpoint, type: "GET", crossDomain: true, - success: function (data) { + success: function(data) { _this.projects = data; if (_this.projects.length > 0) { _this.selected_project = _this.projects[0]; } post_message(`Got ${_this.projects.length} projects from server.`); }, - error: function (jqXHR, textStatus, errorThrown) { + error: function(jqXHR, textStatus, errorThrown) { post_message(`Error getting projects: ${textStatus} ${errorThrown}`); } }); }, - initializeAtlas: function () { + initializeAtlas: function() { // this.checkOrGetAtlasId(); this.getProjects(); }, - updateSelectedProject: function (project) { + updateSelectedProject: function(project) { if (project !== this.selected_project) { this.selected_project = project; this.selected_task = null; } }, - updateSelectedTask: function (task) { + updateSelectedTask: function(task) { if (task !== this.selected_task) { this.selected_task = task; } }, - updateSelectedJob: function (job) { + updateSelectedJob: function(job) { if (job !== this.selected_job) { this.selected_job = job; } }, - showJobsForTask: function (project, task) { + showJobsForTask: function(project, task) { this.selected_task = task; this.getJobStatusForAll(project, task); }, - newProject: function () { + newProject: function() { // Create a new project and set it as selected project to be edited let new_project = new Project(); let match = /B(?<brain>\d+)\.json/.exec(myOrigin.source); @@ -227,11 +238,11 @@ var app = new Vue({ } this.selected_project = new_project; }, - editProject: function (project) { + editProject: function(project) { // To edit a project, we set it as selected project this.selected_project = project; }, - saveProject: function (project) { + saveProject: function(project) { if (project.created === undefined) { // This is a new project, so we add it to the project list // and send it to the server @@ -244,13 +255,13 @@ var app = new Vue({ contentType: "application/json", data: JSON.stringify(project), crossDomain: true, - success: function (data) { + success: function(data) { let new_project = data; post_message(`Created project ${new_project.name} (id ${new_project.project_id})`); _this.projects.push(new_project); _this.closeProject(); }, - error: function (jqXHR, textStatus, errorThrown) { + error: function(jqXHR, textStatus, errorThrown) { post_message(`Error creating project: ${textStatus} ${errorThrown}`); _this.closeProject(); } @@ -266,7 +277,7 @@ var app = new Vue({ contentType: "application/json", data: JSON.stringify(project), crossDomain: true, - success: function (data) { + success: function(data) { let updated_project = data; post_message(`Updated project ${updated_project.name} (id ${updated_project.project_id})`); // Find index of project to update it @@ -279,14 +290,14 @@ var app = new Vue({ } _this.closeProject(); }, - error: function (jqXHR, textStatus, errorThrown) { + error: function(jqXHR, textStatus, errorThrown) { post_message(`Error updating project: ${textStatus} ${errorThrown}`); _this.closeProject(); } }); } }, - deleteProject: function (project_index) { + deleteProject: function(project_index) { let project = this.projects[project_index]; // Confirm delete @@ -309,10 +320,10 @@ var app = new Vue({ url: endpoint, type: "DELETE", crossDomain: true, - success: function (data) { + success: function(data) { post_message(`Removed project ${project.name} (id ${project.project_id})`); }, - error: function (jqXHR, textStatus, errorThrown) { + error: function(jqXHR, textStatus, errorThrown) { post_message(`Error removing project: ${textStatus} ${errorThrown}`); } }); @@ -321,20 +332,20 @@ var app = new Vue({ this.selected_project = null; this.selected_task = null; }, - newTask: function () { + newTask: function() { // Create a new task and set it as selected task to be edited let new_task = new Task(); this.selected_task = new_task; // Refresh annotations this.updateAvailableAnnotations(); }, - editTask: function (task) { + editTask: function(task) { // To edit a task, we set it as selected task this.selected_task = task; // Refresh annotations this.updateAvailableAnnotations(); }, - saveTask: function (project, task) { + saveTask: function(project, task) { let _this = this; if (task.created === undefined) { if (project.created === undefined) { @@ -353,14 +364,14 @@ var app = new Vue({ crossDomain: true, // I know this is deprecated, but I don't care... async: false, - success: function (data) { + success: function(data) { let created_task = data; post_message(`Created task ${created_task.name} (id ${project.project_id} - ${created_task.task_id})`); // Add the task to the existing project project.tasks.push(created_task); _this.closeTask(); }, - error: function (jqXHR, textStatus, errorThrown) { + error: function(jqXHR, textStatus, errorThrown) { post_message(`Error creating task: ${textStatus} ${errorThrown}`); _this.closeTask(); } @@ -376,7 +387,7 @@ var app = new Vue({ contentType: "application/json", data: JSON.stringify(task), crossDomain: true, - success: function (data) { + success: function(data) { let updated_task = data; post_message(`Updated task ${updated_task.name} (id ${project.project_id} - ${updated_task.task_id})`); // Find the corresponding task in the project and update it @@ -389,7 +400,7 @@ var app = new Vue({ } _this.closeTask(); }, - error: function (jqXHR, textStatus, errorThrown) { + error: function(jqXHR, textStatus, errorThrown) { post_message(`Error updating task: ${textStatus} ${errorThrown}`); _this.closeTask(); } @@ -397,7 +408,7 @@ var app = new Vue({ } }, - addTask: function () { + addTask: function() { let new_task = new Task(); this.updateSelectedTask(new_task); }, @@ -405,7 +416,7 @@ var app = new Vue({ let pattern = /_(l|r)(_\d+)?$/i return name.replace(pattern, ""); }, - addAnnotationToTask: function (index) { + addAnnotationToTask: function(index) { let annotation_to_add = this.available_annotations[index]; this.selected_task.annotations.push(annotation_to_add); if (this.enable_auto_rename) { @@ -417,7 +428,7 @@ var app = new Vue({ } this.available_annotations.splice(index, 1); }, - removeAnnotationFromTask: function (index) { + removeAnnotationFromTask: function(index) { let annoation_to_remove = this.selected_task.annotations[index]; this.selected_task.annotations.splice(index, 1); if (annoation_to_remove.original_name !== undefined) { @@ -426,7 +437,7 @@ var app = new Vue({ } this.available_annotations.push(annoation_to_remove); }, - updateAvailableAnnotations: function () { + updateAvailableAnnotations: function() { // Clear available regions this.available_annotations.splice(0, this.available_annotations.length); // Add current regions @@ -448,7 +459,7 @@ var app = new Vue({ try { // Ignore case var re = new RegExp(annotation_filter, "i"); - } catch(err) { + } catch (err) { // Invalid regex, might also occur during typing return true; } @@ -471,12 +482,12 @@ var app = new Vue({ url: endpoint, type: "DELETE", crossDomain: true, - success: function (data) { + success: function(data) { // Remove task from the project project.tasks.splice(task_index, 1); post_message(`Deleted task task ${task.name} (id ${project.project_id} - ${task.task_id})`); }, - error: function (jqXHR, textStatus, errorThrown) { + error: function(jqXHR, textStatus, errorThrown) { post_message(`Error deleting task: ${textStatus} ${errorThrown}`); } }); @@ -496,13 +507,13 @@ var app = new Vue({ contentType: "application/json", data: JSON.stringify(job_configuration), crossDomain: true, - success: function (data) { + success: function(data) { let job = data; post_message(`Submitted job ${job.job_id}`); task.jobs.push(job); _this.getJobStatus(project, task, job); }, - error: function (jqXHR, textStatus, errorThrown) { + error: function(jqXHR, textStatus, errorThrown) { let msg = `Error submitting job. Server may be unreachable.\nStatus: ${textStatus}\nMessage: ${errorThrown}`; alert(msg); post_message(msg); @@ -512,7 +523,7 @@ var app = new Vue({ submitTrainingPredictionJob(project, task) { this.submitTrainingJob(project, task, true); }, - submitTrainingJob(project, task, with_prediction=false) { + submitTrainingJob(project, task, with_prediction = false) { let job_type = "training"; if (with_prediction) { job_type = "train+predict"; @@ -610,7 +621,7 @@ var app = new Vue({ url: endpoint, type: "GET", crossDomain: true, - success: function (data) { + success: function(data) { // Update job state post_message(`Updated status of job ${job.job_id}`); if (data.status.JobState) { @@ -620,12 +631,12 @@ var app = new Vue({ job.status = "UNKNOWN"; } }, - error: function (jqXHR, textStatus, errorThrown) { + error: function(jqXHR, textStatus, errorThrown) { post_message(`Error getting status job status: ${textStatus} ${errorThrown}`); } }); }, - cancelJob: function (project, task, job_index) { + cancelJob: function(project, task, job_index) { // Cancel the given job let job = task.jobs[job_index] let endpoint = `${atlasServerApiUrl}/projects/${project.project_id}/tasks/${task.task_id}/jobs/${job.job_id}`; @@ -633,34 +644,34 @@ var app = new Vue({ url: endpoint, type: "DELETE", crossDomain: true, - success: function (data) { + success: function(data) { // Remove job from task list and save it task.jobs.splice(job_index, 1); post_message(`Deleted job ${job.job_id} of task ${task.name} (id ${project.project_id} - ${task.task_id})`); }, - error: function (jqXHR, textStatus, errorThrown) { + error: function(jqXHR, textStatus, errorThrown) { post_message(`Error canceling job: ${textStatus} ${errorThrown}`); } }); }, - cancelAllJobs: function (project, task) { + cancelAllJobs: function(project, task) { // Cancel all jobs for a task for (var job_index in task.jobs) { this.cancelJob(project, task, job_index); } }, - getJobStatusForAll: function (project, task) { + getJobStatusForAll: function(project, task) { for (var i in task.jobs) { this.getJobStatus(project, task, task.jobs[i]); } }, - pollJobStatus: function () { + pollJobStatus: function() { if (this.selected_project !== null && this.selected_task !== null) { post_message(`Polling for job status of project ${this.selected_project.project_id}, task ${this.selected_task.task_id}`); this.getJobStatusForAll(this.selected_project, this.selected_task); } }, - getResultLink: function (project, task) { + getResultLink: function(project, task) { let host = window.location.host; let path = window.location.pathname; let protocol = window.location.protocol; @@ -684,7 +695,7 @@ var app = new Vue({ window.open(target_link); }, initializeAnnotationImport: function() { - if (typeof(myOrigin.user) == "object") { + if (typeof (myOrigin.user) == "object") { this.annotation_import.src_user = null; this.annotation_import.dst_user = null; } else { @@ -724,7 +735,7 @@ var app = new Vue({ url: endpoint, type: "GET", crossDomain: true, - success: function (data) { + success: function(data) { // Update job state post_message(`Retrieved annotations`); let annotations = data["annotations"]; @@ -735,7 +746,7 @@ var app = new Vue({ _this.annotation_import.annotation_items.push(new AnnotationImportItem(key, value)); } }, - error: function (jqXHR, textStatus, errorThrown) { + error: function(jqXHR, textStatus, errorThrown) { post_message(`Error getting annotations status: ${textStatus} ${errorThrown}`); }, complete: function(data) { @@ -780,11 +791,11 @@ var app = new Vue({ contentType: "application/json", data: JSON.stringify(data), crossDomain: true, - success: function (data) { + success: function(data) { // Remove annotation from list _this.removeImportAnnotation(annotation); }, - error: function (jqXHR, textStatus, errorThrown) { + error: function(jqXHR, textStatus, errorThrown) { post_message(`Error importing annotation: ${textStatus} ${errorThrown}`); }, complete: function(data) { @@ -823,7 +834,7 @@ var app = new Vue({ this.taskWizardSetTaskName(); }, taskWizardSectionToggle: function(section_item) { - section_item.selected = ! section_item.selected; + section_item.selected = !section_item.selected; this.taskWizardSetTaskName(); }, taskWizardSetTaskName: function() { @@ -921,7 +932,7 @@ var app = new Vue({ let match = line.match(pattern); let abort = false; // Search for selected sections - for (let mi=0; mi<match.length; mi++) { + for (let mi = 0; mi < match.length; mi++) { let section = parseInt(match[mi]); let found = false; @@ -990,7 +1001,7 @@ var app = new Vue({ let import_text = ""; for (let i = 0; i < sections.length - 1; i++) { let section_from = sections[i]; - let section_to = sections[i+1]; + let section_to = sections[i + 1]; import_text = import_text + `${section_from} - ${section_to}\n`; } this.task_wizard.import_text = import_text; @@ -1028,8 +1039,8 @@ var app = new Vue({ contentType: "application/json", data: JSON.stringify(query), crossDomain: true, - success: function (data) { - if (! data) { + success: function(data) { + if (!data) { return; } if (data.length == 0) { @@ -1049,9 +1060,9 @@ var app = new Vue({ } ); } - _this.task_wizard.section_data.sort(function (a, b) { return a.section - b.section; }); + _this.task_wizard.section_data.sort(function(a, b) { return a.section - b.section; }); }, - error: function (jqXHR, textStatus, errorThrown) { + error: function(jqXHR, textStatus, errorThrown) { post_message(`Error getting annotations for section ${sections}: ${textStatus} ${errorThrown}`); }, complete: function(data) { @@ -1060,9 +1071,9 @@ var app = new Vue({ }); }, }, - created: function () { + created: function() { post_message("Register job status polling") - setInterval(function () { + setInterval(function() { this.pollJobStatus(); }.bind(this), 5000); }, diff --git a/microdraw/src/microdraw.html b/microdraw/src/microdraw.html index fdb69ae9e2700dea6a08eda6641b10fdff64c6a8..75f9326a9b3b8d1b2e3f078d23ced773cdfc4b58 100755 --- a/microdraw/src/microdraw.html +++ b/microdraw/src/microdraw.html @@ -254,7 +254,7 @@ <ol id="atlas_task_list" class="list-group atlas_task_list border border-dark rounded bg-light" v-if="selected_project !== null"> - <li v-for="(task, index) in selected_project.tasks" + <li v-for="(task, index) in sorted_tasks" class="list-group-item list-group-item-action"> <div class="form-row"> <input v-model="task.task_id" class="col-1 form-control" type="text" readonly /> @@ -290,10 +290,11 @@ </ol> </div> + <!-- Button group --> <div class="btn-group" role="group"> <button v-on:click="newTask" type="button" class="btn btn-primary" data-toggle="modal" - data-target="#atlas_ui_annotations_modal">New task...</button> + data-target="#atlas_ui_annotations_modal">New task...</button> <button type="button" class="btn btn-success" data-toggle="modal" data-target="#atlas_ui_task_wizard_modal" v-on:click="initializeTaskWizard">Task wizard</button> </div> @@ -303,10 +304,15 @@ <button type="button" class="btn btn-info" v-on:click="submitAllJobsForProject(selected_project, submitTrainingPredictionJob)">Train+Predict all</button> <button type="button" class="btn btn-danger" v-on:click="cancelAllJobsForProject(selected_project)">Cancel all</button> </div> + + <div class="form-group" id="atlas_ui_task_options"> + <input type="checkbox" v-model="sort_tasks_by_name" id="atlas_ui_task_sort_by_name"/> + <label for="atlas_ui_task_sort_by_name">Sort by name</label><p> + </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-primary" data-dismiss="modal" - v-on:click="saveProject(selected_project)">Save</button> + v-on:click="saveProject(selected_project)">Save</button> <button type="button" class="btn btn-secondary" data-dismiss="modal" v-on:click="closeProject()">Close</button> </div> </div>