changeset 562:f88082aa5230 annotations

first end-to-end sharing of layer
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 01 Sep 2026 17:19:47 +0200
parents a802aa368b0a
children 05c99738d28b
files ViewerPlugin/Annotations/AnnotationsRestApi.cpp ViewerPlugin/WebApplication/viewer.html ViewerPlugin/WebApplication/viewer.js
diffstat 3 files changed, 80 insertions(+), 42 deletions(-) [+]
line wrap: on
line diff
--- a/ViewerPlugin/Annotations/AnnotationsRestApi.cpp	Tue Sep 01 16:46:07 2026 +0200
+++ b/ViewerPlugin/Annotations/AnnotationsRestApi.cpp	Tue Sep 01 17:19:47 2026 +0200
@@ -134,9 +134,11 @@
   static const char* const KEY_FEATURES = "features";
   static const char* const KEY_ID = "id";
   static const char* const KEY_LAYERS = "layers";
+  static const char* const KEY_LAYER_ID = "layer-id";
   static const char* const KEY_NAME = "name";
   static const char* const KEY_PUBLIC = "public";
   static const char* const KEY_SHARED_WITH = "shared_with";
+  static const char* const KEY_TYPE = "type";
   static const char* const KEY_VERSION = "version";
   static const char* const KEY_VISIBLE = "visible";
 
@@ -754,32 +756,6 @@
   };
 
 
-  class SharedLayerId
-  {
-  private:
-    UserId       author_;
-    std::string  layerId_;
-
-  public:
-    SharedLayerId(const UserId& author,
-                  const std::string& layerId) :
-      author_(author),
-      layerId_(layerId)
-    {
-    }
-
-    const UserId& GetAuthor() const
-    {
-      return author_;
-    }
-
-    const std::string& GetLayerId() const
-    {
-      return layerId_;
-    }
-  };
-
-
   class AnnotationsWorkspace : public Orthanc::IDynamicObject
   {
   private:
@@ -1075,9 +1051,11 @@
         }
       }
 
-      void ListSharedLayers(std::list<SharedLayerId>& target) const
+      void ListSharedLayers(std::set<UserId>& authors,
+                            std::set<std::string>& layerIds) const
       {
-        target.clear();
+        authors.clear();
+        layerIds.clear();
 
         if (IsValid())
         {
@@ -1085,7 +1063,12 @@
 
           while (!iterator.IsDone())
           {
-            target.push_back(SharedLayerId(userId_, iterator.GetLayer().GetId()));
+            const SharedLayer& layer = dynamic_cast<const SharedLayer&>(iterator.GetLayer());
+
+            // TODO - Ensure that layer is still shared with "userId_"
+
+            authors.insert(layer.GetAuthor());
+            layerIds.insert(layer.GetId());
             iterator.Next();
           }
         }
@@ -1443,7 +1426,7 @@
     {
       AnnotationsCommandContext context(request);
 
-      const std::string layerId = context.GetBodyString("layer-id");
+      const std::string layerId = context.GetBodyString(KEY_LAYER_ID);
 
       {
         AnnotationsWorkspace::UserWriter writer(context.GetWorkspace(), context.GetUser().GetAnnotatingId());
@@ -1477,7 +1460,8 @@
 
         if (!Orthanc::Toolbox::ReadJson(unserialized, uncompressed) ||
             !unserialized.isObject() ||
-            !unserialized.isMember(KEY_FEATURES))
+            !unserialized.isMember(KEY_FEATURES) ||
+            !unserialized[KEY_FEATURES].isArray())
         {
           throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
         }
@@ -1504,6 +1488,8 @@
 
     void Save() const
     {
+      assert(content_.isArray());
+
       Json::Value unserialized;
       unserialized[KEY_VERSION] = static_cast<unsigned int>(ORTHANC_WSI_ANNOTATIONS_VERSION);
       unserialized[KEY_FEATURES] = content_;
@@ -1528,14 +1514,35 @@
     void GetContent(Json::Value& target)
     {
       Orthanc::ReaderWriterLock::ReadLock lock(mutex_);
+
+      assert(content_.isArray());
       target = content_;
     }
 
     void SetContent(const Json::Value& content)
     {
-      Orthanc::ReaderWriterLock::WriteLock lock(mutex_);
-      content_ = content;
-      Save();
+      if (!content.isArray())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+
+      for (Json::Value::ArrayIndex i = 0; i < content.size(); i++)
+      {
+        if (!content[i].isObject() ||
+            !content[i].isMember(KEY_LAYER_ID) ||
+            !content[i].isMember(KEY_TYPE) ||
+            !content[i][KEY_LAYER_ID].isString() ||
+            !content[i][KEY_TYPE].isString())
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+        }
+      }
+
+      {
+        Orthanc::ReaderWriterLock::WriteLock lock(mutex_);
+        content_ = content;
+        Save();
+      }
     }
   };
 
@@ -1563,10 +1570,10 @@
     }
 
   public:
-    CachedUserFeatures(const AnnotationsWorkspaceId& annotations,
+    CachedUserFeatures(const AnnotationsWorkspaceId& id,
                        const UserId& user)
     {
-      const std::string key = annotations.GetFeaturesKey(user);
+      const std::string key = id.GetFeaturesKey(user);
 
       cached_ = GetCache().GetCachedValue(key);
 
@@ -1821,19 +1828,40 @@
     {
       AnnotationsCommandContext context(request);
 
-      std::list<SharedLayerId> layers;
+      std::set<UserId> authors;
+      std::set<std::string> layerIds;
 
       {
         AnnotationsWorkspace::UserReader reader(context.GetWorkspace(), context.GetUser().GetAnnotatingId());
-        reader.ListSharedLayers(layers);
+        reader.ListSharedLayers(authors, layerIds);
       }
 
-      for (std::list<SharedLayerId>::const_iterator it = layers.begin(); it != layers.end(); ++it)
+      Json::Value sharedFeatures = Json::arrayValue;
+
+      // Loop over the imported authors
+      for (std::set<UserId>::const_iterator it = authors.begin(); it != authors.end(); ++it)
       {
-        printf("[%s] [%s]\n", it->GetAuthor().GetName().c_str(), it->GetLayerId().c_str());
+        Json::Value authorFeatures;
+
+        {
+          CachedUserFeatures author(context.GetWorkspaceId(), *it);
+          author.GetFeatures().GetContent(authorFeatures);
+        }
+
+        assert(authorFeatures.isArray());
+
+        for (Json::Value::ArrayIndex i = 0; i < authorFeatures.size(); i++)
+        {
+          std::string layerId = Orthanc::SerializationToolbox::ReadString(authorFeatures[i], KEY_LAYER_ID);
+          if (layerIds.find(layerId) != layerIds.end())
+          {
+            sharedFeatures.append(authorFeatures[i]);
+          }
+        }
       }
 
       Json::Value answer;
+      answer[KEY_FEATURES] = sharedFeatures;
       ViewerToolbox::AnswerJson(output, answer);
     }
   }
--- a/ViewerPlugin/WebApplication/viewer.html	Tue Sep 01 16:46:07 2026 +0200
+++ b/ViewerPlugin/WebApplication/viewer.html	Tue Sep 01 17:19:47 2026 +0200
@@ -396,13 +396,14 @@
                   <button type="button" class="btn btn-link p-1" style="color:inherit">
                     <i class="bi"
                        :class="layer.visible ? 'bi-eye' : 'bi-eye-slash'"
-                       v-on:click="layer.visible = !layer.visible; SaveSharedLayer(layer)"
+                       v-on:click="layer.visible = !layer.visible; drawSharedLayer.changed(); SaveSharedLayer(layer)"
                        ></i>
                   </button>
                 </td>
                 <td class="text-center align-middle p-0">
                   <input type="color" style="width:22px;height:22px;padding:1px;border:none;cursor:pointer;border-radius:3px"
                          v-model:value="layer.color"
+                         v-on:input="drawSharedLayer.changed()"
                          v-on:change="SaveSharedLayer(layer)">
                 </td>
                 <td class="align-middle px-1 py-0">
--- a/ViewerPlugin/WebApplication/viewer.js	Tue Sep 01 16:46:07 2026 +0200
+++ b/ViewerPlugin/WebApplication/viewer.js	Tue Sep 01 17:19:47 2026 +0200
@@ -866,6 +866,15 @@
         return null;
       }
 
+      function GetSharedLayerById(id) {
+        for (var i = 0; i < app.sharedLayers.length; i++) {
+          if (app.sharedLayers[i].id == id) {
+            return app.sharedLayers[i];
+          }
+        }
+        return null;
+      }
+
       function GetLayerOfFeature(feature) {
         var layerId = feature.get('layer-id');
         console.assert(layerId !== null);
@@ -900,7 +909,7 @@
       this.drawSharedLayer = new ol.layer.Vector({
         source: this.drawSharedSource,
         style: function(feature) {
-          var entry = GetSharedLayerById(feature.get('shared-layer-id'));
+          var entry = GetSharedLayerById(feature.get('layer-id'));
           if (!entry || !entry.visible) {
             return null;
           }