Mercurial > hg > orthanc-education
view WebApplication/dashboard.js @ 77:80b663d5f8fe default tip
replaced boost::math::iround() by Orthanc::Math::llround()
| author | Sebastien Jodogne <s.jodogne@gmail.com> |
|---|---|
| date | Tue, 27 Jan 2026 17:05:03 +0100 |
| parents | 0f8c46d755e2 |
| children |
line wrap: on
line source
/** * SPDX-FileCopyrightText: 2024-2026 Sebastien Jodogne, EPL UCLouvain, Belgium * SPDX-License-Identifier: AGPL-3.0-or-later */ /** * Orthanc for Education * Copyright (C) 2024-2026 Sebastien Jodogne, EPL UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License * as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ var app = new Vue({ el: '#app', data() { return { config: {}, projects: [], projectsIndex: {}, images: [], projectIdForParameters: '', projectIdForImages: '', projectIdForContent: '', projectForContent: {}, projectForContentResources: [], linkImage: '', modalModifyTextTitle: '', modalModifyTextValue: '', modalModifyText: '', modalCreateProject: '', modalCreateProjectName: '', modalCreateProjectDescription: '', modalCreateProjectSave: '', modalConfirmTitle: '', modalConfirm: '', modalProjectParameters: '', modalProjectParametersPolicy: '', modalProjectParametersPrimaryViewer: '', modalProjectParametersSecondaryViewers: [], modalProjectParametersBindLti: false, modalProjectParametersLtiContextId: false, modalEditInstructorsArea: '', modalEditInstructors: '', modalEditLearnersArea: '', modalEditLearners: '', modalLinkImageWithProject: '', modalLinkImageWithProjectSelected: '', modalLinkImageWithProjectAvailable: [], editProjectsSwitch: false, editImagesSwitch: false, configLtiClientId: '', // Variables that are used by multiple tabs filter: '', selectedViewer: '', // For DICOM-ization dicomizations: [], dicomizationType: '', uploading: false, uploadProgress: 0, uploadSize: 0, dicomizationBackgroundColor: 'white', dicomizationImagedAutodetect: true, dicomizationImagedWidth: '15', dicomizationOpenSlide: false, dicomizationPyramid: true } }, computed: { isProjectForContentSelected() { return this.projectIdForContent !== ''; }, projectViewers() { return this.projectForContent.secondary_viewers || ''; }, isUploadAvailable() { return (this.dicomizationType !== ''); }, isDicomizationWSI() { return (this.dicomizationType === 'wsi'); } }, watch: { configLtiClientId(newValue, oldValue) { axiosPutAsJson('../api/config/lti-client-id', newValue); }, projectIdForContent(newValue, oldValue) { this.reloadProjectForContent(); }, projectIdForImages(newValue, oldValue) { this.reloadImages(); } }, mounted: function() { this.modalModifyText = new bootstrap.Modal(document.getElementById('modalModifyText'), {}); this.modalCreateProject = new bootstrap.Modal(document.getElementById('modalCreateProject'), {}); this.modalConfirm = new bootstrap.Modal(document.getElementById('modalConfirm'), {}); this.modalProjectParameters = new bootstrap.Modal(document.getElementById('modalProjectParameters'), {}); this.modalEditInstructors = new bootstrap.Modal(document.getElementById('modalEditInstructors'), {}); this.modalEditLearners = new bootstrap.Modal(document.getElementById('modalEditLearners'), {}); this.modalLinkImageWithProject = new bootstrap.Modal(document.getElementById('modalLinkImageWithProject'), {}); var that = this; axios .get('../api/config') .then(function(response) { that.config = response.data; that.configLtiClientId = response.data.lti_client_id; that.selectedViewer = that.config.default_viewer; }); this.reloadProjectsParameters(); // Reload the projects whenever the modal to edit the project parameters is closed document.getElementById('modalProjectParameters').addEventListener('hidden.bs.modal', function (event) { that.reloadProjectsParameters(); }); document.getElementById('pills-images-tab').addEventListener('shown.bs.tab', function (event) { that.reloadProjectsParameters(); that.projectIdForImages = ''; that.filter = ''; that.selectedViewer = that.config.default_viewer; }); document.getElementById('pills-content-tab').addEventListener('shown.bs.tab', function (event) { that.projectIdForContent = ''; }); document.getElementById('pills-status-tab').addEventListener('shown.bs.tab', function (event) { that.reloadDicomizations(); }); // Track the current tab in the hash of the URL var hash = window.location.hash.substr(1); if (hash !== '') { var pill = 'pills-' + hash + '-tab'; var el = document.getElementById(pill); new bootstrap.Tab(el).show(); } document.querySelectorAll('[data-bs-toggle="pill"]').forEach((pill) => { pill.addEventListener('shown.bs.tab', (event) => { var pill = event.target.id; var hash = pill.split('-') [1]; window.location.hash = '#' + hash; }); }); }, methods: { getClipboardIconId: function(resource) { return 'clipboard-' + resource.level + '-' + resource['resource-id']; }, logout: function() { window.location.href = '../do-logout'; }, openOrthancExplorer: function(resource) { var url = '../../app/explorer.html'; if (resource !== undefined) { if (resource.level === 'Study') { url += '#study?uuid=' + encodeURIComponent(resource['resource-id']); } else if (resource.level === 'Series') { url += '#series?uuid=' + encodeURIComponent(resource['resource-id']); } else if (resource.level === 'Instance') { url += '#instance?uuid=' + encodeURIComponent(resource['resource-id']); } else { alert('Cannot generate the link to this resource in Orthanc Explorer'); return; } } window.open(url, '_blank').focus(); }, openOrthancExplorer2: function() { window.open('../../ui/app/index.html', '_blank').focus(); }, reloadProjectsParameters: function() { var that = this; axios .get('../api/projects') .then(function(response) { that.projects = sortObjectsByField(response.data, 'name'); that.projectsIndex = {}; that.projects.forEach((project) => { that.projectsIndex[project.id] = project; }); // Display the application after data is loaded to avoid flickering var el = document.getElementById('app'); el.classList.remove('hidden'); }); }, reloadImages: function() { var that = this; axios .post('../api/list-images', { project: this.projectIdForImages }) .then(function(response) { that.images = sortObjectsByField(response.data, 'title'); that.filter = ''; }) .catch(function() { that.images = []; }); }, reloadProjectForContent: function() { var projectId = this.projectIdForContent; var that = this; axios .get('../api/projects/' + projectId) .then(function(response) { axios .post('../api/list-images', { project: projectId }) .then(function(resources) { that.projectForContent = response.data; that.selectedViewer = response.data.primary_viewer; that.projectForContentResources = resources.data; that.filter = ''; that.linkImage = ''; }); }); }, launchModalModifyText(title, currentValue, callback) { var that = this; this.modalModifyTextTitle = title; this.modalModifyTextValue = currentValue; this.modalModifyTextSave = function(event) { that.modalModifyText.hide(); callback(that.modalModifyTextValue); }; this.modalModifyText.show(); }, createProject: function() { var that = this; this.modalCreateProjectSave = function(event) { that.modalCreateProject.hide(); axios.post('../api/projects', { name: that.modalCreateProjectName, description: that.modalCreateProjectDescription }).then(function() { that.reloadProjectsParameters(); }); } this.modalCreateProject.show(); }, deleteProject: function(project) { var that = this; this.modalConfirmTitle = 'Are you sure to delete this project?'; this.modalConfirmSave = function() { this.modalConfirm.hide(); axios.delete('../api/projects/' + project.id) .then(function() { that.reloadProjectsParameters(); }); } this.modalConfirm.show(); }, modifyProjectName: function(project) { var that = this; this.launchModalModifyText('Modify project name', project.name, function(newValue) { axiosPutAsJson('../api/projects/' + project.id + '/name', newValue) .then(function() { that.reloadProjectsParameters() }); }); }, modifyProjectDescription: function(project) { var that = this; this.launchModalModifyText('Modify project description', project.description, function(newValue) { axiosPutAsJson('../api/projects/' + project.id + '/description', newValue) .then(function() { that.reloadProjectsParameters() }); }); }, openProjectParameters: function(project) { var secondary_viewers = project.secondary_viewers.map((viewer) => viewer.id); this.projectIdForParameters = project.id; this.modalProjectParametersPolicy = project.policy; this.modalProjectParametersPrimaryViewer = project.primary_viewer; this.modalProjectParametersSecondaryViewers = this.config.viewers.map((viewer) => ({ 'id': viewer.id, 'description': viewer.description, 'checked': secondary_viewers.includes(viewer.id) })); if ('lti_context_id' in project) { this.modalProjectParametersBindLti = true; this.modalProjectParametersLtiContextId = project.lti_context_id; } else { this.modalProjectParametersBindLti = false; this.modalProjectParametersLtiContextId = ''; } this.modalProjectParameters.show(); }, changeProjectPolicy: function() { axiosPutAsJson('../api/projects/' + this.projectIdForParameters + '/policy', this.modalProjectParametersPolicy); }, changeProjectPrimaryViewer: function() { axiosPutAsJson('../api/projects/' + this.projectIdForParameters + '/primary-viewer', this.modalProjectParametersPrimaryViewer); }, changeProjectSecondaryViewers: function() { var secondary_viewers = []; this.modalProjectParametersSecondaryViewers.forEach((viewer) => { if (viewer.checked) { secondary_viewers.push(viewer.id); } }); axios.put('../api/projects/' + this.projectIdForParameters + '/secondary-viewers', secondary_viewers); }, changeProjectLtiContext: function() { var url = '../api/projects/' + this.projectIdForParameters + '/lti-context-id'; if (this.modalProjectParametersBindLti) { axiosPutAsJson(url, this.modalProjectParametersLtiContextId); } else { axios.delete(url); } }, editProjectInstructors: function(project) { var that = this; this.modalEditInstructorsArea = project.instructors.join('\n'); this.modalEditInstructorsSave = function(event) { this.modalEditInstructors.hide(); var instructors = this.modalEditInstructorsArea.split(/\r?\n/); axios.put('../api/projects/' + project.id + '/instructors', instructors) .then(function() { that.reloadProjectsParameters(); }); } this.modalEditInstructors.show(); }, editProjectLearners: function(project) { var that = this; this.modalEditLearnersArea = project.learners.join('\n'); this.modalEditLearnersSave = function(event) { this.modalEditLearners.hide(); var learners = this.modalEditLearnersArea.split(/\r?\n/); axios.put('../api/projects/' + project.id + '/learners', learners) .then(function() { that.reloadProjectsParameters(); }); } this.modalEditLearners.show(); }, clearClipboardIcons: function() { // Clear any "check" icon const icons = document.getElementsByClassName('clipboard-icon'); for (var i = 0; i < icons.length; i++) { icons[i].classList.remove('fa-check'); icons[i].classList.add('fa-clipboard'); } }, checkClipboardIcon: function(elementId) { const icon = document.getElementById(elementId); icon.classList.remove('fa-clipboard'); icon.classList.add('fa-check'); }, copyViewerToClipboard: function(resource) { var that = this; doCopyViewerToClipboard(this.selectedViewer, resource, function() { that.clearClipboardIcons(); that.checkClipboardIcon(that.getClipboardIconId(resource)); }); }, reloadForActivePane: function() { const activePane = document.querySelector('#v-pills-tabContent .tab-pane.active.show'); if (activePane.id == 'pills-content') { this.reloadProjectForContent(); } else if (activePane.id == 'pills-images') { this.reloadImages(); } else { console.error('Cannot detect the active pane: ' + activePane.id); } }, unlinkResource: function(resource, projectId) { var that = this; this.modalConfirmTitle = 'Are you sure to remove this image from the project?'; this.modalConfirmSave = function() { this.modalConfirm.hide(); axios.post('../api/unlink', { resource: resource, project: projectId }) .then(function(res) { that.reloadForActivePane(); }) .catch(function() { alert('This image cannot be removed'); }); } this.modalConfirm.show(); }, doLinkImage: function() { var that = this; axios.post('../api/link', { data: this.linkImage, project: this.projectIdForContent }) .then(function(res) { that.reloadProjectForContent(); }) .catch(function() { alert('Cannot create the link, check out your description of the image'); }); }, modifyImageTitle: function(resource) { var that = this; this.launchModalModifyText('Modify the title of the image', resource.title, function(newValue) { axios.post('../api/change-title', { resource: resource, title: newValue }) .then(function(res) { that.reloadForActivePane(); }); }); }, copyListProjectToClipboard: function(resource) { var that = this; doCopyListProjectToClipboard(this.projectIdForContent, function() { that.clearClipboardIcons(); that.checkClipboardIcon('copyListProjectIcon'); }); }, openListProject: function() { var url = 'list-projects.html?open-project-id=' + encodeURIComponent(this.projectIdForContent); window.open(url, '_blank').focus(); }, linkImageWithProject: function(resource) { this.modalLinkImageWithProjectAvailable = []; this.projects.forEach((project) => { if (!resource.projects.includes(project.id)) { this.modalLinkImageWithProjectAvailable.push(project); } }); if (this.modalLinkImageWithProjectAvailable.length == 0) { alert('This image is already associated with all the available projects'); } else { this.modalLinkImageWithProjectSelected = this.modalLinkImageWithProjectAvailable[0].id; var that = this; this.modalLinkImageWithProjectSave = function(event) { that.modalLinkImageWithProject.hide(); axios.post('../api/link', { resource: resource, project: that.modalLinkImageWithProjectSelected }) .then(function(res) { that.reloadImages(); }); } this.modalLinkImageWithProject.show(); } }, upload: function() { var file = document.getElementById('dicomizationFile').files[0]; if (!file) { alert('Please select a file first.'); return; } var chunkSize = 1 * 1024 * 1024; // 1 MB var totalChunks = Math.ceil(file.size / chunkSize); var uploadId = crypto.randomUUID(); this.uploading = true; this.uploadProgress = 0; this.uploadSize = Math.round(file.size / (1024 * 1024)); var dicomization = { 'upload-id': uploadId, 'type' : this.dicomizationType } if (!this.dicomizationImagedAutodetect) { if (isNaN(this.dicomizationImagedWidth)) { alert('Invalid value for the imaged volume width, must be a float: ' + this.dicomizationImagedWidth); } else { dicomization['imaged-width'] = parseFloat(this.dicomizationImagedWidth); } } if (this.dicomizationType == 'wsi') { dicomization['background-color'] = this.dicomizationBackgroundColor; dicomization['force-openslide'] = this.dicomizationOpenSlide; dicomization['reconstruct-pyramid'] = this.dicomizationPyramid; dicomization['study-description'] = file.name; } else { alert('Not implemented'); return; } var that = this; function uploadChunk(currentChunk) { if (currentChunk >= totalChunks) { console.log('All chunks uploaded successfully!'); axios.post('../api/dicomization', dicomization) .then(function(res) { that.uploading = false; console.log('Upload is now being DICOM-ized'); }) .catch(function(a) { that.uploading = false; alert('Upload has failed'); }); } else { var start = currentChunk * chunkSize; var end = Math.min(file.size, start + chunkSize); var chunk = file.slice(start, end); that.uploadProgress = Math.round(start / (1024 * 1024)); console.log('Uploading chunk ' + (currentChunk + 1) + ' of ' + totalChunks + '...'); axios.post('../api/upload', chunk, { headers: { 'Content-Range': 'bytes ' + start + '-' + (end - 1) + '/' + file.size, 'Upload-Id': uploadId, 'Content-Type': 'application/octet-stream' } }) .then(function(res) { uploadChunk(currentChunk + 1); }) .catch(function() { that.uploading = false; alert('Upload has failed'); }); } } uploadChunk(0); }, reloadDicomizations: function() { var that = this; axios .get('../api/dicomization') .then(function(response) { that.dicomizations = response.data.sort((a, b) => { if (a.time < b.time) { return 1; } else if (a.time > b.time) { return -1; } else { return 0; } }); that.dicomizations.forEach((dicomization) => { dicomization.date = new Date(dicomization.time + 'Z').toLocaleString(); dicomization.is_success = (dicomization.status === 'success'); dicomization.is_failure = (dicomization.status === 'failure'); }); }); }, cancelDicomization: function(dicomization) { var that = this; axios.delete('../api/dicomization/' + dicomization.id) .then(function() { that.reloadDicomizations(); }); }, openLogs: function(dicomization) { var url = '../api/dicomization/' + dicomization.id + '/logs'; window.open(url, '_blank').focus(); } } });
