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
Loading items

Target

Select target project
  • lpo/mirador-annotations
1 result
Select Git revision
Loading items
Show changes
Commits on Source (352)
# Use this variable to add configurations :
# docker-compose.yml : required
# ports.yml : bind the port 300 to the $PORT variable
# traefik.yml : add traefik configurations
COMPOSE_FILE=docker-compose.yml:docker/ports.yml
# Choose between "dev" and "prod"
ENV=dev
# If you use docker/ports.yml
PORT=3000
# If you use docker/traefik.yml
# A unique name for traefik router
NAME=
# A traefik host rule ex `domain.FQDN` or `domain1.FQDN`,`domain2.FQDN`
HOST=`domain.fqdn`
......@@ -10,6 +10,10 @@
"parser": "@babel/eslint-parser",
"plugins": ["jest"],
"rules": {
"import/no-extraneous-dependencies": [
"error",
{"devDependencies": true}
],
"import/prefer-default-export": "off",
"no-console": "off",
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
......
......@@ -5,3 +5,7 @@
/node_modules
/umd
npm-debug.log*
.idea
.package-lock.json
.env
.*.sw?
[submodule "mirador"]
path = mirador
url = https://github.com/ProjectMirador/mirador/tree/mui5.git
16.20.2
\ No newline at end of file
This diff is collapsed.
# mirador-annotations
ARCHIVED IN FAVOR OF https://github.com/SCENE-CE/mirador-annotation-editor
[![Travis][build-badge]][build]
[![npm package][npm-badge]][npm]
[![Coveralls][coveralls-badge]][coveralls]
# Mirador Annotation Editor - GPL edition
`mirador-annotations` is a [Mirador 3](https://github.com/projectmirador/mirador) plugin that adds annotation creation tools to the user interface. Users can` create rectangle, oval, and polygon annotations and add text descriptors. A [live demo](https://mirador-annotations.netlify.app/) that stores annotations in local storage is available for testing. See the [issue queue](https://github.com/ProjectMirador/mirador-annotations/issues) for design proposals for additional functionality.
## Presentation
![annotation creation panel](https://user-images.githubusercontent.com/5402927/86628717-23c3ae80-bf7f-11ea-8f0b-389c39eb4398.png)
### Generalities
`mirador-annotation-editor` is a [Mirador 3](https://github.com/projectmirador/mirador) plugin that adds annotation creation tools to the user interface.
It is based on the original [mirador-annotations](https://github.com/ProjectMirador/mirador-annotations/) plugin with a lot of technical and functional modifications.
### Copyrights
#### Licence
Unlike the original [mirador-annotations](https://github.com/ProjectMirador/mirador-annotations/) plugin, this `mirador-annotation-editor` is distributed under the **GPL v3**.
Please acknoldge that any modification you make must be distributed under a compatible licence and cannot be closed source.
If you need to integrate this code base in closed source pieces of software, please contact us so we can discuss dual licencing.
#### Property
The base of this software (up to V1) is the property of [SATT Ouest Valorisation](https://www.ouest-valorisation.fr/) that funded its development under the french public contract AO-MA2023-0004-DV5189.
#### Authors
The authors of this software are :
- Clarisse Bardiot (concept and use cases)
- Jacob Hart (specifications)
- [Tétras Libre SARL](https://tetras-libre.fr) (development):
- David Rouquet
- Anthony Geourjon
- Antoine Roy
#### Contributors (updated february 2024)
- AZOPSOFT SAS
- Samuel Jugnet (especially code for the Konvas part)
- Loïs Poujade (especially the original modifications to anotate videos)
### General functionatities
- Activate a pannel with tools to create annotations on IIIF documents (manifests) containing images **and videos**
- Spatial and temporal targets for annotations
- Overlay annotations (geometric forms, free hand drawing, text and images)
- Textual/semantic annotations and tags
- Annotation metadata (based on Dublin Core)
- Annotation with anoter manifest -> network of IIIF documents
### Technical aspects
- Update to Material UI 5 and React 18 to follow latest Mirador upgrades (React 17 release also available)
- The [paperjs](http://paperjs.org/ ) library has been replaced with [Konvas](https://konvajs.org)
- Major refactorisation since the original `[mirador-annotations](https://github.com/ProjectMirador/mirador-annotations/) plugins`
- Works with the original [Mirador 3](https://github.com/projectmirador/mirador) if you need only image annotation
- If you need video annotation, you can use [our fork of Mirador: mirador-video](https://gitlab.tetras-libre.fr/iiif/mirador/mirador-video)
## Install (local)
This method requires `nvm`, `npm`.
```
git clone gitlab@gitlab.tetras-libre.fr:iiif/mirador/mirador-annotations.git
cd mirador-annotations
nvm use
npm install
```
NPM Install throw two errors (https://gitlab.tetras-libre.fr/iiif/mirador/mirador-annotations/-/issues/12). To fix run :
```
./cli post_install
```
Run mirador and the plugin :
```
npm start
```
## Persisting Annotations
Persisting annotations requires implementing an a IIIF annotation server. Several [examples of annotation servers](https://github.com/IIIF/awesome-iiif#annotation-servers) are available on iiif-awesome.
......@@ -17,14 +90,8 @@ Persisting annotations requires implementing an a IIIF annotation server. Severa
`mirador-annotations` requires an instance of Mirador 3. See the [Mirador wiki](https://github.com/ProjectMirador/mirador/wiki) for examples of embedding Mirador within an application. See the [live demo's index.js](https://github.com/ProjectMirador/mirador-annotations/blob/master/demo/src/index.js) for an example of importing the `mirador-annotations` plugin and configuring the adapter.
**You must use node v16.20.2**. You can `run nvm use` at the racine of the project to set your node version to 16.20.2.
## Contribute
Mirador's development, design, and maintenance is driven by community needs and ongoing feedback and discussion. Join us at our regularly scheduled community calls, on [IIIF slack #mirador](http://bit.ly/iiif-slack), or the [mirador-tech](https://groups.google.com/forum/#!forum/mirador-tech) and [iiif-discuss](https://groups.google.com/forum/#!forum/iiif-discuss) mailing lists. To suggest features, report bugs, and clarify usage, please submit a GitHub issue.
[build-badge]: https://img.shields.io/travis/user/repo/master.png?style=flat-square
[build]: https://travis-ci.org/user/repo
[npm-badge]: https://img.shields.io/npm/v/mirador-annotations.png?style=flat-square
[npm]: https://www.npmjs.org/package/mirador-annotations
[coveralls-badge]: https://img.shields.io/coveralls/user/repo/master.png?style=flat-square
[coveralls]: https://coveralls.io/github/user/repo
......@@ -4,6 +4,7 @@ import ToggleButtonGroup from '@material-ui/lab/ToggleButtonGroup';
import AnnotationCreation from '../src/AnnotationCreation';
import AnnotationDrawing from '../src/AnnotationDrawing';
import TextEditor from '../src/TextEditor';
import ImageFormField from '../src/ImageFormField';
/** */
function createWrapper(props) {
......@@ -36,6 +37,10 @@ describe('AnnotationCreation', () => {
wrapper = createWrapper();
expect(wrapper.dive().find(TextEditor).length).toBe(1);
});
it('adds the ImageFormField component', () => {
wrapper = createWrapper();
expect(wrapper.dive().find(ImageFormField).length).toBe(1);
});
it('can handle annotations without target selector', () => {
wrapper = createWrapper({
annotation: {
......
......@@ -3,13 +3,14 @@ import WebAnnotation from '../src/WebAnnotation';
/** */
function createSubject(args = {}) {
return new WebAnnotation({
body: 'body',
body: {
value: 'body',
},
canvasId: 'canvasId',
fragsel: { t: '5,10', xywh: 'xywh' },
id: 'id',
svg: 'svg',
tags: ['tags'],
timing: [1, 3],
xywh: 'xywh',
...args,
});
}
......@@ -18,14 +19,19 @@ describe('WebAnnotation', () => {
let subject = createSubject();
describe('constructor', () => {
it('sets instance accessors', () => {
['body', 'canvasId', 'id', 'svg', 'xywh'].forEach((prop) => {
['canvasId', 'id', 'svg'].forEach((prop) => {
expect(subject[prop]).toBe(prop);
});
expect(subject.timing).toStrictEqual([1, 3]);
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', () => {
it('with svg, xywh and timing', () => {
it('with svg and xywh', () => {
expect(subject.target()).toEqual({
selector: [
{
......@@ -34,18 +40,14 @@ describe('WebAnnotation', () => {
},
{
type: 'FragmentSelector',
value: 'xywh=xywh',
},
{
type: 'FragmentSelector',
value: 't=1,3',
value: 't=5,10&xywh=xywh',
},
],
source: 'canvasId',
});
});
it('with svg only', () => {
subject = createSubject({ timing: null, xywh: null });
subject = createSubject({ fragsel: null });
expect(subject.target()).toEqual({
selector: {
type: 'SvgSelector',
......@@ -54,8 +56,28 @@ describe('WebAnnotation', () => {
source: 'canvasId',
});
});
it('with time interval only', () => {
subject = createSubject({ fragsel: { t: '5,10' }, svg: null });
expect(subject.target()).toEqual({
selector: {
type: 'FragmentSelector',
value: 't=5,10',
},
source: 'canvasId',
});
});
it('with time interval only - xywh present but null', () => {
subject = createSubject({ fragsel: { t: '5,10', xywh: null }, svg: null });
expect(subject.target()).toEqual({
selector: {
type: 'FragmentSelector',
value: 't=5,10',
},
source: 'canvasId',
});
});
it('with xywh only', () => {
subject = createSubject({ svg: null, timing: null });
subject = createSubject({ fragsel: { xywh: 'xywh' }, svg: null });
expect(subject.target()).toEqual({
selector: {
type: 'FragmentSelector',
......@@ -64,18 +86,18 @@ describe('WebAnnotation', () => {
source: 'canvasId',
});
});
it('with timing only', () => {
subject = createSubject({ svg: null, xywh: null });
it('with xywh only - time interval present but null', () => {
subject = createSubject({ fragsel: { t: null, xywh: 'xywh' }, svg: null });
expect(subject.target()).toEqual({
selector: {
type: 'FragmentSelector',
value: 't=1,3',
value: 'xywh=xywh',
},
source: 'canvasId',
});
});
it('with no xywh, svg or timing', () => {
subject = createSubject({ svg: null, timing: null, xywh: null });
it('with no xywh or svg', () => {
subject = createSubject({ fragsel: null, svg: null });
expect(subject.target()).toBe('canvasId');
});
});
......@@ -94,20 +116,40 @@ describe('WebAnnotation', () => {
]);
});
it('with text only', () => {
subject = createSubject({ tags: null });
subject = createSubject({ image: null, tags: null });
expect(subject.createBody()).toEqual({
type: 'TextualBody',
value: 'body',
});
});
it('with tags only', () => {
subject = createSubject({ body: null });
subject = createSubject({ body: null, image: null });
expect(subject.createBody()).toEqual({
purpose: 'tagging',
type: 'TextualBody',
value: 'tags',
});
});
it('with image and text', () => {
subject = createSubject({ body: { value: 'hello' }, image: { id: 'http://example.photo/pic.jpg' }, tags: null });
expect(subject.createBody()).toEqual([
{
type: 'TextualBody',
value: 'hello',
},
{
id: 'http://example.photo/pic.jpg',
type: 'Image',
},
]);
});
it('with image only', () => {
subject = createSubject({ body: null, image: { id: 'http://example.photo/pic.jpg' }, tags: null });
expect(subject.createBody()).toEqual({
id: 'http://example.photo/pic.jpg',
type: 'Image',
});
});
});
describe('toJson', () => {
it('generates a WebAnnotation', () => {
......
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1215"
height="684">
<defs/>
<g>
<g>
<path fill="rgb(255,0,0)" stroke="rgb(255,0,0)" paint-order="fill stroke markers"
d=" M 188 197.66666412353516 L 477 197.66666412353516 L 477 524.6666641235352 L 188 524.6666641235352 L 188 197.66666412353516 Z Z"
fill-opacity="0.5" stroke-opacity="0.5" stroke-miterlimit="10" stroke-width="12.65625"
stroke-dasharray=""/>
</g>
<g>
<path fill="rgb(255,0,0)" stroke="rgb(255,0,0)" paint-order="fill stroke markers"
d=" M 625 194.66666412353513 L 916 194.66666412353513 L 916 577.6666641235352 L 625 577.6666641235352 L 625 194.66666412353513 Z Z"
fill-opacity="0.5" stroke-opacity="0.5" stroke-miterlimit="10" stroke-width="12.65625"
stroke-dasharray=""/>
</g>
</g>
</svg>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1215" height="684">
<defs/>
<g>
<g>
<path fill="rgb(255,0,0)" stroke="rgb(255,0,0)" d=" M 143 124.66666412353516 L 392 124.66666412353516 L 392 354.66666412353516 L 143 354.66666412353516 L 143 124.66666412353516 Z Z" fill-opacity="0.5" stroke-opacity="0.5" stroke-miterlimit="10" stroke-width="12.65625" stroke-dasharray=""/>
</g>
<g>
<image width="1800" height="963" preserveAspectRatio="none" transform="matrix(0.3266922638266449 0 0 0.3266922638266448 478.0164251120414 122.79378743494328)" xlink:href="https://presskit.porsche.de/models/assets/images/e/P17_0701_a5_rgb-c04a4c6e.jpg"/>
</g>
<g>
<g>
<text fill="red" stroke="none" font-family="Arial" font-size="40px" font-style="normal" font-weight="normal" text-decoration="undefined" x="0" y="20" text-anchor="start" dominant-baseline="central" transform="matrix(0.6328125 0 0 0.6328125 332.99999999999994 538.6666641235352)">Mon gros Cayenne</text>
</g>
</g>
</g>
</svg>
#!/bin/bash
is_docker() {
if [ ! -z "$(which docker-compose 2>/dev/null)" ];
then
echo "1"
else
echo "0"
fi
}
usage() {
echo -e "Usage $0 <command> [args]\n"
echo -e "COMMANDS\n"
echo "post_install"
echo -e "\t Do post install tasks "
}
DIR="$(dirname $0)"
SCRIPTS_DIR="$DIR/public/scripts"
action=$1
shift
# Keep actions sorted
case $action in
"post_install")
rm -f ./mirador/node_modules/dnd-multi-backend/dist/index.js
rm -f ./mirador/node_modules/react-dnd-multi-backend/dist/index.js
cp ./post_install_assets/dnd-multi-backend/index.js node_modules/dnd-multi-backend/dist/index.js
cp ./post_install_assets/react-dnd-multi-backend/index.js node_modules/react-dnd-multi-backend/dist/index.js
;;
"help")
usage
;;
*)
echo "ERROR: No command given"
usage
exit 1
;;
esac
......@@ -10,21 +10,25 @@ const config = {
// adapter: (canvasId) => new AnnototAdapter(canvasId, endpointUrl),
exportLocalStorageAnnotations: false, // display annotation JSON export button
},
catalog: [
{ manifestId: 'https://files.tetras-libre.fr/manifests/re_walden_cut.json' },
{ manifestId: 'https://files.tetras-libre.fr/manifests/jf_peyret_re_walden.json' },
{ manifestId: 'https://files.tetras-libre.fr/manifests/test_markeas_manifest.json' },
{ manifestId: 'https://files.tetras-libre.fr/manifests/installation_fresnoy_manifest.json' },
{ manifestId: 'https://files.tetras-libre.fr/manifests/sceno_avignon_manifest.json' },
{ manifestId: 'https://files.tetras-libre.fr/manifests/walden_nouvel_manifest.json' },
{ manifestId: 'https://files.tetras-libre.fr/manifests/walden_nouvel2_manifest.json' },
{ manifestId: 'https://files.tetras-libre.fr/manifests/score_manifest.json' },
{ manifestId: 'https://files.tetras-libre.fr/manifests/program_manifest.json' },
],
debugMode: true,
id: 'demo',
window: {
defaultSideBarPanel: 'annotations',
sideBarOpenByDefault: true,
},
catalog: [
{ manifestId: 'https://dzkimgs.l.u-tokyo.ac.jp/videos/iiif_in_japan_2017/manifest.json' },
{ manifestId: 'https://iiif.io/api/cookbook/recipe/0219-using-caption-file/manifest.json' },
{ manifestId: 'https://preview.iiif.io/cookbook/master/recipe/0003-mvm-video/manifest.json' },
{ manifestId: 'https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/manifest.json' },
{ manifestId: 'https://iiif.io/api/cookbook/recipe/0064-opera-one-canvas/manifest.json' },
{ manifestId: 'https://iiif.io/api/cookbook/recipe/0074-multiple-language-captions/manifest.json' },
{ manifestId: 'https://iiif.harvardartmuseums.org/manifests/object/299843' },
{ manifestId: 'https://iiif.io/api/cookbook/recipe/0002-mvm-audio/manifest.json' },
]
windows: [
],
};
mirador.viewer(config, [...annotationPlugins]);
version: "3"
services:
front:
build:
context: "docker/"
volumes:
- ${PWD}:/app
environment:
ENV:
FROM node:16
EXPOSE 3000
VOLUME /app
WORKDIR /app
RUN npm install -g serve
COPY entrypoint.sh /
USER node
ENTRYPOINT ["/entrypoint.sh"]
#!/bin/bash
# Remove all node stuff to avoid arror on docker rebuild
rm -rf node_modules
rm -f package-lock.json
rm -f .babelrc
npm install
./cli post_install
if [ "$ENV" == "prod" ]; then
npm run build
cmd="serve -s demo/dist"
else
cmd="npm start"
fi
if [ ! -z "$1" ]; then
cmd=$@
fi
exec $cmd
version: "3"
services:
front:
ports:
- ${PORT}:3000
version: '3'
services:
front:
networks:
- traefik
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik"
- "traefik.http.routers.${NAME}.rule=Host(${HOST})"
- "traefik.http.routers.${NAME}.tls.certresolver=myresolver"
- "traefik.http.routers.${NAME}.entrypoints=web,websecure"
- "traefik.http.routers.${NAME}.middlewares=hardening@docker"
networks:
traefik:
external: true
This diff is collapsed.
......@@ -14,6 +14,7 @@
"build": "nwb build-react-component",
"clean": "nwb clean-module",
"lint": "eslint ./src ./__tests__",
"prepare": "npm run build",
"prepublishOnly": "npm run build",
"start": "nwb serve-react-demo",
"test": "npm run lint && jest",
......@@ -22,38 +23,49 @@
"test:ci": "jest --ci --reporters=default --reporters=jest-junit --watchAll=false"
},
"dependencies": {
"@psychobolt/react-paperjs": "< 1.0",
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
"@mui/system": "^5.15.1",
"@psychobolt/react-paperjs": "^1.0.3",
"@psychobolt/react-paperjs-editor": "0.0.11",
"axios": "^1.6.7",
"draft-js": "^0.11.6",
"draft-js-export-html": "^1.4.1",
"draft-js-import-html": "^1.4.1",
"material-ui-color-components": "^0.3.0",
"paper": "^0.12.11",
"react-color": "^2.18.1"
"react-color": "^2.18.1",
"react-konva": ">=17.0.1-3 <18.0.0",
"react-konva-to-svg": "^1.0.2",
"react-quill": "^2.0.0",
"react-redux": "8.1.3",
"react-resize-observer": "^1.1.1",
"react-sortablejs": "^6.1.4",
"redux": "^4.2.1",
"sortablejs": "^1.15.2",
"use-image": "^1.1.1"
},
"peerDependencies": {
"@material-ui/core": "^4.9.13",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "^4.0.0-alpha.52",
"lodash": "^4.17.11",
"mirador": "git+https://gitlab.tetras-libre.fr/iiif/mirador-video-annotation#annotation-on-video",
"mirador": "git+https://gitlab.tetras-libre.fr/iiif/mirador/mirador-video#mui5-annotation-on-video-stable",
"prop-types": "^15.7.2",
"react": "^16.8",
"react-dom": "^16.8",
"react": "^17.0.0",
"react-dom": "^17.0.0",
"uuid": "^8.0.0"
},
"devDependencies": {
"@babel/core": "^7.10.4",
"@babel/eslint-parser": "^7.19.1",
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@babel/preset-env": "^7.10.4",
"@babel/preset-react": "^7.10.4",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "^4.0.0-alpha.56",
"@mui/icons-material": "^5.11.16",
"@mui/lab": "^5.0.0-alpha.134",
"@mui/material": "^5.13.5",
"@mui/utils": "^5.13.1",
"@mui/x-tree-view": "^6.17.0",
"canvas": "^2.6.1",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.2",
"eslint": "^8.11.0",
"eslint": "^8.56.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-react-app": "^7.0.0",
"eslint-plugin-flowtype": "^8.0.3",
......@@ -65,11 +77,11 @@
"jest-canvas-mock": "^2.2.0",
"jest-junit": "^15.0.0",
"jest-localstorage-mock": "^2.4.2",
"mirador": "git+https://gitlab.tetras-libre.fr/iiif/mirador-video-annotation#annotation-on-video",
"mirador": "git+https://gitlab.tetras-libre.fr/iiif/mirador/mirador-video#mui5-annotation-on-video-stable",
"nwb": "^0.24.7",
"prop-types": "^15.7.2",
"react": "^16.8",
"react-dom": "^16.8",
"react": "^17.0.0",
"react-dom": "^17.0",
"uuid": "^8.2.0"
},
"author": "",
......