# HG changeset patch
# User Sebastien Jodogne
# Date 1785427387 -7200
# Node ID c98502a806f23635c6b8c565fbd6ae6d591b7264
# Parent 3a6a279a8ff97e0a38aaf0f5746c154b39477cda
passing the project ID to the viewer URL generator
diff -r 3a6a279a8ff9 -r c98502a806f2 Sources/EducationRestApi.cpp
--- a/Sources/EducationRestApi.cpp Thu Jul 30 16:10:10 2026 +0200
+++ b/Sources/EducationRestApi.cpp Thu Jul 30 18:03:07 2026 +0200
@@ -176,6 +176,7 @@
{
assert(user.GetRole() == Role_Guest);
+ const std::string projectId = Orthanc::SerializationToolbox::ReadString(body, "project");
const ViewerType viewer = ParseViewerType(Orthanc::SerializationToolbox::ReadString(body, "viewer"));
Json::Value resource;
@@ -184,7 +185,7 @@
throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
}
- const std::string viewerUrl = EducationToolbox::GenerateViewerUrl(viewer, resource);
+ const std::string viewerUrl = EducationToolbox::GenerateViewerUrlGeneric(projectId, viewer, resource);
Json::Value answer;
diff -r 3a6a279a8ff9 -r c98502a806f2 Sources/EducationToolbox.cpp
--- a/Sources/EducationToolbox.cpp Thu Jul 30 16:10:10 2026 +0200
+++ b/Sources/EducationToolbox.cpp Thu Jul 30 18:03:07 2026 +0200
@@ -24,19 +24,28 @@
#include "EducationToolbox.h"
+#include "HttpToolbox.h"
+
#include
+#include
+#include
+
+#include
-static std::string FormatWSITitle(const std::string& title)
+static std::string FormatGetArgument(const std::string& key,
+ const std::string& value)
{
- if (title.empty())
+ assert(!key.empty());
+
+ if (value.empty())
{
return "";
}
else
{
/**
- * The following implementation is correct if title is an UTF-8
+ * The following implementation is correct if value is an UTF-8
* string: "When a new URI scheme defines a component that
* represents textual data consisting of characters from the
* Universal Character Set [UCS], the data should first be encoded
@@ -46,13 +55,14 @@
* https://www.rfc-editor.org/rfc/rfc3986
**/
std::string tmp;
- Orthanc::Toolbox::UriEncode(tmp, title);
- return "&description=" + tmp;
+ Orthanc::Toolbox::UriEncode(tmp, value);
+ return "&" + key + "=" + tmp;
}
}
-static std::string GenerateStudyViewerUrl(ViewerType viewer,
+static std::string GenerateStudyViewerUrl(const std::string& projectId,
+ ViewerType viewer,
const std::string& studyId,
const std::string& studyInstanceUid,
const std::string& title)
@@ -83,7 +93,8 @@
}
-static std::string GenerateSeriesViewerUrl(ViewerType viewer,
+static std::string GenerateSeriesViewerUrl(const std::string& projectId,
+ ViewerType viewer,
const std::string& seriesId,
const std::string& studyInstanceUid,
const std::string& seriesInstanceUid,
@@ -95,7 +106,7 @@
return "stone-webviewer/index.html?study=" + studyInstanceUid + "&series=" + seriesInstanceUid;
case ViewerType_WholeSlideImaging:
- return "wsi/app/viewer.html?series=" + seriesId + FormatWSITitle(title);
+ return "wsi/app/viewer.html?series=" + seriesId + FormatGetArgument("title", title);
case ViewerType_VolView:
return "volview/index.html?names=[archive.zip]&urls=[../series/" + seriesId + "/archive]";
@@ -106,7 +117,8 @@
}
-static std::string GenerateInstanceViewerUrl(ViewerType viewer,
+static std::string GenerateInstanceViewerUrl(const std::string& projectId,
+ ViewerType viewer,
const std::string& instanceId,
const std::string& studyInstanceUid,
const std::string& seriesInstanceUid,
@@ -116,7 +128,7 @@
switch (viewer)
{
case ViewerType_WholeSlideImaging:
- return "wsi/app/viewer.html?instance=" + instanceId + FormatWSITitle(title);
+ return "wsi/app/viewer.html?instance=" + instanceId + FormatGetArgument("title", title);
default:
throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
@@ -124,36 +136,51 @@
}
+static const char* const KEY_LEVEL = "level";
+static const char* const KEY_PROJECT = "project";
+static const char* const KEY_RESOURCE_ID = "resource-id";
+static const char* const KEY_SERIES_INSTANCE_UID = "series-instance-uid";
+static const char* const KEY_SOP_INSTANCE_UID = "sop-instance-uid";
+static const char* const KEY_STUDY_INSTANCE_UID = "study-instance-uid";
+static const char* const KEY_TITLE = "title";
+
+
namespace EducationToolbox
{
- std::string GenerateViewerUrl(ViewerType viewer,
- const std::map& resource)
+ std::string GenerateViewerUrlForDeepLinking(ViewerType viewer,
+ const std::map& resource)
{
- const std::string levelString = HttpToolbox::ReadMandatoryString(resource, "level");
+ const std::string projectId = HttpToolbox::ReadMandatoryString(resource, KEY_PROJECT);
+ if (projectId.empty())
+ {
+ throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+ }
+
+ const std::string levelString = HttpToolbox::ReadMandatoryString(resource, KEY_LEVEL);
const Orthanc::ResourceType level = Orthanc::StringToResourceType(levelString.c_str());
switch (level)
{
case Orthanc::ResourceType_Study:
- return GenerateStudyViewerUrl(viewer,
- HttpToolbox::ReadMandatoryString(resource, "resource-id"),
- HttpToolbox::ReadMandatoryString(resource, "study-instance-uid"),
- HttpToolbox::ReadMandatoryString(resource, "title"));
+ return GenerateStudyViewerUrl(projectId, viewer,
+ HttpToolbox::ReadMandatoryString(resource, KEY_RESOURCE_ID),
+ HttpToolbox::ReadMandatoryString(resource, KEY_STUDY_INSTANCE_UID),
+ HttpToolbox::ReadMandatoryString(resource, KEY_TITLE));
case Orthanc::ResourceType_Series:
- return GenerateSeriesViewerUrl(viewer,
- HttpToolbox::ReadMandatoryString(resource, "resource-id"),
- HttpToolbox::ReadMandatoryString(resource, "study-instance-uid"),
- HttpToolbox::ReadMandatoryString(resource, "series-instance-uid"),
- HttpToolbox::ReadMandatoryString(resource, "title"));
+ return GenerateSeriesViewerUrl(projectId, viewer,
+ HttpToolbox::ReadMandatoryString(resource, KEY_RESOURCE_ID),
+ HttpToolbox::ReadMandatoryString(resource, KEY_STUDY_INSTANCE_UID),
+ HttpToolbox::ReadMandatoryString(resource, KEY_SERIES_INSTANCE_UID),
+ HttpToolbox::ReadMandatoryString(resource, KEY_TITLE));
case Orthanc::ResourceType_Instance:
- return GenerateInstanceViewerUrl(viewer,
- HttpToolbox::ReadMandatoryString(resource, "resource-id"),
- HttpToolbox::ReadMandatoryString(resource, "study-instance-uid"),
- HttpToolbox::ReadMandatoryString(resource, "series-instance-uid"),
- HttpToolbox::ReadMandatoryString(resource, "sop-instance-uid"),
- HttpToolbox::ReadMandatoryString(resource, "title"));
+ return GenerateInstanceViewerUrl(projectId, viewer,
+ HttpToolbox::ReadMandatoryString(resource, KEY_RESOURCE_ID),
+ HttpToolbox::ReadMandatoryString(resource, KEY_STUDY_INSTANCE_UID),
+ HttpToolbox::ReadMandatoryString(resource, KEY_SERIES_INSTANCE_UID),
+ HttpToolbox::ReadMandatoryString(resource, KEY_SOP_INSTANCE_UID),
+ HttpToolbox::ReadMandatoryString(resource, KEY_TITLE));
default:
throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
@@ -161,26 +188,38 @@
}
- std::string GenerateViewerUrl(ViewerType viewer,
- const Json::Value& resource)
+ std::string GenerateViewerUrlGeneric(const std::string& projectId,
+ ViewerType viewer,
+ const Json::Value& resource)
{
- std::map args;
+ const std::string levelString = Orthanc::SerializationToolbox::ReadString(resource, KEY_LEVEL);
+ const Orthanc::ResourceType level = Orthanc::StringToResourceType(levelString.c_str());
- if (resource.type() != Json::objectValue)
+ switch (level)
{
- throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
- }
+ case Orthanc::ResourceType_Study:
+ return GenerateStudyViewerUrl(projectId, viewer,
+ Orthanc::SerializationToolbox::ReadString(resource, KEY_RESOURCE_ID),
+ Orthanc::SerializationToolbox::ReadString(resource, KEY_STUDY_INSTANCE_UID),
+ Orthanc::SerializationToolbox::ReadString(resource, KEY_TITLE));
- Json::Value::Members members = resource.getMemberNames();
- for (size_t i = 0; i < members.size(); i++)
- {
- const Json::Value& value = resource[members[i]];
- if (value.type() == Json::stringValue)
- {
- args[members[i]] = value.asString();
- }
+ case Orthanc::ResourceType_Series:
+ return GenerateSeriesViewerUrl(projectId, viewer,
+ Orthanc::SerializationToolbox::ReadString(resource, KEY_RESOURCE_ID),
+ Orthanc::SerializationToolbox::ReadString(resource, KEY_STUDY_INSTANCE_UID),
+ Orthanc::SerializationToolbox::ReadString(resource, KEY_SERIES_INSTANCE_UID),
+ Orthanc::SerializationToolbox::ReadString(resource, KEY_TITLE));
+
+ case Orthanc::ResourceType_Instance:
+ return GenerateInstanceViewerUrl(projectId, viewer,
+ Orthanc::SerializationToolbox::ReadString(resource, KEY_RESOURCE_ID),
+ Orthanc::SerializationToolbox::ReadString(resource, KEY_STUDY_INSTANCE_UID),
+ Orthanc::SerializationToolbox::ReadString(resource, KEY_SERIES_INSTANCE_UID),
+ Orthanc::SerializationToolbox::ReadString(resource, KEY_SOP_INSTANCE_UID),
+ Orthanc::SerializationToolbox::ReadString(resource, KEY_TITLE));
+
+ default:
+ throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
}
-
- return GenerateViewerUrl(viewer, args);
}
}
diff -r 3a6a279a8ff9 -r c98502a806f2 Sources/EducationToolbox.h
--- a/Sources/EducationToolbox.h Thu Jul 30 16:10:10 2026 +0200
+++ b/Sources/EducationToolbox.h Thu Jul 30 18:03:07 2026 +0200
@@ -24,14 +24,19 @@
#pragma once
-#include "Permissions/AuthenticatedUser.h"
+#include "EducationEnumerations.h"
+
+#include
+#include
@@ -397,7 +397,7 @@
diff -r 3a6a279a8ff9 -r c98502a806f2 WebApplication/dashboard.js
--- a/WebApplication/dashboard.js Thu Jul 30 16:10:10 2026 +0200
+++ b/WebApplication/dashboard.js Thu Jul 30 18:03:07 2026 +0200
@@ -414,7 +414,7 @@
copyViewerToClipboard: function(resource) {
var that = this;
- doCopyViewerToClipboard(this.selectedViewer, resource, function() {
+ doCopyViewerToClipboard(this.projectIdForContent, this.selectedViewer, resource, function() {
that.clearClipboardIcons();
that.checkClipboardIcon(that.getClipboardIconId(resource));
});
diff -r 3a6a279a8ff9 -r c98502a806f2 WebApplication/deep.js
--- a/WebApplication/deep.js Thu Jul 30 16:10:10 2026 +0200
+++ b/WebApplication/deep.js Thu Jul 30 18:03:07 2026 +0200
@@ -28,6 +28,7 @@
return {
project_name: '',
project_description: '',
+ project_id: '',
filter: '',
resources: [],
info: {},
@@ -50,6 +51,7 @@
params.append('study-instance-uid', resource['study-instance-uid']);
params.append('viewer', this.selected_viewer);
params.append('title', resource.title);
+ params.append('project', this.project_id);
if (full) {
// Add the additional parameters needed to create the deep link
@@ -82,6 +84,7 @@
that.resources = sortObjectsByField(res.data.resources, 'title');
that.selected_viewer = res.data.primary_viewer;
that.viewers = res.data.secondary_viewers;
+ that.project_id = res.data.project;
that.resources.forEach((resource) => {
axios.get(resource.preview_url, {
diff -r 3a6a279a8ff9 -r c98502a806f2 WebApplication/list-projects.html
--- a/WebApplication/list-projects.html Thu Jul 30 16:10:10 2026 +0200
+++ b/WebApplication/list-projects.html Thu Jul 30 18:03:07 2026 +0200
@@ -128,7 +128,7 @@
diff -r 3a6a279a8ff9 -r c98502a806f2 WebApplication/list-projects.js
--- a/WebApplication/list-projects.js Thu Jul 30 16:10:10 2026 +0200
+++ b/WebApplication/list-projects.js Thu Jul 30 18:03:07 2026 +0200
@@ -143,7 +143,7 @@
copyViewerToClipboard: function(resource) {
var that = this;
- doCopyViewerToClipboard(this.selectedViewer, resource, function() {
+ doCopyViewerToClipboard(this.selectedProjectId, this.selectedViewer, resource, function() {
// Clear any "check" icon
const icons = document.getElementsByClassName('clipboard-icon');
for (var i = 0; i < icons.length; i++) {
diff -r 3a6a279a8ff9 -r c98502a806f2 WebApplication/toolbox.js
--- a/WebApplication/toolbox.js Thu Jul 30 16:10:10 2026 +0200
+++ b/WebApplication/toolbox.js Thu Jul 30 18:03:07 2026 +0200
@@ -47,8 +47,9 @@
}
-function openViewer(viewer, resource) {
+function openViewer(projectId, viewer, resource) {
axios.post('../api/resource-viewer-url', {
+ project: projectId,
viewer: viewer,
resource: resource
})
@@ -73,8 +74,9 @@
}
-function doCopyViewerToClipboard(viewer, resource, callback) {
+function doCopyViewerToClipboard(projectId, viewer, resource, callback) {
axios.post('../api/resource-viewer-url', {
+ project: projectId,
viewer: viewer,
resource: resource
})