From 46b5896adb47258ec56953bd3594990e406d8706 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Lo=C3=AFs=20Poujade?= <lois.poujade@tetras-libre.fr>
Date: Tue, 20 Dec 2022 15:00:54 +0100
Subject: [PATCH] Separate xywh & time fragment selectors

handle both fragment selectors types:
- target: https://ressource.url#xywh=0,0,10,10&t=19,20
- target: https..., selectors: [ FragmentSelector: ..., ]
---
 __tests__/WebAnnotation.test.js | 28 ++++++++++++++++++-----
 src/AnnotationCreation.js       | 26 ++++++++++------------
 src/WebAnnotation.js            | 39 +++++++++++++++++----------------
 3 files changed, 54 insertions(+), 39 deletions(-)

diff --git a/__tests__/WebAnnotation.test.js b/__tests__/WebAnnotation.test.js
index 80ace4d..1b0cb50 100644
--- a/__tests__/WebAnnotation.test.js
+++ b/__tests__/WebAnnotation.test.js
@@ -8,6 +8,7 @@ function createSubject(args = {}) {
     id: 'id',
     svg: 'svg',
     tags: ['tags'],
+    timing: [1, 3],
     xywh: 'xywh',
     ...args,
   });
@@ -20,26 +21,31 @@ describe('WebAnnotation', () => {
       ['body', 'canvasId', 'id', 'svg', 'xywh'].forEach((prop) => {
         expect(subject[prop]).toBe(prop);
       });
+      expect(subject.timing).toStrictEqual([1, 3]);
     });
   });
   describe('target', () => {
-    it('with svg and xywh', () => {
+    it('with svg, xywh and timing', () => {
       expect(subject.target()).toEqual({
         selector: [
+          {
+            type: 'SvgSelector',
+            value: 'svg',
+          },
           {
             type: 'FragmentSelector',
             value: 'xywh=xywh',
           },
           {
-            type: 'SvgSelector',
-            value: 'svg',
+            type: 'FragmentSelector',
+            value: 't=1,3',
           },
         ],
         source: 'canvasId',
       });
     });
     it('with svg only', () => {
-      subject = createSubject({ xywh: null });
+      subject = createSubject({ timing: null, xywh: null });
       expect(subject.target()).toEqual({
         selector: {
           type: 'SvgSelector',
@@ -49,7 +55,7 @@ describe('WebAnnotation', () => {
       });
     });
     it('with xywh only', () => {
-      subject = createSubject({ svg: null });
+      subject = createSubject({ svg: null, timing: null });
       expect(subject.target()).toEqual({
         selector: {
           type: 'FragmentSelector',
@@ -58,8 +64,18 @@ describe('WebAnnotation', () => {
         source: 'canvasId',
       });
     });
-    it('with no xywh or svg', () => {
+    it('with timing only', () => {
       subject = createSubject({ svg: null, xywh: null });
+      expect(subject.target()).toEqual({
+        selector: {
+          type: 'FragmentSelector',
+          value: 't=1,3',
+        },
+        source: 'canvasId',
+      });
+    });
+    it('with no xywh, svg or timing', () => {
+      subject = createSubject({ svg: null, timing: null, xywh: null });
       expect(subject.target()).toBe('canvasId');
     });
   });
diff --git a/src/AnnotationCreation.js b/src/AnnotationCreation.js
index 4b5a275..6bb0b42 100644
--- a/src/AnnotationCreation.js
+++ b/src/AnnotationCreation.js
@@ -33,8 +33,9 @@ import CursorIcon from './icons/Cursor';
 
 /** Extract time information from annotation target */
 function timeFromAnnoTarget(annotarget) {
+  console.info('TODO proper time extraction from: ', annotarget);
   // TODO w3c media fragments: t=,10 t=5,
-  const r = /t=([0-9]+),([0-9]+)/.exec(annotarget);
+  const r = /t=([0-9.]+),([0-9.]+)/.exec(annotarget);
   if (!r || r.length !== 3) {
     return ['', ''];
   }
@@ -43,13 +44,12 @@ function timeFromAnnoTarget(annotarget) {
 
 /** Extract xywh from annotation target */
 function geomFromAnnoTarget(annotarget) {
-  console.warn('TODO proper extraction');
+  console.info('TODO proper xywh extraction from: ', annotarget);
   const r = /xywh=((-?[0-9]+,?)+)/.exec(annotarget);
-  console.info('extracted from ', annotarget, r);
   if (!r || r.length !== 3) {
-    return ['', ''];
+    return '';
   }
-  return [r[1], r[2]];
+  return r[1];
 }
 
 /** */
@@ -88,12 +88,12 @@ class AnnotationCreation extends Component {
           });
         } else {
           annoState.svg = props.annotation.target.selector.value;
-          // eslint-disable-next-line max-len
-          [annoState.tstart, annoState.tend] = timeFromAnnoTarget(props.annotation.target.selector.value);
+          // TODO does this happen ? when ? where are fragments selectors ?
         }
+      } else if (typeof props.annotation.target === 'string') {
+        annoState.xywh = geomFromAnnoTarget(props.annotation.target);
+        [annoState.tstart, annoState.tend] = timeFromAnnoTarget(props.annotation.target);
       }
-      //
-      // start/end time
     }
 
     const toolState = {
@@ -196,10 +196,7 @@ class AnnotationCreation extends Component {
     const {
       annoBody, tags, xywh, svg, tstart, tend, textEditorStateBustingKey,
     } = this.state;
-    let fsel = xywh;
-    if (tstart && tend) {
-      fsel = `${xywh || ''}&t=${tstart},${tend}`;
-    }
+    const timing = (tstart && tend) ? [tstart, tend] : null;
     canvases.forEach((canvas) => {
       const storageAdapter = config.annotation.adapter(canvas.id);
       const anno = new WebAnnotation({
@@ -209,7 +206,8 @@ class AnnotationCreation extends Component {
         manifestId: canvas.options.resource.id,
         svg,
         tags,
-        xywh: fsel,
+        timing,
+        xywh,
       }).toJson();
       if (annotation) {
         storageAdapter.update(anno).then((annoPage) => {
diff --git a/src/WebAnnotation.js b/src/WebAnnotation.js
index 22bff18..d31c22b 100644
--- a/src/WebAnnotation.js
+++ b/src/WebAnnotation.js
@@ -2,11 +2,12 @@
 export default class WebAnnotation {
   /** */
   constructor({
-    canvasId, id, xywh, body, tags, svg, manifestId,
+    canvasId, id, xywh, timing, body, tags, svg, manifestId,
   }) {
     this.id = id;
     this.canvasId = canvasId;
     this.xywh = xywh;
+    this.timing = timing;
     this.body = body;
     this.tags = tags;
     this.svg = svg;
@@ -48,33 +49,33 @@ export default class WebAnnotation {
 
   /** */
   target() {
-    let target = this.canvasId;
-    if (this.svg || this.xywh) {
-      target = {
-        source: this.source(),
-      };
+    if (!this.svg && !this.xywh && !this.timing) {
+      return this.canvasId;
     }
+    const selectors = [];
+    const target = {
+      source: this.source(),
+    };
     if (this.svg) {
-      target.selector = {
+      selectors.push({
         type: 'SvgSelector',
         value: this.svg,
-      };
+      });
     }
     if (this.xywh) {
-      const fragsel = {
+      selectors.push({
         type: 'FragmentSelector',
         value: `xywh=${this.xywh}`,
-      };
-      if (target.selector) {
-        // add fragment selector
-        target.selector = [
-          fragsel,
-          target.selector,
-        ];
-      } else {
-        target.selector = fragsel;
-      }
+      });
+    }
+    if (this.timing) {
+      const [start, end] = this.timing;
+      selectors.push({
+        type: 'FragmentSelector',
+        value: `t=${start},${end}`,
+      });
     }
+    target.selector = selectors.length === 1 ? selectors[0] : selectors;
     return target;
   }
 
-- 
GitLab