Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • 1-edit-annotations-on-videos
  • 5-chpk-images-in-annot
  • 5-final-images
  • 5-images-in-annotations
  • 5-old-images-in-annotations
  • 5-rebase-images-in-annot
  • 5-wip-images-in-annot
  • demo_ci_gitlab_pages
  • demo_gitlab_ci
  • devsetup
  • images_annotations
  • master
  • old_demo_ci_gitlab_pages
  • tetras-main
  • tmp
  • v0.4.0_react16
  • wip
  • wip-annot-video-ui
  • wip-annotations-on-videos
  • wip-debugging-annotations
20 results

Target

Select target project
No results found
Select Git revision
  • 1-edit-annotations-on-videos
  • 5-chpk-images-in-annot
  • 5-final-images
  • 5-images-in-annotations
  • 5-old-images-in-annotations
  • 5-rebase-images-in-annot
  • 5-wip-images-in-annot
  • demo_ci_gitlab_pages
  • demo_gitlab_ci
  • devsetup
  • images_annotations
  • master
  • old_demo_ci_gitlab_pages
  • tetras-main
  • tmp
  • v0.4.0_react16
  • wip
  • wip-annot-video-ui
  • wip-annotations-on-videos
  • wip-debugging-annotations
20 results
Show changes

Commits on Source 4

6 files
+ 428
48
Compare changes
  • Side-by-side
  • Inline

Files

+1 −1

File changed.

Contains only whitespace changes.

Original line number Original line Diff line number Diff line
@@ -3,7 +3,9 @@ import WebAnnotation from '../src/WebAnnotation';
/** */
/** */
function createSubject(args = {}) {
function createSubject(args = {}) {
  return new WebAnnotation({
  return new WebAnnotation({
    body: 'body',
    body: {
      value: 'body',
    },
    canvasId: 'canvasId',
    canvasId: 'canvasId',
    fragsel: { t: '5,10', xywh: 'xywh' },
    fragsel: { t: '5,10', xywh: 'xywh' },
    id: 'id',
    id: 'id',
@@ -17,11 +19,16 @@ describe('WebAnnotation', () => {
  let subject = createSubject();
  let subject = createSubject();
  describe('constructor', () => {
  describe('constructor', () => {
    it('sets instance accessors', () => {
    it('sets instance accessors', () => {
      ['body', 'canvasId', 'id', 'svg'].forEach((prop) => {
      ['canvasId', 'id', 'svg'].forEach((prop) => {
        expect(subject[prop]).toBe(prop);
        expect(subject[prop]).toBe(prop);
      });
      });
      expect(subject.fragsel).toStrictEqual({ t: '5,10', xywh: 'xywh' });
      expect(subject.fragsel).toStrictEqual({ t: '5,10', xywh: 'xywh' });
    });
    });
    it('sets instance accessors for body', () => {
      ['body'].forEach((prop) => {
        expect(subject[prop].value).toBe(prop);
      });
    });
  });
  });
  describe('target', () => {
  describe('target', () => {
    it('with svg and xywh', () => {
    it('with svg and xywh', () => {
@@ -109,20 +116,42 @@ describe('WebAnnotation', () => {
      ]);
      ]);
    });
    });
    it('with text only', () => {
    it('with text only', () => {
      subject = createSubject({ tags: null });
      subject = createSubject({ image: null, tags: null });
      expect(subject.createBody()).toEqual({
      expect(subject.createBody()).toEqual({
        type: 'TextualBody',
        type: 'TextualBody',
        value: 'body',
        value: 'body',
      });
      });
    });
    });
    it('with tags only', () => {
    it('with tags only', () => {
      subject = createSubject({ body: null });
      subject = createSubject({ body: null, image: null });
      expect(subject.createBody()).toEqual({
      expect(subject.createBody()).toEqual({
        purpose: 'tagging',
        purpose: 'tagging',
        type: 'TextualBody',
        type: 'TextualBody',
        value: 'tags',
        value: 'tags',
      });
      });
    });
    });
    it('with image and text', () => {
      subject = createSubject({ body: { value: 'hello' }, image: { url: 'http://example.photo/pic.jpg' }, tags: null });
      expect(subject.createBody()).toEqual([
        {
          type: 'TextualBody',
          value: 'hello',
        },
        {
          format: 'image/jpg',
          id: 'http://example.photo/pic.jpg',
          type: 'Image',
        },
      ]);
    });
    it('with image only', () => {
      subject = createSubject({ body: null, image: { url: 'http://example.photo/pic.jpg' }, tags: null });
      expect(subject.createBody()).toEqual({
        format: 'image/jpg',
        id: 'http://example.photo/pic.jpg',
        type: 'Image',
      });
    });
  });
  });
  describe('toJson', () => {
  describe('toJson', () => {
    it('generates a WebAnnotation', () => {
    it('generates a WebAnnotation', () => {
Original line number Original line Diff line number Diff line
@@ -15514,7 +15514,7 @@
    },
    },
    "node_modules/mirador": {
    "node_modules/mirador": {
      "version": "3.3.0",
      "version": "3.3.0",
      "resolved": "git+https://gitlab.tetras-libre.fr/iiif/mirador-video-annotation#8d142157eeb008edd0761859b6ad8abfa564c2a6",
      "resolved": "git+https://gitlab.tetras-libre.fr/iiif/mirador-video-annotation#047b206353616adc135bcd3b018da9857c4222d6",
      "dev": true,
      "dev": true,
      "license": "Apache-2.0",
      "license": "Apache-2.0",
      "dependencies": {
      "dependencies": {
@@ -34987,7 +34987,7 @@
      }
      }
    },
    },
    "mirador": {
    "mirador": {
      "version": "git+https://gitlab.tetras-libre.fr/iiif/mirador-video-annotation#8d142157eeb008edd0761859b6ad8abfa564c2a6",
      "version": "git+https://gitlab.tetras-libre.fr/iiif/mirador-video-annotation#047b206353616adc135bcd3b018da9857c4222d6",
      "dev": true,
      "dev": true,
      "from": "mirador@git+https://gitlab.tetras-libre.fr/iiif/mirador-video-annotation#annotation-on-video",
      "from": "mirador@git+https://gitlab.tetras-libre.fr/iiif/mirador-video-annotation#annotation-on-video",
      "requires": {
      "requires": {
Original line number Original line Diff line number Diff line
import React, { Component } from 'react';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import PropTypes from 'prop-types';
import {
import {
  IconButton, Button, Paper, Grid, Popover, Divider,
  Button, Paper, Grid, Popover, Divider,
  MenuList, MenuItem, ClickAwayListener,
  MenuList, MenuItem, ClickAwayListener,
} from '@material-ui/core';
} from '@material-ui/core';
import { Alarm, LastPage } from '@material-ui/icons';
import { Alarm, LastPage } from '@material-ui/icons';
@@ -19,6 +19,14 @@ import StrokeColorIcon from '@material-ui/icons/BorderColor';
import LineWeightIcon from '@material-ui/icons/LineWeight';
import LineWeightIcon from '@material-ui/icons/LineWeight';
import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown';
import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown';
import FormatShapesIcon from '@material-ui/icons/FormatShapes';
import FormatShapesIcon from '@material-ui/icons/FormatShapes';
import InsertPhotoIcon from '@material-ui/icons/InsertPhoto';
import Dialog from '@material-ui/core/Dialog';
import DialogTitle from '@material-ui/core/DialogTitle';
import DialogContent from '@material-ui/core/DialogContent';
import DialogActions from '@material-ui/core/DialogActions';
import TextField from '@material-ui/core/TextField';
import Checkbox from '@material-ui/core/Checkbox';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import { SketchPicker } from 'react-color';
import { SketchPicker } from 'react-color';
import { v4 as uuid } from 'uuid';
import { v4 as uuid } from 'uuid';
import { withStyles } from '@material-ui/core/styles';
import { withStyles } from '@material-ui/core/styles';
@@ -55,25 +63,52 @@ function geomFromAnnoTarget(annotarget) {


/** */
/** */
class AnnotationCreation extends Component {
class AnnotationCreation extends Component {
  /** */
  static checkURL(url) {
    const expression = /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)/;
    const regex = new RegExp(expression);

    return url.match(regex);
  }

  /** */
  static loadImg(url) {
    return new Promise((resolve, reject) => {
      const img = new Image();
      img.src = url;

      img.onload = () => resolve({ height: img.height, width: img.width });
      img.onerror = reject;
    });
  }

  /** */
  /** */
  constructor(props) {
  constructor(props) {
    super(props);
    super(props);


    const annoState = {};
    const annoState = {};
    annoState.image = false;

    if (props.annotation) {
    if (props.annotation) {
      //
      //
      // annotation body
      // annotation body
      if (Array.isArray(props.annotation.body)) {
      if (Array.isArray(props.annotation.body)) {
        annoState.tags = [];
        annoState.tags = [];
        props.annotation.body.forEach((body) => {
        props.annotation.body.forEach((body) => {
          if (body.purpose === 'tagging') {
          if (body.purpose === 'tagging' && body.type === 'TextualBody') {
            annoState.tags.push(body.value);
            annoState.tags.push(body.value);
          } else {
          } else if (body.type === 'TextualBody') {
            annoState.annoBody = body.value;
            annoState.textBody = body.value;
          } else if (body.type === 'Image') {
            // annoState.textBody = body.value; // why text body here ???
            annoState.image = body;
          }
          }
        });
        });
      } else {
      } else if (props.annotation.body.type === 'TextualBody') {
        annoState.annoBody = props.annotation.body.value;
        annoState.textBody = props.annotation.body.value;
      } else if (props.annotation.body.type === 'Image') {
        // annoState.textBody = props.annotation.body.value; // why text body here ???
        annoState.image = props.annotation.body;
      }
      }
      //
      //
      // drawing position
      // drawing position
@@ -101,6 +136,7 @@ class AnnotationCreation extends Component {
    const toolState = {
    const toolState = {
      activeTool: 'cursor',
      activeTool: 'cursor',
      closedMode: 'closed',
      closedMode: 'closed',
      colorPopoverOpen: false,
      currentColorType: false,
      currentColorType: false,
      fillColor: null,
      fillColor: null,
      strokeColor: '#00BFFF',
      strokeColor: '#00BFFF',
@@ -108,23 +144,52 @@ class AnnotationCreation extends Component {
      ...(props.config.annotation.defaults || {}),
      ...(props.config.annotation.defaults || {}),
    };
    };


    const timeState = props.currentTime !== null
      ? { tend: Math.floor(props.currentTime) + 10, tstart: Math.floor(props.currentTime) }
      : { tend: null, tstart: null };

    this.state = {
    this.state = {
      ...toolState,
      ...toolState,
      annoBody: '',
      ...timeState,
      colorPopoverOpen: false,
      activeTool: 'cursor',
      closedMode: 'closed',
      currentColorType: false,
      fillColor: null,
      imgConstrain: false,
      imgHeight: {
        lastSubmittedValue: '',
        srcValue: '',
        validity: 0,
        value: '',
      },
      imgUrl: {
        lastSubmittedValue: '',
        validity: 0,
        value: '',
      },
      imgWidth: {
        lastSubmittedValue: '',
        srcValue: '',
        validity: 0,
        value: '',
      },
      lineWeightPopoverOpen: false,
      lineWeightPopoverOpen: false,
      openAddImgDialog: false,
      popoverAnchorEl: null,
      popoverAnchorEl: null,
      popoverLineWeightAnchorEl: null,
      popoverLineWeightAnchorEl: null,
      svg: null,
      svg: null,
      tend: Math.floor(props.currentTime) + 10,
      textBody: '',
      textEditorStateBustingKey: 0,
      textEditorStateBustingKey: 0,
      tstart: Math.floor(props.currentTime),
      xywh: null,
      xywh: null,
      ...annoState,
      ...annoState,
    };
    };


    this.submitForm = this.submitForm.bind(this);
    this.submitForm = this.submitForm.bind(this);
    this.updateBody = this.updateBody.bind(this);
    // this.updateBody = this.updateBody.bind(this);
    this.updateTextBody = this.updateTextBody.bind(this);
    this.getImgDimensions = this.getImgDimensions.bind(this);
    this.setImgWidth = this.setImgWidth.bind(this);
    this.setImgHeight = this.setImgHeight.bind(this);
    this.updateTstart = this.updateTstart.bind(this);
    this.updateTstart = this.updateTstart.bind(this);
    this.updateTend = this.updateTend.bind(this);
    this.updateTend = this.updateTend.bind(this);
    this.setTstartNow = this.setTstartNow.bind(this);
    this.setTstartNow = this.setTstartNow.bind(this);
@@ -140,6 +205,79 @@ class AnnotationCreation extends Component {
    this.handleCloseLineWeight = this.handleCloseLineWeight.bind(this);
    this.handleCloseLineWeight = this.handleCloseLineWeight.bind(this);
    this.closeChooseColor = this.closeChooseColor.bind(this);
    this.closeChooseColor = this.closeChooseColor.bind(this);
    this.updateStrokeColor = this.updateStrokeColor.bind(this);
    this.updateStrokeColor = this.updateStrokeColor.bind(this);
    this.isConstrained = this.isConstrained.bind(this);
    this.handleConstrainCheck = this.handleConstrainCheck.bind(this);
    this.handleImgDialogChange = this.handleImgDialogChange.bind(this);
    this.handleImgDialogSubmit = this.handleImgDialogSubmit.bind(this);
  }

  /** */
  handleImgDialogChange(open) {
    const { imgHeight, imgWidth, imgUrl } = this.state;

    this.setState({
      imgHeight: {
        ...imgHeight,
        validity: 1,
        value: imgHeight.lastSubmittedValue,
      },
      imgUrl: {
        ...imgUrl,
        validity: 1,
        value: imgUrl.lastSubmittedValue,
      },
      imgWidth: {
        ...imgWidth,
        validity: 1,
        value: imgWidth.lastSubmittedValue,
      },
      openAddImgDialog: open,
    });
  }

  /** */
  handleConstrainCheck(event) {
    const value = event.target.checked;

    this.setState({
      imgConstrain: value,
    });
  }

  /** */
  handleImgDialogSubmit() {
    let open = true;
    const { imgUrl, imgHeight, imgWidth } = this.state;

    const urlValidity = AnnotationCreation.checkURL(imgUrl.value) ? 1 : 2;
    const widthValidity = imgWidth.value > 0 ? 1 : 2;
    const heightValidity = imgHeight.value > 0 ? 1 : 2;
    if (urlValidity === 1 && widthValidity === 1 && heightValidity === 1) {
      open = false;
    }

    this.setState({
      image: { id: imgUrl.value },
      imgHeight: {
        ...imgHeight,
        lastSubmittedValue: heightValidity === 1 ? imgHeight.value : imgHeight.lastSubmittedValue,
        validity: heightValidity,
        value: imgHeight.value,
      },
      imgUrl: {
        ...imgUrl,
        lastSubmittedValue: urlValidity === 1 ? imgUrl.value : imgUrl.lastSubmittedValue,
        validity: urlValidity,
        value: imgUrl.value,
      },
      imgWidth: {
        ...imgWidth,
        lastSubmittedValue: widthValidity === 1 ? imgWidth.value : imgWidth.lastSubmittedValue,
        validity: widthValidity,
        value: imgWidth.value,
      },
      openAddImgDialog: open,
    });
  }
  }


  /** */
  /** */
@@ -171,11 +309,74 @@ class AnnotationCreation extends Component {
    this.setState({ tend: Math.floor(this.props.currentTime) });
    this.setState({ tend: Math.floor(this.props.currentTime) });
  }
  }


  /** update annotation start time */
  /** */
  updateTstart(value) { this.setState({ tstart: value }); }
  async getImgDimensions(url) {
    const { imgHeight, imgWidth, imgUrl } = this.state;
    const urlValidity = AnnotationCreation.checkURL(url) ? 1 : 2;


  /** update annotation end time */
    try {
  updateTend(value) { this.setState({ tend: value }); }
      const dimensions = await AnnotationCreation.loadImg(url);

      this.setState({
        imgHeight: {
          ...imgHeight,
          srcValue: dimensions.height || '',
          value: dimensions.height || '',
        },
        imgUrl: {
          ...imgUrl,
          validity: 1,
          value: url,
        },
        imgWidth: {
          ...imgWidth,
          srcValue: dimensions.width || '',
          value: dimensions.width || '',
        },
      });
    } catch (e) {
      this.setState({
        imgUrl: {
          ...imgUrl,
          validity: urlValidity,
          value: url,
        },
      });
    }
  }

  /** */
  setImgWidth(value) {
    const { imgWidth } = this.state;
    this.setState({
      imgWidth: {
        ...imgWidth,
        value,
      },
    });
  }

  /** */
  setImgUrl(value) {
    const { imgUrl } = this.state;
    this.setState({
      imgUrl: {
        ...imgUrl,
        value,
      },
    });
  }

  /** */
  setImgHeight(value) {
    const { imgHeight } = this.state;
    this.setState({
      imgHeight: {
        ...imgHeight,
        value,
      },
    });
  }


  /** seekTo/goto annotation start time */
  /** seekTo/goto annotation start time */
  seekToTstart() {
  seekToTstart() {
@@ -199,6 +400,43 @@ class AnnotationCreation extends Component {
    }
    }
  }
  }


  /** update annotation start time */
  updateTstart(value) { this.setState({ tstart: value }); }

  /** update annotation end time */
  updateTend(value) { this.setState({ tend: value }); }

  /** */
  isConstrained(event) { // adjust other dimension in proportion to inputted dimension
    const { imgConstrain, imgWidth, imgHeight } = this.state;
    const ratio = imgWidth.srcValue / imgHeight.srcValue;

    if (imgConstrain) {
      if (event.target.id === 'width' && imgWidth.srcValue !== '') {
        // set height to be the same as width if width is less than 0
        const height = imgWidth.value > 0 ? imgWidth.value * (1 / ratio) : imgWidth.value;
        this.setState({
          imgHeight: {
            ...imgHeight,
            validity: 1,
            value: height,
          },
        });
      } else if (event.target.id === 'height' && imgHeight.srcValue !== '') {
        // set width to be the same as height if height is less than 0
        const width = imgHeight.value > 0 ? imgHeight.value * ratio : imgHeight.value;

        this.setState({
          imgWidth: {
            ...imgWidth,
            validity: 1,
            value: width,
          },
        });
      }
    }
  }

  /** */
  /** */
  openChooseColor(e) {
  openChooseColor(e) {
    this.setState({
    this.setState({
@@ -240,19 +478,38 @@ class AnnotationCreation extends Component {
      annotation, canvases, receiveAnnotation, config,
      annotation, canvases, receiveAnnotation, config,
    } = this.props;
    } = this.props;
    const {
    const {
      annoBody, tags, xywh, svg, tstart, tend, textEditorStateBustingKey,
      textBody, image, imgWidth, imgHeight, imgUrl, tags, xywh, svg,
      imgConstrain, tstart, tend, textEditorStateBustingKey,
    } = this.state;
    } = this.state;
    const t = (tstart && tend) ? `${tstart},${tend}` : null;
    const annoBody = { value: (!textBody.length && t) ? `${secondsToHMS(tstart)} -> ${secondsToHMS(tend)}` : textBody };

    let imgBody;
    if (imgWidth.validity === 1 && imgHeight.validity === 1 && imgUrl.validity === 1) {
      imgBody = {
        constrain: imgConstrain,
        h: imgHeight.value,
        url: imgUrl.value,
        w: imgWidth.value,
      };
    } else {
      imgBody = image;
    }

    canvases.forEach((canvas) => {
    canvases.forEach((canvas) => {
      const storageAdapter = config.annotation.adapter(canvas.id);
      const storageAdapter = config.annotation.adapter(canvas.id);

      const anno = new WebAnnotation({
      const anno = new WebAnnotation({
        body: !annoBody.length ? `${secondsToHMS(tstart)} -> ${secondsToHMS(tend)}` : annoBody,
        body: annoBody,
        canvasId: canvas.id,
        canvasId: canvas.id,
        fragsel: { t: `${tstart},${tend}`, xywh },
        fragsel: { t, xywh },
        id: (annotation && annotation.id) || `${uuid()}`,
        id: (annotation && annotation.id) || `${uuid()}`,
        image: imgBody,
        manifestId: canvas.options.resource.id,
        manifestId: canvas.options.resource.id,
        svg,
        svg,
        tags,
        tags,
      }).toJson();
      }).toJson();

      if (annotation) {
      if (annotation) {
        storageAdapter.update(anno).then((annoPage) => {
        storageAdapter.update(anno).then((annoPage) => {
          receiveAnnotation(canvas.id, storageAdapter.annotationPageId, annoPage);
          receiveAnnotation(canvas.id, storageAdapter.annotationPageId, annoPage);
@@ -265,11 +522,12 @@ class AnnotationCreation extends Component {
    });
    });


    this.setState({
    this.setState({
      annoBody: '',
      image: false,
      svg: null,
      svg: null,
      tend: 0,
      tend: null,
      textBody: '',
      textEditorStateBustingKey: textEditorStateBustingKey + 1,
      textEditorStateBustingKey: textEditorStateBustingKey + 1,
      tstart: 0,
      tstart: null,
      xywh: null,
      xywh: null,
    });
    });
  }
  }
@@ -289,8 +547,8 @@ class AnnotationCreation extends Component {
  }
  }


  /** */
  /** */
  updateBody(annoBody) {
  updateTextBody(textBody) {
    this.setState({ annoBody });
    this.setState({ textBody });
  }
  }


  /** */
  /** */
@@ -307,9 +565,10 @@ class AnnotationCreation extends Component {
    } = this.props;
    } = this.props;


    const {
    const {
      activeTool, colorPopoverOpen, currentColorType, fillColor, popoverAnchorEl, strokeColor,
      activeTool, colorPopoverOpen, currentColorType, fillColor, openAddImgDialog, popoverAnchorEl,
      popoverLineWeightAnchorEl, lineWeightPopoverOpen, strokeWidth, closedMode, annoBody, svg,
      strokeColor, popoverLineWeightAnchorEl, lineWeightPopoverOpen, strokeWidth, closedMode,
      tstart, tend, textEditorStateBustingKey,
      textBody, imgUrl, imgWidth, imgHeight, imgConstrain, svg, tstart, tend,
      textEditorStateBustingKey, image,
    } = this.state;
    } = this.state;


    const mediaIsVideo = typeof VideosReferences.get(windowId) !== 'undefined';
    const mediaIsVideo = typeof VideosReferences.get(windowId) !== 'undefined';
@@ -322,6 +581,7 @@ class AnnotationCreation extends Component {
      >
      >
        <AnnotationDrawing
        <AnnotationDrawing
          activeTool={activeTool}
          activeTool={activeTool}
          annotation={annotation}
          fillColor={fillColor}
          fillColor={fillColor}
          strokeColor={strokeColor}
          strokeColor={strokeColor}
          strokeWidth={strokeWidth}
          strokeWidth={strokeWidth}
@@ -436,7 +696,6 @@ class AnnotationCreation extends Component {
                  )
                  )
                  : null
                  : null
              }
              }

            </Grid>
            </Grid>
          </Grid>
          </Grid>
          <Grid container>
          <Grid container>
@@ -460,7 +719,7 @@ class AnnotationCreation extends Component {


              <Grid item xs={12}>
              <Grid item xs={12}>
                <Typography variant="overline">
                <Typography variant="overline">
                  <ToggleButton value="true" title="Go to start time" size="small" onClick={this.seekToTend} className={classes.timecontrolsbutton}>
                  <ToggleButton value="true" title="Go to end time" size="small" onClick={this.seekToTend} className={classes.timecontrolsbutton}>
                    <LastPage />
                    <LastPage />
                  </ToggleButton>
                  </ToggleButton>
                  End
                  End
@@ -477,14 +736,93 @@ class AnnotationCreation extends Component {
            )}
            )}
            <Grid item xs={12}>
            <Grid item xs={12}>
              <Typography variant="overline">
              <Typography variant="overline">
                Content
                Image Content
              </Typography>
            </Grid>
            <Grid item xs={12} style={{ marginBottom: 10 }}>
              <ToggleButton value="image-icon" aria-label="insert an image" onClick={() => this.handleImgDialogChange(true)}>
                { image === false && <InsertPhotoIcon /> }
                { image !== false && <img src={image.id} width="100" height="auto" alt="loading failed" /> }
              </ToggleButton>
            </Grid>
            <Dialog open={openAddImgDialog} fullWidth onClose={() => this.handleImgDialogChange(false)} aria-labelledby="form-dialog-title">
              <DialogTitle id="form-dialog-title" disableTypography>
                <Typography variant="h2">Insert image</Typography>
              </DialogTitle>
              <DialogContent>
                <DialogTitle id="form-dialog-subtitle-1" style={{ paddingLeft: 0 }} disableTypography>
                  <Typography variant="h5">Image source</Typography>
                </DialogTitle>
                <TextField
                  value={imgUrl.value}
                  onChange={(e) => this.setImgUrl(e.target.value)}
                  onBlur={(e) => this.getImgDimensions(e.target.value)}
                  error={imgUrl.validity === 2}
                  helperText={imgUrl.validity === 2 ? 'Invalid URL' : ''}
                  margin="dense"
                  id="source"
                  label="Image URL"
                  type="url"
                  fullWidth
                />
              </DialogContent>
              <DialogContent>
                <DialogTitle id="form-dialog-subtitle-2" style={{ paddingLeft: 0 }} disableTypography>
                  <Typography variant="h5">Image dimensions</Typography>
                </DialogTitle>
                <TextField
                  value={imgWidth.value}
                  style={{ marginRight: 10, width: 100 }}
                  onChange={(e) => this.setImgWidth(e.target.value)}
                  onBlur={(e) => this.isConstrained(e)}
                  error={imgWidth.validity === 2}
                  helperText={imgWidth.validity === 2 ? 'Invalid width' : ''}
                  margin="dense"
                  id="width"
                  label="Width"
                  type="number"
                  variant="outlined"
                />
                <TextField
                  value={imgHeight.value}
                  style={{ marginLeft: 10, width: 100 }}
                  onChange={(e) => this.setImgHeight(e.target.value)}
                  onBlur={(e) => this.isConstrained(e)}
                  error={imgHeight.validity === 2}
                  helperText={imgHeight.validity === 2 ? 'Invalid height' : ''}
                  margin="dense"
                  id="height"
                  label="Height"
                  type="number"
                  variant="outlined"
                />
                <FormControlLabel
                  control={(
                    <Checkbox
                      checked={imgConstrain}
                      onChange={(e) => this.handleConstrainCheck(e)}
                      inputProps={{ 'aria-label': 'primary checkbox' }}
                      style={{ marginLeft: 30 }}
                    />
                  )}
                  label="Constrain proportions"
                />
              </DialogContent>
              <DialogActions>
                <Button onClick={() => this.handleImgDialogChange(false)}>Cancel</Button>
                <Button variant="contained" onClick={this.handleImgDialogSubmit} color="primary">Add</Button>
              </DialogActions>
            </Dialog>
            <Grid item xs={12}>
              <Typography variant="overline">
                Text Content
              </Typography>
              </Typography>
            </Grid>
            </Grid>
            <Grid item xs={12}>
            <Grid item xs={12}>
              <TextEditor
              <TextEditor
                key={textEditorStateBustingKey}
                key={textEditorStateBustingKey}
                annoHtml={annoBody}
                annoHtml={textBody}
                updateAnnotationBody={this.updateBody}
                updateAnnotationBody={this.updateTextBody}
              />
              />
            </Grid>
            </Grid>
          </Grid>
          </Grid>
@@ -586,7 +924,7 @@ AnnotationCreation.propTypes = {
      ),
      ),
    }),
    }),
  }).isRequired,
  }).isRequired,
  currentTime: PropTypes.number,
  currentTime: PropTypes.oneOfType([PropTypes.number, PropTypes.instanceOf(null)]),
  id: PropTypes.string.isRequired,
  id: PropTypes.string.isRequired,
  paused: PropTypes.bool,
  paused: PropTypes.bool,
  receiveAnnotation: PropTypes.func.isRequired,
  receiveAnnotation: PropTypes.func.isRequired,
@@ -599,7 +937,7 @@ AnnotationCreation.defaultProps = {
  annotation: null,
  annotation: null,
  canvases: [],
  canvases: [],
  closeCompanionWindow: () => {},
  closeCompanionWindow: () => {},
  currentTime: 0,
  currentTime: null,
  paused: true,
  paused: true,
  setCurrentTime: () => {},
  setCurrentTime: () => {},
  setSeekTo: () => {},
  setSeekTo: () => {},
Original line number Original line Diff line number Diff line
@@ -2,7 +2,7 @@
export default class WebAnnotation {
export default class WebAnnotation {
  /** */
  /** */
  constructor({
  constructor({
    canvasId, id, fragsel, body, tags, svg, manifestId,
    canvasId, id, fragsel, image, body, tags, svg, manifestId,
  }) {
  }) {
    this.id = id;
    this.id = id;
    this.canvasId = canvasId;
    this.canvasId = canvasId;
@@ -10,6 +10,7 @@ export default class WebAnnotation {
    this.body = body;
    this.body = body;
    this.tags = tags;
    this.tags = tags;
    this.svg = svg;
    this.svg = svg;
    this.image = image;
    this.manifestId = manifestId;
    this.manifestId = manifestId;
  }
  }


@@ -27,12 +28,23 @@ export default class WebAnnotation {
  /** */
  /** */
  createBody() {
  createBody() {
    let bodies = [];
    let bodies = [];
    if (this.body) {

      bodies.push({
    if (this.body && this.body.value !== '') {
      const textBody = {
        type: 'TextualBody',
        type: 'TextualBody',
        value: this.body,
        value: this.body.value,
      });
      };
      bodies.push(textBody);
    }
    }

    // TODO correct WebAnnotation format
    if (this.image) {
      const imgBody = { type: 'Image' };
      Object.assign(imgBody, this.image);
      imgBody.id = imgBody.url;
      bodies.push(imgBody);
    }

    if (this.tags) {
    if (this.tags) {
      bodies = bodies.concat(this.tags.map((tag) => ({
      bodies = bodies.concat(this.tags.map((tag) => ({
        purpose: 'tagging',
        purpose: 'tagging',
@@ -46,9 +58,10 @@ export default class WebAnnotation {
    return bodies;
    return bodies;
  }
  }


  /** */
  /** Fill target object with selectors (if any), else returns target url */
  target() {
  target() {
    if (!this.svg && !this.fragsel) {
    if (!this.svg
      && (!this.fragsel || !Object.values(this.fragsel).find((e) => e !== null))) {
      return this.canvasId;
      return this.canvasId;
    }
    }
    const target = { source: this.source() };
    const target = { source: this.source() };
Original line number Original line Diff line number Diff line
@@ -25,7 +25,7 @@ function mapStateToProps(state, { id: companionWindowId, windowId }) {
  const canvases = getVisibleCanvases(state, { windowId });
  const canvases = getVisibleCanvases(state, { windowId });


  const annotation = getPresentAnnotationsOnSelectedCanvases(state, { windowId })
  const annotation = getPresentAnnotationsOnSelectedCanvases(state, { windowId })
    .flatMap((annoPage) => annoPage.json.items)
    .flatMap((annoPage) => annoPage.json.items || [])
    .find((annot) => annot.id === annotationid);
    .find((annot) => annot.id === annotationid);


  return {
  return {