From ba8a457096e848691291f5792c52fde45bd71a5f Mon Sep 17 00:00:00 2001 From: Mathias Date: Thu, 25 Jul 2024 14:10:54 +0200 Subject: [PATCH 01/41] tests: add example for annotation type Variant (#451) the api is subject to change of key names and data structure. but all the data provided in the example will be available later on. Co-authored-by: Paul Pestov <10750176+paulpestov@users.noreply.github.com> --- .../183a/latest/annotationCollection.json | 9 +- .../3r176/183a/latest/annotationPage.json | 411 +++++++++++++++++- 2 files changed, 418 insertions(+), 2 deletions(-) diff --git a/tests/mocks/ahiqar/annotations/ahiqar/arabic-karshuni/3r176/183a/latest/annotationCollection.json b/tests/mocks/ahiqar/annotations/ahiqar/arabic-karshuni/3r176/183a/latest/annotationCollection.json index 9d70ea77..ded7683f 100644 --- a/tests/mocks/ahiqar/annotations/ahiqar/arabic-karshuni/3r176/183a/latest/annotationCollection.json +++ b/tests/mocks/ahiqar/annotations/ahiqar/arabic-karshuni/3r176/183a/latest/annotationCollection.json @@ -1 +1,8 @@ -{"@context": "http://www.w3.org/ns/anno.jsonld", "id": "http://ahiqar.uni-goettingen.de/ns/annotations/annotationCollection/3r176/183a", "type": "AnnotationCollection", "label": "Ahiqar annotations for textgrid:3r176: Brit.Mus. cod. Add. 7209, page 183a", "x-creator": "Dr. Aly Elrefaei", "first": "http://localhost:8181/ahiqar/annotations/ahiqar/arabic-karshuni/3r176/183a/latest/annotationPage.json"} \ No newline at end of file +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://ahiqar.uni-goettingen.de/ns/annotations/annotationCollection/3r176/183a", + "type": "AnnotationCollection", + "label": "Ahiqar annotations for textgrid:3r176: Brit.Mus. cod. Add. 7209, page 183a", + "x-creator": "Dr. Aly Elrefaei", + "first": "http://localhost:8181/ahiqar/annotations/ahiqar/arabic-karshuni/3r176/183a/latest/annotationPage.json" +} diff --git a/tests/mocks/ahiqar/annotations/ahiqar/arabic-karshuni/3r176/183a/latest/annotationPage.json b/tests/mocks/ahiqar/annotations/ahiqar/arabic-karshuni/3r176/183a/latest/annotationPage.json index 18763a7b..8e0afe52 100644 --- a/tests/mocks/ahiqar/annotations/ahiqar/arabic-karshuni/3r176/183a/latest/annotationPage.json +++ b/tests/mocks/ahiqar/annotations/ahiqar/arabic-karshuni/3r176/183a/latest/annotationPage.json @@ -1 +1,410 @@ -{"@context": "http://www.w3.org/ns/anno.jsonld", "id": "http://ahiqar.uni-goettingen.de/ns/annotations/annotationPage/arabic-karshuni/3r176-183a", "type": "AnnotationPage", "partOf": {"id": "http://ahiqar.uni-goettingen.de/ns/annotations/annotationCollection/3r176", "label": "Ahiqar annotations for textgrid:3r176: Brit.Mus. cod. Add. 7209"}, "next": "http://localhost:8181/ahiqar/annotations/ahiqar/arabic-karshuni/3r176/183b/latest/annotationPage.json", "prev": "http://localhost:8181/ahiqar/annotations/ahiqar/arabic-karshuni/3r176/182b/latest/annotationPage.json", "items": [{"body": {"x-content-type": "Person", "type": "TextualBody", "format": "text/plain", "value": "\u0722\u0710\u0715\u0722"}, "target": [{"selector": {"type": "CssSelector", "value": "#MD12675N1l4l2l6l4l58l2"}, "language": "karshuni", "source": "http://localhost:8181/ahiqar/content/transliteration/3r14z-183a.html", "format": "text/xml"}], "type": "Annotation", "id": "http://ahiqar.uni-goettingen.de/ns/annotations/3r14z/annotation-MD12675N1l4l2l6l4l58l2"}, {"body": {"x-content-type": "Person", "type": "TextualBody", "format": "text/plain", "value": "\u0722\u0710\u0715\u0722"}, "target": [{"selector": {"type": "CssSelector", "value": "#MD12675N1l4l2l6l4l62l2"}, "language": "karshuni", "source": "http://localhost:8181/ahiqar/content/transliteration/3r14z-183a.html", "format": "text/xml"}], "type": "Annotation", "id": "http://ahiqar.uni-goettingen.de/ns/annotations/3r14z/annotation-MD12675N1l4l2l6l4l62l2"}, {"body": {"x-content-type": "Person", "type": "TextualBody", "format": "text/plain", "value": "\u0722\u0710\u0715\u0722"}, "target": [{"selector": {"type": "CssSelector", "value": "#MD12675N1l4l2l6l4l72l2"}, "language": "karshuni", "source": "http://localhost:8181/ahiqar/content/transliteration/3r14z-183a.html", "format": "text/xml"}], "type": "Annotation", "id": "http://ahiqar.uni-goettingen.de/ns/annotations/3r14z/annotation-MD12675N1l4l2l6l4l72l2"}, {"body": {"x-content-type": "Editorial Comment", "type": "TextualBody", "format": "text/plain", "value": "correction of faulty text. original: \u0718\u0710\u0720\u0726\u0720\u0710\u0723\u0726\u0717, corrected by the editor to: \u0718\u0710\u0720\u0726\u0720\u0723\u0726\u0717"}, "target": [{"selector": {"type": "CssSelector", "value": "#MD12675N1l4l2l6l4l76l2"}, "language": "karshuni", "source": "http://localhost:8181/ahiqar/content/transliteration/3r14z-183a.html", "format": "text/xml"}], "type": "Annotation", "id": "http://ahiqar.uni-goettingen.de/ns/annotations/3r14z/annotation-MD12675N1l4l2l6l4l76l2"}, {"body": {"x-content-type": "Person", "type": "TextualBody", "format": "text/plain", "value": "\u0723\u0722\u071a\u0710\u072a\u071d\u0712"}, "target": [{"selector": {"type": "CssSelector", "value": "#MD12675N1l4l2l6l4l78l2"}, "language": "karshuni", "source": "http://localhost:8181/ahiqar/content/transliteration/3r14z-183a.html", "format": "text/xml"}], "type": "Annotation", "id": "http://ahiqar.uni-goettingen.de/ns/annotations/3r14z/annotation-MD12675N1l4l2l6l4l78l2"}, {"body": {"x-content-type": "Person", "type": "TextualBody", "format": "text/plain", "value": "\u071a\u071d\u0729\u072a"}, "target": [{"selector": {"type": "CssSelector", "value": "#MD12675N1l4l2l6l4l80l2"}, "language": "karshuni", "source": "http://localhost:8181/ahiqar/content/transliteration/3r14z-183a.html", "format": "text/xml"}], "type": "Annotation", "id": "http://ahiqar.uni-goettingen.de/ns/annotations/3r14z/annotation-MD12675N1l4l2l6l4l80l2"}, {"body": {"x-content-type": "Person", "type": "TextualBody", "format": "text/plain", "value": "\u0646\u0627\u062f\u0646"}, "target": [{"selector": {"type": "CssSelector", "value": "#MD12675N1l4l2l4l4l60l2"}, "language": "ara", "source": "http://localhost:8181/ahiqar/content/transcription/3r14z-183a.html", "format": "text/xml"}], "type": "Annotation", "id": "http://ahiqar.uni-goettingen.de/ns/annotations/3r14z/annotation-MD12675N1l4l2l4l4l60l2"}, {"body": {"x-content-type": "Person", "type": "TextualBody", "format": "text/plain", "value": "\u0646\u0627\u062f\u0646"}, "target": [{"selector": {"type": "CssSelector", "value": "#MD12675N1l4l2l4l4l64l2"}, "language": "ara", "source": "http://localhost:8181/ahiqar/content/transcription/3r14z-183a.html", "format": "text/xml"}], "type": "Annotation", "id": "http://ahiqar.uni-goettingen.de/ns/annotations/3r14z/annotation-MD12675N1l4l2l4l4l64l2"}, {"body": {"x-content-type": "Person", "type": "TextualBody", "format": "text/plain", "value": "\u0646\u0627\u062f\u0646"}, "target": [{"selector": {"type": "CssSelector", "value": "#MD12675N1l4l2l4l4l74l2"}, "language": "ara", "source": "http://localhost:8181/ahiqar/content/transcription/3r14z-183a.html", "format": "text/xml"}], "type": "Annotation", "id": "http://ahiqar.uni-goettingen.de/ns/annotations/3r14z/annotation-MD12675N1l4l2l4l4l74l2"}, {"body": {"x-content-type": "Editorial Comment", "type": "TextualBody", "format": "text/plain", "value": "correction of faulty text. original: \u0648\u0627\u0644\u0641\u0644\u0627\u0633\u0641\u0647, corrected by the editor to: \u0648\u0627\u0644\u0641\u0644\u0633\u0641\u0629"}, "target": [{"selector": {"type": "CssSelector", "value": "#MD12675N1l4l2l4l4l78l2"}, "language": "ara", "source": "http://localhost:8181/ahiqar/content/transcription/3r14z-183a.html", "format": "text/xml"}], "type": "Annotation", "id": "http://ahiqar.uni-goettingen.de/ns/annotations/3r14z/annotation-MD12675N1l4l2l4l4l78l2"}, {"body": {"x-content-type": "Person", "type": "TextualBody", "format": "text/plain", "value": "\u0633\u0646\u062d\u0627\u0631\u064a\u0628"}, "target": [{"selector": {"type": "CssSelector", "value": "#MD12675N1l4l2l4l4l80l2"}, "language": "ara", "source": "http://localhost:8181/ahiqar/content/transcription/3r14z-183a.html", "format": "text/xml"}], "type": "Annotation", "id": "http://ahiqar.uni-goettingen.de/ns/annotations/3r14z/annotation-MD12675N1l4l2l4l4l80l2"}, {"body": {"x-content-type": "Person", "type": "TextualBody", "format": "text/plain", "value": "\u062d\u064a\u0642\u0631"}, "target": [{"selector": {"type": "CssSelector", "value": "#MD12675N1l4l2l4l4l82l2"}, "language": "ara", "source": "http://localhost:8181/ahiqar/content/transcription/3r14z-183a.html", "format": "text/xml"}], "type": "Annotation", "id": "http://ahiqar.uni-goettingen.de/ns/annotations/3r14z/annotation-MD12675N1l4l2l4l4l82l2"}, {"body": {"x-content-type": "Motif", "type": "TextualBody", "format": "text/plain", "value": "Loyal obligation (gods)"}, "target": [{"selector": {"type": "CssSelector", "value": "#t_Brit_Mus_Add_7209_MD17104N1l5l3l5l5l45l4_1,#t_Brit_Mus_Add_7209_MD17104N1l5l3l5l5l45l4_2,#t_Brit_Mus_Add_7209_MD17104N1l5l3l5l5l45l4_3,#t_Brit_Mus_Add_7209_MD17104N1l5l3l5l5l47l2_1,#t_Brit_Mus_Add_7209_MD17104N1l5l3l5l5l47l2_2,#t_Brit_Mus_Add_7209_MD17104N1l5l3l5l5l47l2_3"}, "source": "PLACEHOLDER \u2192 TARGET CONTENT HTML IRI"}], "type": "Annotation", "id": "http://ahiqar.uni-goettingen.de/ns/annotations/3r14z/annotation-MD12675N1l4l2l4l4l44l2"}]} \ No newline at end of file +{ + "@context": "http://www.w3.org/ns/anno.jsonld", + "id": "http://ahiqar.uni-goettingen.de/ns/annotations/annotationPage/arabic-karshuni/3r176-183a", + "type": "AnnotationPage", + "partOf": { + "id": "http://ahiqar.uni-goettingen.de/ns/annotations/annotationCollection/3r176", + "label": "Ahiqar annotations for textgrid:3r176: Brit.Mus. cod. Add. 7209" + }, + "next": "http://localhost:8181/ahiqar/annotations/ahiqar/arabic-karshuni/3r176/183b/latest/annotationPage.json", + "prev": "http://localhost:8181/ahiqar/annotations/ahiqar/arabic-karshuni/3r176/182b/latest/annotationPage.json", + "items": [ + { + "body": { + "x-content-type": "Person", + "type": "TextualBody", + "format": "text/plain", + "value": "\u0722\u0710\u0715\u0722" + }, + "target": [ + { + "selector": { + "type": "CssSelector", + "value": "#MD12675N1l4l2l6l4l58l2" + }, + "language": "karshuni", + "source": "http://localhost:8181/ahiqar/content/transliteration/3r14z-183a.html", + "format": "text/xml" + } + ], + "type": "Annotation", + "id": "http://ahiqar.uni-goettingen.de/ns/annotations/3r14z/annotation-MD12675N1l4l2l6l4l58l2" + }, + { + "body": { + "x-content-type": "Person", + "type": "TextualBody", + "format": "text/plain", + "value": "\u0722\u0710\u0715\u0722" + }, + "target": [ + { + "selector": { + "type": "CssSelector", + "value": "#MD12675N1l4l2l6l4l62l2" + }, + "language": "karshuni", + "source": "http://localhost:8181/ahiqar/content/transliteration/3r14z-183a.html", + "format": "text/xml" + } + ], + "type": "Annotation", + "id": "http://ahiqar.uni-goettingen.de/ns/annotations/3r14z/annotation-MD12675N1l4l2l6l4l62l2" + }, + { + "body": { + "x-content-type": "Person", + "type": "TextualBody", + "format": "text/plain", + "value": "\u0722\u0710\u0715\u0722" + }, + "target": [ + { + "selector": { + "type": "CssSelector", + "value": "#MD12675N1l4l2l6l4l72l2" + }, + "language": "karshuni", + "source": "http://localhost:8181/ahiqar/content/transliteration/3r14z-183a.html", + "format": "text/xml" + } + ], + "type": "Annotation", + "id": "http://ahiqar.uni-goettingen.de/ns/annotations/3r14z/annotation-MD12675N1l4l2l6l4l72l2" + }, + { + "body": { + "x-content-type": "Editorial Comment", + "type": "TextualBody", + "format": "text/plain", + "value": "correction of faulty text. original: \u0718\u0710\u0720\u0726\u0720\u0710\u0723\u0726\u0717, corrected by the editor to: \u0718\u0710\u0720\u0726\u0720\u0723\u0726\u0717" + }, + "target": [ + { + "selector": { + "type": "CssSelector", + "value": "#MD12675N1l4l2l6l4l76l2" + }, + "language": "karshuni", + "source": "http://localhost:8181/ahiqar/content/transliteration/3r14z-183a.html", + "format": "text/xml" + } + ], + "type": "Annotation", + "id": "http://ahiqar.uni-goettingen.de/ns/annotations/3r14z/annotation-MD12675N1l4l2l6l4l76l2" + }, + { + "body": { + "x-content-type": "Person", + "type": "TextualBody", + "format": "text/plain", + "value": "\u0723\u0722\u071a\u0710\u072a\u071d\u0712" + }, + "target": [ + { + "selector": { + "type": "CssSelector", + "value": "#MD12675N1l4l2l6l4l78l2" + }, + "language": "karshuni", + "source": "http://localhost:8181/ahiqar/content/transliteration/3r14z-183a.html", + "format": "text/xml" + } + ], + "type": "Annotation", + "id": "http://ahiqar.uni-goettingen.de/ns/annotations/3r14z/annotation-MD12675N1l4l2l6l4l78l2" + }, + { + "body": { + "x-content-type": "Person", + "type": "TextualBody", + "format": "text/plain", + "value": "\u071a\u071d\u0729\u072a" + }, + "target": [ + { + "selector": { + "type": "CssSelector", + "value": "#MD12675N1l4l2l6l4l80l2" + }, + "language": "karshuni", + "source": "http://localhost:8181/ahiqar/content/transliteration/3r14z-183a.html", + "format": "text/xml" + } + ], + "type": "Annotation", + "id": "http://ahiqar.uni-goettingen.de/ns/annotations/3r14z/annotation-MD12675N1l4l2l6l4l80l2" + }, + { + "body": { + "x-content-type": "Person", + "type": "TextualBody", + "format": "text/plain", + "value": "\u0646\u0627\u062f\u0646" + }, + "target": [ + { + "selector": { + "type": "CssSelector", + "value": "#MD12675N1l4l2l4l4l60l2" + }, + "language": "ara", + "source": "http://localhost:8181/ahiqar/content/transcription/3r14z-183a.html", + "format": "text/xml" + } + ], + "type": "Annotation", + "id": "http://ahiqar.uni-goettingen.de/ns/annotations/3r14z/annotation-MD12675N1l4l2l4l4l60l2" + }, + { + "body": { + "x-content-type": "Person", + "type": "TextualBody", + "format": "text/plain", + "value": "\u0646\u0627\u062f\u0646" + }, + "target": [ + { + "selector": { + "type": "CssSelector", + "value": "#MD12675N1l4l2l4l4l64l2" + }, + "language": "ara", + "source": "http://localhost:8181/ahiqar/content/transcription/3r14z-183a.html", + "format": "text/xml" + } + ], + "type": "Annotation", + "id": "http://ahiqar.uni-goettingen.de/ns/annotations/3r14z/annotation-MD12675N1l4l2l4l4l64l2" + }, + { + "body": { + "x-content-type": "Person", + "type": "TextualBody", + "format": "text/plain", + "value": "\u0646\u0627\u062f\u0646" + }, + "target": [ + { + "selector": { + "type": "CssSelector", + "value": "#MD12675N1l4l2l4l4l74l2" + }, + "language": "ara", + "source": "http://localhost:8181/ahiqar/content/transcription/3r14z-183a.html", + "format": "text/xml" + } + ], + "type": "Annotation", + "id": "http://ahiqar.uni-goettingen.de/ns/annotations/3r14z/annotation-MD12675N1l4l2l4l4l74l2" + }, + { + "body": { + "x-content-type": "Editorial Comment", + "type": "TextualBody", + "format": "text/plain", + "value": "correction of faulty text. original: \u0648\u0627\u0644\u0641\u0644\u0627\u0633\u0641\u0647, corrected by the editor to: \u0648\u0627\u0644\u0641\u0644\u0633\u0641\u0629" + }, + "target": [ + { + "selector": { + "type": "CssSelector", + "value": "#MD12675N1l4l2l4l4l78l2" + }, + "language": "ara", + "source": "http://localhost:8181/ahiqar/content/transcription/3r14z-183a.html", + "format": "text/xml" + } + ], + "type": "Annotation", + "id": "http://ahiqar.uni-goettingen.de/ns/annotations/3r14z/annotation-MD12675N1l4l2l4l4l78l2" + }, + { + "body": { + "x-content-type": "Person", + "type": "TextualBody", + "format": "text/plain", + "value": "\u0633\u0646\u062d\u0627\u0631\u064a\u0628" + }, + "target": [ + { + "selector": { + "type": "CssSelector", + "value": "#MD12675N1l4l2l4l4l80l2" + }, + "language": "ara", + "source": "http://localhost:8181/ahiqar/content/transcription/3r14z-183a.html", + "format": "text/xml" + } + ], + "type": "Annotation", + "id": "http://ahiqar.uni-goettingen.de/ns/annotations/3r14z/annotation-MD12675N1l4l2l4l4l80l2" + }, + { + "body": { + "x-content-type": "Person", + "type": "TextualBody", + "format": "text/plain", + "value": "\u062d\u064a\u0642\u0631" + }, + "target": [ + { + "selector": { + "type": "CssSelector", + "value": "#MD12675N1l4l2l4l4l82l2" + }, + "language": "ara", + "source": "http://localhost:8181/ahiqar/content/transcription/3r14z-183a.html", + "format": "text/xml" + } + ], + "type": "Annotation", + "id": "http://ahiqar.uni-goettingen.de/ns/annotations/3r14z/annotation-MD12675N1l4l2l4l4l82l2" + }, + { + "body": { + "x-content-type": "Motif", + "type": "TextualBody", + "format": "text/plain", + "value": "Loyal obligation (gods)" + }, + "target": [ + { + "selector": { + "type": "CssSelector", + "value": "#t_Brit_Mus_Add_7209_MD17104N1l5l3l5l5l45l4_1,#t_Brit_Mus_Add_7209_MD17104N1l5l3l5l5l45l4_2,#t_Brit_Mus_Add_7209_MD17104N1l5l3l5l5l45l4_3,#t_Brit_Mus_Add_7209_MD17104N1l5l3l5l5l47l2_1,#t_Brit_Mus_Add_7209_MD17104N1l5l3l5l5l47l2_2,#t_Brit_Mus_Add_7209_MD17104N1l5l3l5l5l47l2_3" + }, + "source": "PLACEHOLDER \u2192 TARGET CONTENT HTML IRI" + } + ], + "type": "Annotation", + "id": "http://ahiqar.uni-goettingen.de/ns/annotations/3r14z/annotation-MD12675N1l4l2l4l4l44l2" + }, + { + "body": { + "x-content-type": "Variant", + "type": "TextualBody", + "format": "text/plain", + "value": [ + { + "witness": "Cod. Arab. 236", + "entry": "omisit" + }, + { + "witness": "DFM 614", + "entry": "omisit" + }, + { + "witness": "Ming. syr. 258", + "entry": "اللبان" + }, + { + "witness": "Sach. 339", + "entry": "البان" + } + ] + }, + "target": [ + { + "selector": { + "type": "CssSelector", + "value": "#t_Brit_Mus_Add_7209_MD17104N1l5l3l7l5l41l2_3" + }, + "source": "PLACEHOLDER \u2192 TARGET CONTENT HTML IRI" + } + ], + "type": "Annotation", + "id": "http://ahikar.uni-goettingen.de/ns/annotations/3r14z/annotation-variants-t_Brit_Mus_Add_7209_N1l5l3l5l5l29l4_w_1" + }, + { + "body": { + "x-content-type": "Variant", + "type": "TextualBody", + "format": "text/plain", + "value": [ + { + "witness": "Cod. Arab. 236", + "entry": "omisit" + }, + { + "witness": "DFM 614", + "entry": "omisit" + }, + { + "witness": "Ming. syr. 258", + "entry": "والقرفه" + }, + { + "witness": "Sach. 339", + "entry": "omisit" + } + ] + }, + "target": [ + { + "selector": { + "type": "CssSelector", + "value": "#t_Brit_Mus_Add_7209_MD17104N1l5l3l7l5l43l2_2" + }, + "source": "PLACEHOLDER \u2192 TARGET CONTENT HTML IRI" + } + ], + "type": "Annotation", + "id": "http://ahikar.uni-goettingen.de/ns/annotations/3r14z/annotation-variants-t_Brit_Mus_Add_7209_N1l5l3l5l5l29l4_w_2" + }, + { + "body": { + "x-content-type": "Variant", + "type": "TextualBody", + "format": "text/plain", + "value": [ + { + "witness": "Cod. Arab. 236", + "entry": "omisit" + }, + { + "witness": "DFM 614", + "entry": "omisit" + }, + { + "witness": "Ming. syr. 258", + "entry": "والكمكام" + }, + { + "witness": "Sach. 339", + "entry": "والكمكام" + } + ] + }, + "target": [ + { + "selector": { + "type": "CssSelector", + "value": "#t_Brit_Mus_Add_7209_MD17104N1l5l3l7l5l43l2_3" + }, + "source": "PLACEHOLDER \u2192 TARGET CONTENT HTML IRI" + } + ], + "type": "Annotation", + "id": "http://ahikar.uni-goettingen.de/ns/annotations/3r14z/annotation-variants-t_Brit_Mus_Add_7209_N1l5l3l5l5l29l4_w_3" + } + ], + "refs": [ + { + "idno": "Cod. Arab. 236", + "manifest": "https://api.dev.ahiqar.sub.uni-goettingen.de/textapi/ahiqar/arabic-karshuni/3r177/manifest.json" + }, + { + "idno": "DFM 614", + "manifest": "https://api.dev.ahiqar.sub.uni-goettingen.de/textapi/ahiqar/arabic-karshuni/3rbm9/manifest.json" + }, + { + "idno": "Ming. syr. 258", + "manifest": "https://api.dev.ahiqar.sub.uni-goettingen.de/textapi/ahiqar/arabic-karshuni/3r17c/manifest.json" + }, + { + "idno": "Sach. 339", + "manifest": "https://api.dev.ahiqar.sub.uni-goettingen.de/textapi/ahiqar/arabic-karshuni/3r179/manifest.json" + } + ] +} From 847f1e56961f59b8add813b7748c13c08623919e Mon Sep 17 00:00:00 2001 From: malkja Date: Thu, 25 Jul 2024 14:15:26 +0200 Subject: [PATCH 02/41] chore: update ci yml --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9c548dac..b701ebea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ on: push: branches: [ "develop" ] pull_request: - branches: [ "develop", "main" ] + branches: [ "develop", "main", "main-variants" ] jobs: build: runs-on: ubuntu-latest From 671ae74b46c5b65a70e8bd005013c9495cbdeab5 Mon Sep 17 00:00:00 2001 From: orlinmalkja <54899269+orlinmalkja@users.noreply.github.com> Date: Fri, 26 Jul 2024 11:27:51 +0200 Subject: [PATCH 03/41] feat: display a list of variants objects in a new Variant view * feat: variants list * fix: updated the local ahiqar example with and added the variant component * refactor: remove the 6th component of variants from panels.js --------- Co-authored-by: malkja --- ...r-arabic-karshuni-with-variants-local.html | 177 ++++++++++++++++++ .../annotations/AnnotationVariantItem.vue | 27 +++ .../annotations/AnnotationsList.vue | 30 ++- src/utils/panels.js | 2 +- 4 files changed, 230 insertions(+), 6 deletions(-) create mode 100644 examples/ahiqar-arabic-karshuni-with-variants-local.html create mode 100644 src/components/annotations/AnnotationVariantItem.vue diff --git a/examples/ahiqar-arabic-karshuni-with-variants-local.html b/examples/ahiqar-arabic-karshuni-with-variants-local.html new file mode 100644 index 00000000..2f1a7241 --- /dev/null +++ b/examples/ahiqar-arabic-karshuni-with-variants-local.html @@ -0,0 +1,177 @@ + + +TIDO + + + + + + + + + + + + +
+ + + + diff --git a/src/components/annotations/AnnotationVariantItem.vue b/src/components/annotations/AnnotationVariantItem.vue new file mode 100644 index 00000000..5cdfa992 --- /dev/null +++ b/src/components/annotations/AnnotationVariantItem.vue @@ -0,0 +1,27 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/components/annotations/AnnotationsList.vue b/src/components/annotations/AnnotationsList.vue index f117f4d2..e121347c 100644 --- a/src/components/annotations/AnnotationsList.vue +++ b/src/components/annotations/AnnotationsList.vue @@ -12,14 +12,22 @@ ]" @click="isText(annotation) ? ()=>{} : toggle(annotation)" > -
- + + /> + +
+ +
+ +
+ - - + + @@ -27,6 +35,7 @@ + + + \ No newline at end of file diff --git a/src/utils/panels.js b/src/utils/panels.js index b399bb64..283170ad 100644 --- a/src/utils/panels.js +++ b/src/utils/panels.js @@ -18,7 +18,7 @@ const components = { 5: { component: 'AnnotationsView', label: 'annotations', - }, + } }; export const findComponent = (id) => ({ From 031d827e8b811cd060945eeb12f15e6d042429f0 Mon Sep 17 00:00:00 2001 From: orlinmalkja <54899269+orlinmalkja@users.noreply.github.com> Date: Thu, 8 Aug 2024 14:46:39 +0200 Subject: [PATCH 04/41] feat: add variant item selection/deselection (#459) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: style better the "chip" * feat: add border color for each of the different ‘chips’ * feat: split up the variant items * feat: select only one variant item * feat: select a variant item and toggle accordingly * feat: add the 'witnesses chips' in the text * feat: remove 'witnesses chips' in other tabs we select a few variant items -> chips are shown in the text. When we switch the annotation tab i.e to 'Motifs' then the chips should not be shown in the text anymore * feat: remove the witnesses in the text for each variant item we deselect, we remove the 'witness chip' from the text * chore: change the icon names in the config * style: fix linting * refactor: remove 'isText' from AnnotationVariantItem * refactor: use watch to initialize 'variantItemsSelection' * refactor: access 'target' from the text Panel html element * refactor: keep the same text of witness served from the backend * style: minor * refactor: access the witnesses span from the text panel html el --------- Co-authored-by: malkja --- ...r-arabic-karshuni-with-variants-local.html | 40 +++--- .../annotations/AnnotationVariantItem.vue | 116 +++++++++++++++++- .../annotations/AnnotationsList.vue | 48 +++++--- src/components/panels/Panel.vue | 6 + src/utils/annotations.js | 67 ++++++++++ src/utils/color.js | 11 ++ 6 files changed, 246 insertions(+), 42 deletions(-) diff --git a/examples/ahiqar-arabic-karshuni-with-variants-local.html b/examples/ahiqar-arabic-karshuni-with-variants-local.html index 2f1a7241..fb2fc78d 100644 --- a/examples/ahiqar-arabic-karshuni-with-variants-local.html +++ b/examples/ahiqar-arabic-karshuni-with-variants-local.html @@ -1,13 +1,13 @@ -TIDO + + TIDO + - + - - - + - - + +
- + + + }); + diff --git a/src/components/annotations/AnnotationVariantItem.vue b/src/components/annotations/AnnotationVariantItem.vue index 5cdfa992..5288e8e1 100644 --- a/src/components/annotations/AnnotationVariantItem.vue +++ b/src/components/annotations/AnnotationVariantItem.vue @@ -1,23 +1,129 @@ diff --git a/src/components/annotations/AnnotationsList.vue b/src/components/annotations/AnnotationsList.vue index e121347c..50bfe3b7 100644 --- a/src/components/annotations/AnnotationsList.vue +++ b/src/components/annotations/AnnotationsList.vue @@ -3,35 +3,40 @@
- -
- + - -
+ /> + +
-
- -
+
+ +
- + + + \ No newline at end of file diff --git a/src/components/panels/Panel.vue b/src/components/panels/Panel.vue index 72da4e7d..6a4a1626 100644 --- a/src/components/panels/Panel.vue +++ b/src/components/panels/Panel.vue @@ -142,6 +142,8 @@ import PanelImageAction from '@/components/panels/actions/PanelImageAction.vue'; import LoadingSpinner from '@/components/LoadingSpinner.vue'; import MessageBox from '@/components/MessageBox.vue'; import { findComponent } from '@/utils/panels'; +import * as AnnotationUtils from '@/utils/annotations' + // NOTE: Using `setup()` rather than the recommended ` diff --git a/src/components/annotations/AnnotationsList.vue b/src/components/annotations/AnnotationsList.vue index 50bfe3b7..3d248dd5 100644 --- a/src/components/annotations/AnnotationsList.vue +++ b/src/components/annotations/AnnotationsList.vue @@ -1,11 +1,10 @@ @@ -40,24 +27,24 @@ - - - - \ No newline at end of file diff --git a/src/components/annotations/AnnotationsView.vue b/src/components/annotations/AnnotationsView.vue index 777e3ba2..e9bd1e56 100644 --- a/src/components/annotations/AnnotationsView.vue +++ b/src/components/annotations/AnnotationsView.vue @@ -1,14 +1,19 @@ diff --git a/src/components/base/BaseButton.vue b/src/components/base/BaseButton.vue index abb040ec..35ef7419 100644 --- a/src/components/base/BaseButton.vue +++ b/src/components/base/BaseButton.vue @@ -60,13 +60,13 @@ classes['t-py-2 t-px-5'] = props.size === 'normal' && !_icon; classes['t-py-1.5 t-ps-2 t-pe-3'] = props.size === 'small' && !!(_icon) && _iconPosition === 'left'; classes['t-py-1.5 t-ps-3 t-pe-2'] = props.size === 'small' && !!(_icon) && _iconPosition === 'right'; classes['t-p-2'] = props.size === 'small' && isIconOnly; -classes['t-py-1.5 px-3'] = props.size === 'small' && !_icon; +classes['t-py-1.5 t-px-3'] = props.size === 'small' && !_icon; // Paddings Size Tiny classes['t-py-1 t-ps-1 t-pe-1.5'] = props.size === 'tiny' && !!(_icon) && _iconPosition === 'left'; classes['t-py-1 t-ps-1.5 t-pe-1'] = props.size === 'tiny' && !!(_icon) && _iconPosition === 'right'; classes['t-p-0.5'] = props.size === 'tiny' && isIconOnly; -classes['t-py-1.5 px-2'] = props.size === 'tiny' && !_icon; +classes['t-py-1.5 t-px-2'] = props.size === 'tiny' && !_icon; // Font Size Normal classes['t-text-sm t-leading-2'] = props.size === 'normal' && !isIconOnly; diff --git a/src/i18n/de/index.js b/src/i18n/de/index.js index a224193b..5d5f4796 100644 --- a/src/i18n/de/index.js +++ b/src/i18n/de/index.js @@ -110,4 +110,6 @@ export default { not_found: 'Nicht gefunden', unauthorized: 'Nicht autorisiert', unknown_error: 'Unbekannter Fehler', + details: 'Details' + }; diff --git a/src/i18n/en/index.js b/src/i18n/en/index.js index a615cae4..98131886 100644 --- a/src/i18n/en/index.js +++ b/src/i18n/en/index.js @@ -114,4 +114,5 @@ export default { not_found: 'Not found', unauthorized: 'Unauthorized', unknown_error: 'Unknown Error', + details: 'Details' }; diff --git a/src/stores/annotations.ts b/src/stores/annotations.ts index bc99abf9..ffa30ae2 100644 --- a/src/stores/annotations.ts +++ b/src/stores/annotations.ts @@ -1,368 +1,367 @@ -import { defineStore } from 'pinia' -import { - computed, ref, - } from 'vue'; +import {defineStore} from 'pinia' +import { ref } from 'vue'; import * as AnnotationUtils from '@/utils/annotations'; -import { request } from '@/utils/http'; +import {request} from '@/utils/http'; import * as Utils from '@/utils'; -import { scrollIntoViewIfNeeded } from '@/utils'; -import { useConfigStore} from '@/stores/config'; +import {scrollIntoViewIfNeeded} from '@/utils'; +import {useConfigStore} from '@/stores/config'; export const useAnnotationsStore = defineStore('annotations', () => { - const activeTab = ref('') - const activeAnnotations = ref({}) - const activeAnnotSelectVariantItems = ref({}) - const variantItemsColors = ref({}) - const annotations = ref(null) - const filteredAnnotations = ref([]) - const isLoading = ref(false); + const activeTab = ref('') + const activeAnnotations = ref({}) + const variantItemsColors = ref({}) + const annotations = ref(null) + const filteredAnnotations = ref([]) + const isLoading = ref(false); + + const setActiveAnnotations = (annotations: ActiveAnnotation) => { + activeAnnotations.value = annotations + } + + function setAnnotations(payload: Annotation[]) { + annotations.value = payload + } + + function updateAnnotationLoading(payload: boolean) { + isLoading.value = payload; + } + + function setFilteredAnnotations(payload: Annotation[]) { + filteredAnnotations.value = payload + } + + function setVariantItemsColors(payload) { + variantItemsColors.value = payload + } + + const addActiveAnnotation = (id: string) => { + const annotationStore = useAnnotationsStore() + const newActiveAnnotation: Annotation = annotations.value.find((annotation) => annotation.id === id); + if (!newActiveAnnotation || activeAnnotations.value[id]) { + return; + } + + const activeAnnotationsList: ActiveAnnotation = {...activeAnnotations.value}; + + activeAnnotationsList[id] = newActiveAnnotation; + + annotationStore.setActiveAnnotations(activeAnnotationsList) + const selector: string = Utils.generateTargetSelector(newActiveAnnotation); + const elements: Array = (selector) ? [...document.querySelectorAll(selector)] : []; + + if (elements.length > 0) { + // If the annotation target exists in the text panel + const target: HTMLElement = elements[0]; + + if (AnnotationUtils.isVariant(newActiveAnnotation)) { + if (AnnotationUtils.getCurrentLevel(target) <= 0) { + Utils.highlightTargets(selector, {operation: 'INC'}); + } + addVariantAnnotation(target, newActiveAnnotation); + } else { + Utils.highlightTargets(selector, {operation: 'INC'}); + addSimpleAnnotation(target, newActiveAnnotation); + } - const isAllAnnotationSelected = computed((total) => Object.keys(activeAnnotations.value).length === total) - const isNoAnnotationSelected = computed(() => !Object.keys(activeAnnotations.value).length) - - const setActiveAnnotations = (annotations: ActiveAnnotation) => { - activeAnnotations.value = annotations + scrollIntoViewIfNeeded(target, target.closest('.panel-body')); } + }; + + const addSimpleAnnotation = (targetElement: HTMLElement, annotation: Annotation) => { + const configStore = useConfigStore() + const iconName: string = configStore.getIconByType(annotation.body['x-content-type']); + Utils.addIcon(targetElement, annotation, iconName); + } + + const addVariantAnnotation = (targetElement: HTMLElement, annotation: Annotation) => { + const witness = annotation.body.value.witness + Utils.addWitness(targetElement, witness, variantItemsColors.value[witness]) + } + + const selectFilteredAnnotations = (types: AnnotationType[]): boolean | void => { + const configStore = useConfigStore() + const activeContentType: string = configStore.activeContentType + let filteredAnnotations: Annotation[] = []; + + if (annotations.value !== null) { + filteredAnnotations = types.length === 0 ? annotations.value : annotations.value.filter( + (annotation) => { + const type: AnnotationType = types.find(({name}) => name === annotation.body['x-content-type']); + // First we check if annotation fits to the current view + if (!type) return false; + + // Next we check if annotation should always be displayed on the current content tab + if (type?.displayWhen && type?.displayWhen === activeContentType) return true; + + // If the display is not dependent on displayWhen then we check if annotation's target exists in the content + const selector: string = AnnotationUtils.generateTargetSelector(annotation); + if (selector) { + const el: HTMLElement = document.querySelector(selector); + if (el) { + return true; + } + } - function setAnnotations(payload: Annotation[]) { - annotations.value = payload + return false; + }, + ); } - - function updateAnnotationLoading(payload: boolean) { - isLoading.value = payload; + setFilteredAnnotations(filteredAnnotations) + }; + + const addHighlightAttributesToText = (dom) => { + if (annotations.value !== null) { + annotations.value.forEach((annotation) => { + const {id} = annotation; + const selector = Utils.generateTargetSelector(annotation); + if (selector) { + Utils.addHighlightToElements(selector, dom, id); + } + }); } + }; - function setFilteredAnnotations(payload: Annotation[]) { - filteredAnnotations.value = payload - } + const annotationLoaded = (annotations) => { + setAnnotations(annotations) + updateAnnotationLoading(false) + }; - function setVariantItemsColors(payload) { - variantItemsColors.value = payload - } - function setActiveAnnotSelectVariantItems(payload) { - activeAnnotSelectVariantItems.value = payload - } + const removeActiveAnnotation = (id) => { + const annotationStore = useAnnotationsStore() + const removeAnnotation = activeAnnotations.value[id]; - function updateActiveAnnotSelectVariantItems(id, payload) { - activeAnnotSelectVariantItems.value[id] = payload + if (!removeAnnotation) { + return; } - const addActiveAnnotation = (id: string) => { - const annotationStore = useAnnotationsStore() - const configStore = useConfigStore() - const newActiveAnnotation: Annotation = annotations.value.find((annotation) => annotation.id === id); - if (!newActiveAnnotation || activeAnnotations.value[id]) { - return; - } - - const iconName: string = configStore.getIconByType(newActiveAnnotation.body['x-content-type']); - - const activeAnnotationsList: ActiveAnnotation = { ...activeAnnotations.value }; - - activeAnnotationsList[id] = newActiveAnnotation; - - annotationStore.setActiveAnnotations(activeAnnotationsList) - - const selector: string = Utils.generateTargetSelector(newActiveAnnotation); - const elements: Array = (selector) ? [...document.querySelectorAll(selector)] : []; - Utils.highlightTargets(selector, { operation: 'INC' }); - - if (elements.length > 0) { - const target: HTMLElement = elements[0]; - Utils.addIcon(target, newActiveAnnotation, iconName); - scrollIntoViewIfNeeded(target, target.closest('.panel-body')); - } - }; - - - const selectFilteredAnnotations = (types: AnnotationType[]): boolean | void => { - const configStore = useConfigStore() - const activeContentType: string = configStore.activeContentType - let filteredAnnotations: Annotation[] = []; - - if (annotations !== null) { - filteredAnnotations = types.length === 0 ? annotations.value : annotations.value.filter( - (annotation) => { - const type: AnnotationType = types.find(({ name }) => name === annotation.body['x-content-type']); - // First we check if annotation fits to the current view - if (!type) return false; - - // Next we check if annotation should always be displayed on the current content tab - if (type?.displayWhen && type?.displayWhen === activeContentType) return true; - - // If the display is not dependent on displayWhen then we check if annotation's target exists in the content - const selector: string = AnnotationUtils.generateTargetSelector(annotation); - if (selector) { - const el: HTMLElement = document.querySelector(selector); - if (el) { - return true; - } - } - - return false; - }, - ); - } - setFilteredAnnotations(filteredAnnotations) - }; + // If removed active annotation is variant - then set all the variant items selection to false for this annotation + const activeAnnotationsList = {...activeAnnotations.value}; + delete activeAnnotationsList[id]; + annotationStore.setActiveAnnotations(activeAnnotationsList) - const addHighlightAttributesToText = (dom) => { - if (annotations.value !== null) { - annotations.value.forEach((annotation) => { - const { id } = annotation; - const selector = Utils.generateTargetSelector(annotation); - if (selector) { - Utils.addHighlightToElements(selector, dom, id); - } - }); - } - }; - - const annotationLoaded = (annotations) => { - setAnnotations(annotations) - updateAnnotationLoading(false) - }; - - - const removeActiveAnnotation = (id) => { - const annotationStore = useAnnotationsStore() - const removeAnnotation = activeAnnotations.value[id]; - - if (!removeAnnotation) { - return; + const selector = AnnotationUtils.generateTargetSelector(removeAnnotation); + if (selector) { + if (AnnotationUtils.isVariant(removeAnnotation)) { + if (AnnotationUtils.getCurrentLevel(document.querySelector(selector)) > 0) { + AnnotationUtils.highlightTargets(selector, {operation: 'DEC'}); } + removeVariantAnnotation(selector, removeAnnotation); + } else { + AnnotationUtils.highlightTargets(selector, {operation: 'DEC'}); + removeSimpleAnnotation(removeAnnotation); + } + } + }; + + const removeSimpleAnnotation = (annotation: Annotation) => { + AnnotationUtils.removeIcon(annotation); + } - // If removed active annotation is variant - then set all the variant items selection to false for this annotation + const removeVariantAnnotation = (selector: string, annotation: Annotation) => { + AnnotationUtils.removeWitness(selector, annotation.body.value.witness); + } - - const activeAnnotationsList = { ...activeAnnotations.value }; - - delete activeAnnotationsList[id]; - annotationStore.setActiveAnnotations(activeAnnotationsList) - - const selector = AnnotationUtils.generateTargetSelector(removeAnnotation); + const resetAnnotations = () => { + if (annotations.value !== null) { + annotations.value.forEach((annotation) => { + const selector = AnnotationUtils.generateTargetSelector(annotation); if (selector) { - AnnotationUtils.highlightTargets(selector, { operation: 'DEC' }); - AnnotationUtils.removeIcon(removeAnnotation); + AnnotationUtils.highlightTargets(selector, {level: -1}); + AnnotationUtils.removeIcon(annotation); } - }; - - - - const resetAnnotations = () => { - - if (annotations.value !== null) { - annotations.value.forEach((annotation) => { - const selector = AnnotationUtils.generateTargetSelector(annotation); - if (selector) { - AnnotationUtils.highlightTargets(selector, { level: -1 }); - AnnotationUtils.removeIcon(annotation); - } - }); - } - setActiveAnnotations({}) - }; + }); + } + setActiveAnnotations({}) + }; + const initAnnotations = async (url) => { + try { + const annotations = await request(url); + if (!annotations.first) { + annotationLoaded([]) + return; + } - const initAnnotations = async (url) => { - try { - const annotations = await request(url); - - if (!annotations.first) { - annotationLoaded([]) - return; - } - - const current = await request(annotations.first); - - if (Array.isArray(current.items)) { - annotationLoaded(current.items) - } - } catch (err) { - annotationLoaded([]) - } - }; - - - - const addHighlightHoverListeners = () => { - const annotationElements = Array.from(document.querySelectorAll('[data-annotation]')); - - const tooltipEl = null; - const configStore = useConfigStore() - - // Annotations can be nested, so we filter out all outer elements from this selection and - // iterate over the deepest elements - annotationElements.forEach((el) => { - el.addEventListener( - 'mouseenter', - ({ clientX: x, clientY: y }) => { - let elementFromPoint = document.elementFromPoint(x, y); - - if (!elementFromPoint.hasAttribute('data-annotation')) { - elementFromPoint = null; - } - - const currentElement = elementFromPoint ?? el; - - const annotationTooltipModels = filteredAnnotations.value.reduce((acc, curr) => { - const { id } = curr; - const name = configStore.getIconByType(curr.body['x-content-type']) - acc[id] = { - value: curr.body.value, - name, - }; - return acc; - }, {}); - - const currentAnnotations = Utils.getValuesFromAttribute(currentElement, 'data-annotation-ids'); - const closestAnnotationId = currentAnnotations[currentAnnotations.length - 1]; - const closestAnnotationTooltipModel = annotationTooltipModels[closestAnnotationId]; - let annotationIds = discoverParentAnnotationIds(currentElement); - annotationIds = discoverChildAnnotationIds(currentElement, annotationIds); - - const otherAnnotationTooltipModels = Object.keys(annotationIds) - .map((id) => annotationTooltipModels[id]) - .filter((m) => m); - - AnnotationUtils.createOrUpdateTooltip.bind( - this, - currentElement, - { closest: closestAnnotationTooltipModel, other: otherAnnotationTooltipModels }, - document.getElementById('text-content'), - )(); - }, - false, - ); - el.addEventListener('mouseout', () => tooltipEl.remove(), false); - }); - }; - - - - const addHighlightClickListeners = () => { - const textEl = document.querySelector('#text-content>div>*'); - - if (!textEl) return; - - textEl.addEventListener('click', ({ target }) => { - // The click event handler works like this: - // When clicking on the text we pick the whole part of the text which belongs to the highest parent annotation. - // Since the annotations can be nested we avoid handling each of them separately - // and select/deselect the whole cluster at once. - // The actual click target decides whether it should be a selection or a deselection. - - // First we make sure to have a valid target. - // Although we receive a target from the event it can be a regular HTML element within the annotation. - // So we try to find it's nearest parent element that is marked as annotation element. - if (!target.dataset.annotation) { - target = getNearestParentAnnotation(target); - } - - if (!target) { - return; + const current = await request(annotations.first); + + if (Array.isArray(current.items)) { + annotationLoaded(current.items) + } + } catch (err) { + annotationLoaded([]) + } + }; + + const addHighlightHoverListeners = () => { + const annotationElements = Array.from(document.querySelectorAll('[data-annotation]')); + + const tooltipEl = null; + const configStore = useConfigStore() + + // Annotations can be nested, so we filter out all outer elements from this selection and + // iterate over the deepest elements + annotationElements.forEach((el) => { + el.addEventListener( + 'mouseenter', + ({clientX: x, clientY: y}) => { + let elementFromPoint = document.elementFromPoint(x, y); + + if (!elementFromPoint.hasAttribute('data-annotation')) { + elementFromPoint = null; } - - // Next we look up which annotations need to be selected - let annotationIds = {}; - - Utils.getValuesFromAttribute(target, 'data-annotation-ids').forEach((value) => annotationIds[value] = true); - annotationIds = discoverParentAnnotationIds(target, annotationIds); - annotationIds = discoverChildAnnotationIds(target, annotationIds); - - // We check the highlighting level to determine whether to select or deselect. - // TODO: it might be better to check the activeAnnotations instead - const targetIsSelected = parseInt(target.getAttribute('data-annotation-level'), 10) > 0; - - Object.keys(annotationIds).forEach((id) => { - // We need to check here if the right annotations panel tab is active - // a.k.a. it exists in the current filteredAnnotations - const annotation = filteredAnnotations.value.find((filtered) => filtered.id === id); - const selector = annotation.target[0].selector.value - if (annotation) { - if (targetIsSelected) { - removeActiveAnnotation(id) - if (AnnotationUtils.isVariant(annotation)) { - // we need to know which witnesses belong to this annotation AND are selected - so that we can remove the witnesses chips from the text - const witnessesHtml = AnnotationUtils.getWitnessesHtmlEl(selector) - const witnessesList = AnnotationUtils.getWitnessesList(witnessesHtml) - // remove the 'witnesses chips' which are selected - AnnotationUtils.removeWitnessesChipsWhenDeselectText(witnessesList, selector) - delete activeAnnotSelectVariantItems.value[annotation.id] - } - } else { - addActiveAnnotation(id) - if(AnnotationUtils.isVariant(annotation)) { - // if annotation is variant - additionally set the variant items selection to true - const variantItemsSelect = AnnotationUtils.initVariantItemsSelection(annotation, true) - - activeAnnotSelectVariantItems.value[annotation.id] = [activeAnnotations.value[annotation.id], variantItemsSelect] - // add all the 'witnesses chips' for this annotation variant - AnnotationUtils.addWitnessesChipsWhenSelectText(variantItemsSelect, selector, variantItemsColors.value) - } - } - } - }); + + const currentElement = elementFromPoint ?? el; + + const annotationTooltipModels = filteredAnnotations.value.reduce((acc, curr) => { + const {id} = curr; + const name = configStore.getIconByType(curr.body['x-content-type']) + acc[id] = { + value: curr.body.value, + name, + }; + return acc; + }, {}); + + const currentAnnotations = Utils.getValuesFromAttribute(currentElement, 'data-annotation-ids'); + const closestAnnotationId = currentAnnotations[currentAnnotations.length - 1]; + const closestAnnotationTooltipModel = annotationTooltipModels[closestAnnotationId]; + let annotationIds = discoverParentAnnotationIds(currentElement); + annotationIds = discoverChildAnnotationIds(currentElement, annotationIds); + + const otherAnnotationTooltipModels = Object.keys(annotationIds) + .map((id) => annotationTooltipModels[id]) + .filter((m) => m); + + AnnotationUtils.createOrUpdateTooltip.bind( + this, + currentElement, + {closest: closestAnnotationTooltipModel, other: otherAnnotationTooltipModels}, + document.getElementById('text-content'), + )(); + }, + false, + ); + el.addEventListener('mouseout', () => tooltipEl.remove(), false); }); - }; + }; + const addHighlightClickListeners = () => { + const textEl = document.querySelector('#text-content>div>*'); - function getNearestParentAnnotation(element) { - const parent = element.parentElement; - - if (!parent) return null; - - if (parent.dataset?.annotation) { - return parent; - } - return getNearestParentAnnotation(parent); - } + if (!textEl) return; - const selectAll = () => { - filteredAnnotations.value.forEach(({ id }) => !activeAnnotations.value[id] && addActiveAnnotation(id)); - }; + textEl.addEventListener('click', ({target}) => { + // The click event handler works like this: + // When clicking on the text we pick the whole part of the text which belongs to the highest parent annotation. + // Since the annotations can be nested we avoid handling each of them separately + // and select/deselect the whole cluster at once. + // The actual click target decides whether it should be a selection or a deselection. - const selectNone = () => { - filteredAnnotations.value.forEach(({ id }) => activeAnnotations.value[id] && removeActiveAnnotation(id)); - }; + // First we make sure to have a valid target. + // Although we receive a target from the event it can be a regular HTML element within the annotation. + // So we try to find it's nearest parent element that is marked as annotation element. + if (!target.dataset.annotation) { + target = getNearestParentAnnotation(target); + } - function discoverParentAnnotationIds(el, annotationIds = {}) { - const parent = el.parentElement; - if (parent && parent.id !== 'text-content') { - Utils.getValuesFromAttribute(parent, 'data-annotation-ids').forEach((value) => annotationIds[value] = true); - return discoverParentAnnotationIds(parent, annotationIds); + if (!target) { + return; } - return annotationIds; - } - function discoverChildAnnotationIds(el, annotationIds = {}) { - const { children } = el; - - [...children].forEach((child) => { - if (child.dataset.annotation) { - Utils.getValuesFromAttribute(child, 'data-annotation-ids').forEach((value) => annotationIds[value] = true); - annotationIds = discoverChildAnnotationIds(child, annotationIds); + // Next we look up which annotations need to be selected + let annotationIds = {}; + + Utils.getValuesFromAttribute(target, 'data-annotation-ids').forEach((value) => annotationIds[value] = true); + annotationIds = discoverParentAnnotationIds(target, annotationIds); + annotationIds = discoverChildAnnotationIds(target, annotationIds); + + // We check the highlighting level to determine whether to select or deselect. + // TODO: it might be better to check the activeAnnotations instead + const targetIsSelected = parseInt(target.getAttribute('data-annotation-level'), 10) > 0; + + Object.keys(annotationIds).forEach((id) => { + // We need to check here if the right annotations panel tab is active + // a.k.a. it exists in the current filteredAnnotations + const annotation = filteredAnnotations.value.find((filtered) => filtered.id === id); + if (annotation) { + if (targetIsSelected) { + removeActiveAnnotation(id) + } else { + addActiveAnnotation(id) + } } }); - return annotationIds; + }); + }; + + function getNearestParentAnnotation(element) { + const parent = element.parentElement; + + if (!parent) return null; + + if (parent.dataset?.annotation) { + return parent; + } + return getNearestParentAnnotation(parent); + } + + const selectAll = () => { + filteredAnnotations.value.forEach(({id}) => !activeAnnotations.value[id] && addActiveAnnotation(id)); + }; + + const selectNone = () => { + filteredAnnotations.value.forEach(({id}) => activeAnnotations.value[id] && removeActiveAnnotation(id)); + }; + + function discoverParentAnnotationIds(el, annotationIds = {}) { + const parent = el.parentElement; + if (parent && parent.id !== 'text-content') { + Utils.getValuesFromAttribute(parent, 'data-annotation-ids').forEach((value) => annotationIds[value] = true); + return discoverParentAnnotationIds(parent, annotationIds); } + return annotationIds; + } - return { - activeTab, activeAnnotations, activeAnnotSelectVariantItems, annotations, filteredAnnotations, isLoading, variantItemsColors, // states - isAllAnnotationSelected, isNoAnnotationSelected, // computed - setActiveAnnotations, setAnnotations, updateAnnotationLoading, setFilteredAnnotations, setActiveAnnotSelectVariantItems, setVariantItemsColors, // functions - addActiveAnnotation, selectFilteredAnnotations, addHighlightAttributesToText, updateActiveAnnotSelectVariantItems, - annotationLoaded, removeActiveAnnotation, resetAnnotations, initAnnotations, - addHighlightHoverListeners, addHighlightClickListeners, getNearestParentAnnotation, - selectAll, selectNone, discoverParentAnnotationIds, discoverChildAnnotationIds - } - -}) \ No newline at end of file + function discoverChildAnnotationIds(el, annotationIds = {}) { + const {children} = el; + + [...children].forEach((child) => { + if (child.dataset.annotation) { + Utils.getValuesFromAttribute(child, 'data-annotation-ids').forEach((value) => annotationIds[value] = true); + annotationIds = discoverChildAnnotationIds(child, annotationIds); + } + }); + return annotationIds; + } + + return { + activeTab, + activeAnnotations, + annotations, + filteredAnnotations, + isLoading, + variantItemsColors, // states + setActiveAnnotations, + setVariantItemsColors, // functions + addActiveAnnotation, + selectFilteredAnnotations, + addHighlightAttributesToText, + annotationLoaded, + removeActiveAnnotation, + resetAnnotations, + initAnnotations, + addHighlightHoverListeners, + addHighlightClickListeners, + selectAll, + selectNone, + } + +}) diff --git a/src/utils/annotations.js b/src/utils/annotations.js index 67c9064b..4008514c 100644 --- a/src/utils/annotations.js +++ b/src/utils/annotations.js @@ -107,7 +107,7 @@ export function getNewLevel(element, operation) { return currentLevel; } -export function highlightTargets(selector, { operation, level }) { +export function highlightTargets(selector, { operation, level } = {}) { // If level is given we set it directly ignoring operation. const elements = (selector) ? [...document.querySelectorAll(selector)] : []; elements.forEach((element) => { @@ -115,6 +115,12 @@ export function highlightTargets(selector, { operation, level }) { }); } +export function getCurrentLevel(element) { + return element.hasAttribute('data-annotation-level') + ? parseInt(element.getAttribute('data-annotation-level'), 10) + : -1; +} + export function setLevelRecursively(element, { operation, level }) { if (element.hasAttribute('data-annotation')) { const newLevel = level !== undefined ? level : getNewLevel(element, operation); @@ -234,30 +240,26 @@ export function removeIcon(annotation) { } } -export function addWitness(selector, witness, variantItemsColors) { - const textPanelEl = document.querySelector('#text-content') - const targetHtmlEl = textPanelEl.querySelector(selector) +export function addWitness(targetHtmlEl, witness, color) { const parentEl = targetHtmlEl.parentElement const indexOfTarget = [].slice.call(parentEl.children).indexOf(targetHtmlEl) - + const witHtml = createCurrWitHtml(witness, color) - const witHtml = createCurrWitHtml(witness, variantItemsColors[witness]) - - if(!parentEl.children[indexOfTarget-1].classList.contains("witnesses")) { + if(!parentEl.children[indexOfTarget-1].classList.contains("witnesses")) { // if the previous element in DOM does not contains 'witnesses chips' then create the 'parent' span of the 'witnesses chips' // Create another function - like create 'witnesses' Html element const witnessesHtmlEl = document.createElement("span"); witnessesHtmlEl.classList.add('witnesses') - + witnessesHtmlEl.prepend(witHtml) parentEl.insertBefore(witnessesHtmlEl, targetHtmlEl) } else { // get the target element and get the previous element - which we know is the 'witnesses' span list // get the witnessesHtml element and append the witHtml element - let witnessesHtmlEl = parentEl.children[indexOfTarget-1] + let witnessesHtmlEl = parentEl.children[indexOfTarget-1] witnessesHtmlEl.appendChild(witHtml) - } + } } function createCurrWitHtml(witness, borderColor) { @@ -266,7 +268,7 @@ function createCurrWitHtml(witness, borderColor) { witHtml.innerHTML = witness witHtml.classList.add('t-rounded-3xl', 't-box-border', 't-w-75', 't-h-8', 't-border-2', 't-p-[2px]', 't-text-sm', 't-ml-[3px]') witHtml.style.borderColor = borderColor - + return witHtml } @@ -285,13 +287,13 @@ export function removeWitness(selector, witness) { // find this witness inside the 'witnesses' html span and remove it const witnessesHtmlEl = getWitnessesHtmlEl(selector) - const witHtml = Array.from(witnessesHtmlEl.children).filter(item => item.innerHTML === witness) - witHtml[0].remove() + const witHtml = Array.from(witnessesHtmlEl.children).filter(item => item.innerHTML === witness) + witHtml[0].remove() } export function getWitnessesHtmlEl(selector) { // selector represents the target text of a certain variant item - // we aim to get the html element which contains the 'witnesses chips' related to the target. + // we aim to get the html element which contains the 'witnesses chips' related to the target. // this html element which contains the 'witnesses chips' is located before the target element const targetHtmlEl = document.querySelector(selector) const parentEl = targetHtmlEl.parentElement @@ -319,8 +321,8 @@ export function unselectVariantItems(variantItemsSelection) { } export function addWitnessesChipsWhenSelectText(variantItemsSelection, selector, variantItemsColors) { - // variantItemsSelection: JSON object of 'witness name': 'true' - // this function aims to add all witnesses on the highlighted text when we click on the text + // variantItemsSelection: JSON object of 'witness name': 'true' + // this function aims to add all witnesses on the highlighted text when we click on the text Object.keys(variantItemsSelection).forEach((witness) => { addWitness(selector, witness, variantItemsColors) diff --git a/tests/mocks/ahiqar/annotations/ahiqar/arabic-karshuni/3r176/183a/latest/annotationPage.json b/tests/mocks/ahiqar/annotations/ahiqar/arabic-karshuni/3r176/183a/latest/annotationPage.json index 8e0afe52..a7e7c2f2 100644 --- a/tests/mocks/ahiqar/annotations/ahiqar/arabic-karshuni/3r176/183a/latest/annotationPage.json +++ b/tests/mocks/ahiqar/annotations/ahiqar/arabic-karshuni/3r176/183a/latest/annotationPage.json @@ -285,24 +285,76 @@ "x-content-type": "Variant", "type": "TextualBody", "format": "text/plain", - "value": [ - { - "witness": "Cod. Arab. 236", - "entry": "omisit" + "value": { + "witness": "Cod. Arab. 236", + "entry": "omisit" + } + }, + "target": [ + { + "selector": { + "type": "CssSelector", + "value": "#t_Brit_Mus_Add_7209_MD17104N1l5l3l7l5l41l2_3" }, - { - "witness": "DFM 614", - "entry": "omisit" + "source": "PLACEHOLDER \u2192 TARGET CONTENT HTML IRI" + } + ], + "type": "Annotation", + "id": "http://ahikar.uni-goettingen.de/ns/annotations/3r14z/annotation-variants-t_Brit_Mus_Add_7209_N1l5l3l5l5l29l4_w_1_0" + }, + { + "body": { + "x-content-type": "Variant", + "type": "TextualBody", + "format": "text/plain", + "value": { + "witness": "DFM 614", + "entry": "omisit" + } + }, + "target": [ + { + "selector": { + "type": "CssSelector", + "value": "#t_Brit_Mus_Add_7209_MD17104N1l5l3l7l5l41l2_3" }, - { - "witness": "Ming. syr. 258", - "entry": "اللبان" + "source": "PLACEHOLDER \u2192 TARGET CONTENT HTML IRI" + } + ], + "type": "Annotation", + "id": "http://ahikar.uni-goettingen.de/ns/annotations/3r14z/annotation-variants-t_Brit_Mus_Add_7209_N1l5l3l5l5l29l4_w_1_1" + }, + { + "body": { + "x-content-type": "Variant", + "type": "TextualBody", + "format": "text/plain", + "value": { + "witness": "Ming. syr. 258", + "entry": "اللبان" + } + }, + "target": [ + { + "selector": { + "type": "CssSelector", + "value": "#t_Brit_Mus_Add_7209_MD17104N1l5l3l7l5l41l2_3" }, - { - "witness": "Sach. 339", - "entry": "البان" - } - ] + "source": "PLACEHOLDER \u2192 TARGET CONTENT HTML IRI" + } + ], + "type": "Annotation", + "id": "http://ahikar.uni-goettingen.de/ns/annotations/3r14z/annotation-variants-t_Brit_Mus_Add_7209_N1l5l3l5l5l29l4_w_1_2" + }, + { + "body": { + "x-content-type": "Variant", + "type": "TextualBody", + "format": "text/plain", + "value": { + "witness": "Sach. 339", + "entry": "البان" + } }, "target": [ { @@ -314,31 +366,83 @@ } ], "type": "Annotation", - "id": "http://ahikar.uni-goettingen.de/ns/annotations/3r14z/annotation-variants-t_Brit_Mus_Add_7209_N1l5l3l5l5l29l4_w_1" + "id": "http://ahikar.uni-goettingen.de/ns/annotations/3r14z/annotation-variants-t_Brit_Mus_Add_7209_N1l5l3l5l5l29l4_w_1_3" }, { "body": { "x-content-type": "Variant", "type": "TextualBody", "format": "text/plain", - "value": [ - { - "witness": "Cod. Arab. 236", - "entry": "omisit" + "value": { + "witness": "Cod. Arab. 236", + "entry": "omisit" + } + }, + "target": [ + { + "selector": { + "type": "CssSelector", + "value": "#t_Brit_Mus_Add_7209_MD17104N1l5l3l7l5l43l2_2" }, - { - "witness": "DFM 614", - "entry": "omisit" + "source": "PLACEHOLDER \u2192 TARGET CONTENT HTML IRI" + } + ], + "type": "Annotation", + "id": "http://ahikar.uni-goettingen.de/ns/annotations/3r14z/annotation-variants-t_Brit_Mus_Add_7209_N1l5l3l5l5l29l4_w_2_0" + }, + { + "body": { + "x-content-type": "Variant", + "type": "TextualBody", + "format": "text/plain", + "value": { + "witness": "DFM 614", + "entry": "omisit" + } + }, + "target": [ + { + "selector": { + "type": "CssSelector", + "value": "#t_Brit_Mus_Add_7209_MD17104N1l5l3l7l5l43l2_2" }, - { - "witness": "Ming. syr. 258", - "entry": "والقرفه" + "source": "PLACEHOLDER \u2192 TARGET CONTENT HTML IRI" + } + ], + "type": "Annotation", + "id": "http://ahikar.uni-goettingen.de/ns/annotations/3r14z/annotation-variants-t_Brit_Mus_Add_7209_N1l5l3l5l5l29l4_w_2_1" + }, + { + "body": { + "x-content-type": "Variant", + "type": "TextualBody", + "format": "text/plain", + "value": { + "witness": "Ming. syr. 258", + "entry": "والقرفه" + } + }, + "target": [ + { + "selector": { + "type": "CssSelector", + "value": "#t_Brit_Mus_Add_7209_MD17104N1l5l3l7l5l43l2_2" }, - { - "witness": "Sach. 339", - "entry": "omisit" - } - ] + "source": "PLACEHOLDER \u2192 TARGET CONTENT HTML IRI" + } + ], + "type": "Annotation", + "id": "http://ahikar.uni-goettingen.de/ns/annotations/3r14z/annotation-variants-t_Brit_Mus_Add_7209_N1l5l3l5l5l29l4_w_2_2" + }, + { + "body": { + "x-content-type": "Variant", + "type": "TextualBody", + "format": "text/plain", + "value": { + "witness": "Sach. 339", + "entry": "omisit" + } }, "target": [ { @@ -350,31 +454,83 @@ } ], "type": "Annotation", - "id": "http://ahikar.uni-goettingen.de/ns/annotations/3r14z/annotation-variants-t_Brit_Mus_Add_7209_N1l5l3l5l5l29l4_w_2" + "id": "http://ahikar.uni-goettingen.de/ns/annotations/3r14z/annotation-variants-t_Brit_Mus_Add_7209_N1l5l3l5l5l29l4_w_2_3" }, { "body": { "x-content-type": "Variant", "type": "TextualBody", "format": "text/plain", - "value": [ - { - "witness": "Cod. Arab. 236", - "entry": "omisit" + "value": { + "witness": "Cod. Arab. 236", + "entry": "omisit" + } + }, + "target": [ + { + "selector": { + "type": "CssSelector", + "value": "#t_Brit_Mus_Add_7209_MD17104N1l5l3l7l5l43l2_3" }, - { - "witness": "DFM 614", - "entry": "omisit" + "source": "PLACEHOLDER \u2192 TARGET CONTENT HTML IRI" + } + ], + "type": "Annotation", + "id": "http://ahikar.uni-goettingen.de/ns/annotations/3r14z/annotation-variants-t_Brit_Mus_Add_7209_N1l5l3l5l5l29l4_w_3_0" + }, + { + "body": { + "x-content-type": "Variant", + "type": "TextualBody", + "format": "text/plain", + "value": { + "witness": "DFM 614", + "entry": "omisit" + } + }, + "target": [ + { + "selector": { + "type": "CssSelector", + "value": "#t_Brit_Mus_Add_7209_MD17104N1l5l3l7l5l43l2_3" }, - { - "witness": "Ming. syr. 258", - "entry": "والكمكام" + "source": "PLACEHOLDER \u2192 TARGET CONTENT HTML IRI" + } + ], + "type": "Annotation", + "id": "http://ahikar.uni-goettingen.de/ns/annotations/3r14z/annotation-variants-t_Brit_Mus_Add_7209_N1l5l3l5l5l29l4_w_3_1" + }, + { + "body": { + "x-content-type": "Variant", + "type": "TextualBody", + "format": "text/plain", + "value": { + "witness": "Ming. syr. 258", + "entry": "والكمكام" + } + }, + "target": [ + { + "selector": { + "type": "CssSelector", + "value": "#t_Brit_Mus_Add_7209_MD17104N1l5l3l7l5l43l2_3" }, - { - "witness": "Sach. 339", - "entry": "والكمكام" - } - ] + "source": "PLACEHOLDER \u2192 TARGET CONTENT HTML IRI" + } + ], + "type": "Annotation", + "id": "http://ahikar.uni-goettingen.de/ns/annotations/3r14z/annotation-variants-t_Brit_Mus_Add_7209_N1l5l3l5l5l29l4_w_3_2" + }, + { + "body": { + "x-content-type": "Variant", + "type": "TextualBody", + "format": "text/plain", + "value": { + "witness": "Sach. 339", + "entry": "والكمكام" + } }, "target": [ { @@ -386,7 +542,7 @@ } ], "type": "Annotation", - "id": "http://ahikar.uni-goettingen.de/ns/annotations/3r14z/annotation-variants-t_Brit_Mus_Add_7209_N1l5l3l5l5l29l4_w_3" + "id": "http://ahikar.uni-goettingen.de/ns/annotations/3r14z/annotation-variants-t_Brit_Mus_Add_7209_N1l5l3l5l5l29l4_w_3_3" } ], "refs": [ From c4b9087e8b1cf7958d983aae52c6dcb475dbd475 Mon Sep 17 00:00:00 2001 From: Paul Pestov <10750176+paulpestov@users.noreply.github.com> Date: Tue, 20 Aug 2024 23:23:01 +0200 Subject: [PATCH 09/41] feat: add active variants details popup --- .../variants/ActiveVariantsDetails.vue | 24 ++++++++++++++----- .../annotations/variants/VariantsList.vue | 5 +++- src/components/base/BaseDialog.vue | 4 ++-- src/i18n/de/index.js | 4 +++- src/i18n/en/index.js | 4 +++- 5 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/components/annotations/variants/ActiveVariantsDetails.vue b/src/components/annotations/variants/ActiveVariantsDetails.vue index 5ad23d29..54702234 100644 --- a/src/components/annotations/variants/ActiveVariantsDetails.vue +++ b/src/components/annotations/variants/ActiveVariantsDetails.vue @@ -13,11 +13,23 @@ diff --git a/src/components/annotations/variants/VariantsList.vue b/src/components/annotations/variants/VariantsList.vue index 48944602..482487ed 100644 --- a/src/components/annotations/variants/VariantsList.vue +++ b/src/components/annotations/variants/VariantsList.vue @@ -14,7 +14,10 @@ /> - + diff --git a/src/components/base/BaseDialog.vue b/src/components/base/BaseDialog.vue index 8dbf9413..b63229ef 100644 --- a/src/components/base/BaseDialog.vue +++ b/src/components/base/BaseDialog.vue @@ -24,10 +24,10 @@ const emit = defineEmits<{(event: 'update:modelValue', payload: boolean): void; :append-to="configStore.config.container + ' .tido > *'" :pt="{ root: { - class: 't-relative t-bg-white dark:t-bg-gray-800 t-p-4 t-rounded-lg lg:t-min-w-[33%] t-shadow-lg dark:t-border dark:t-border-gray-700' + class: 't-relative t-bg-white dark:t-bg-gray-800 t-p-4 t-rounded-lg lg:t-min-w-[33%] t-max-h-[80%] t-shadow-lg dark:t-border dark:t-border-gray-700' }, header: { - class: 't-flex' + class: 't-flex t-mb-4' }, headerIcons: { class: 't-ml-auto' diff --git a/src/i18n/de/index.js b/src/i18n/de/index.js index 5d5f4796..9c8154ba 100644 --- a/src/i18n/de/index.js +++ b/src/i18n/de/index.js @@ -110,6 +110,8 @@ export default { not_found: 'Nicht gefunden', unauthorized: 'Nicht autorisiert', unknown_error: 'Unbekannter Fehler', - details: 'Details' + details: 'Details', + selected_variants: 'Ausgewählte Varianten', + original: 'Original' }; diff --git a/src/i18n/en/index.js b/src/i18n/en/index.js index 98131886..6b1de7ba 100644 --- a/src/i18n/en/index.js +++ b/src/i18n/en/index.js @@ -114,5 +114,7 @@ export default { not_found: 'Not found', unauthorized: 'Unauthorized', unknown_error: 'Unknown Error', - details: 'Details' + details: 'Details', + selected_variants: 'Selected Variants', + original: 'Original' }; From d2e7699cd4480e7f22c6ebba16815591e654fde2 Mon Sep 17 00:00:00 2001 From: Paul Pestov <10750176+paulpestov@users.noreply.github.com> Date: Wed, 21 Aug 2024 00:31:31 +0200 Subject: [PATCH 10/41] fix: update witness design --- .../variants/ActiveVariantsDetails.vue | 27 ++++++++++++++----- .../annotations/variants/VariantItem.vue | 18 ++++++------- .../annotations/variants/VariantsList.vue | 12 ++++++--- src/components/base/BaseDialog.vue | 2 +- src/utils/annotations.js | 9 ++++--- src/utils/color.js | 6 ++--- 6 files changed, 46 insertions(+), 28 deletions(-) diff --git a/src/components/annotations/variants/ActiveVariantsDetails.vue b/src/components/annotations/variants/ActiveVariantsDetails.vue index 54702234..6fba3ae2 100644 --- a/src/components/annotations/variants/ActiveVariantsDetails.vue +++ b/src/components/annotations/variants/ActiveVariantsDetails.vue @@ -1,6 +1,7 @@ -