mirador-annotation-editor-video 1.0.99 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -35
- package/demo/src/index.js +8 -1
- package/es/TextEditor.js +7 -14
- package/es/annotationForm/AnnotationFormBody.js +1 -1
- package/es/annotationForm/MultipleBodyTemplate.js +6 -5
- package/es/annotationForm/TextCommentInput.js +71 -0
- package/es/locales/locales_en.js +1 -0
- package/es/locales/locales_fr.js +1 -0
- package/package.json +2 -2
- package/src/TextEditor.js +7 -12
- package/src/annotationForm/AnnotationFormBody.js +1 -1
- package/src/annotationForm/MultiTagsInput.js +0 -5
- package/src/annotationForm/MultipleBodyTemplate.js +6 -5
- package/src/annotationForm/TextCommentInput.js +72 -0
- package/src/locales/locales_en.js +1 -0
- package/src/locales/locales_fr.js +1 -0
package/README.md
CHANGED
|
@@ -4,57 +4,54 @@
|
|
|
4
4
|
|
|
5
5
|
### Generalities
|
|
6
6
|
|
|
7
|
-
`mirador-annotation-editor-video`(also known as "MAEV") is a [Mirador 4](https://github.com/projectmirador/mirador)
|
|
7
|
+
`mirador-annotation-editor-video`(also known as "MAEV") is a [Mirador 4](https://github.com/projectmirador/mirador)
|
|
8
|
+
plugin that
|
|
8
9
|
adds annotation creation tools to the user interface. It support both image and video annotation.
|
|
9
10
|
|
|
10
11
|
### Copyrights
|
|
11
12
|
|
|
13
|
+
Originally forked from https://github.com/ARVEST-APP/mirador-annotation-editor-video
|
|
14
|
+
|
|
12
15
|
#### Licence
|
|
13
16
|
|
|
14
17
|
This plugin is released under the **GPL v3** license unlike MAE and the original plugin.
|
|
15
18
|
|
|
16
|
-
Please acknowledge that any modification you make must be distributed under a compatible licence and cannot be closed
|
|
19
|
+
Please acknowledge that any modification you make must be distributed under a compatible licence and cannot be closed
|
|
17
20
|
source.
|
|
18
21
|
|
|
19
|
-
If you need to integrate this code base in closed source pieces of software, please contact us, so we can discuss dual
|
|
20
|
-
licencing.
|
|
21
|
-
|
|
22
|
-
#### Property
|
|
23
|
-
|
|
24
|
-
The base of this software (up to V1) is the property of [SATT Ouest Valorisation](https://www.ouest-valorisation.fr/)
|
|
25
|
-
that funded its development under the French public contract AO-MA2023-0004-DV5189.
|
|
22
|
+
If you need to integrate this code base in closed source pieces of software, please contact us, so we can discuss dual
|
|
23
|
+
licencing.
|
|
26
24
|
|
|
27
|
-
#### Authors
|
|
25
|
+
#### Authors
|
|
28
26
|
|
|
29
27
|
The authors of this software are :
|
|
30
28
|
|
|
31
29
|
- Clarisse Bardiot (concept and use cases)
|
|
32
30
|
- Jacob Hart (specifications)
|
|
33
31
|
- [Tétras Libre SARL](https://tetras-libre.fr) (development):
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
- David Rouquet
|
|
33
|
+
- Anthony Geourjon
|
|
34
|
+
- Antoine Roy
|
|
37
35
|
|
|
38
36
|
#### Contributors (updated february 2024)
|
|
39
37
|
|
|
40
|
-
- AZOPSOFT SAS
|
|
41
|
-
|
|
42
|
-
- Loïs Poujade (especially the original modifications to annotate videos)
|
|
38
|
+
- AZOPSOFT SAS
|
|
39
|
+
- Samuel Jugnet (especially code for the Konvas part)
|
|
43
40
|
|
|
44
|
-
### General functionalities
|
|
41
|
+
### General functionalities
|
|
45
42
|
|
|
46
|
-
- Activate a panel with tools to create annotations on IIIF documents (manifests) containing images **and videos with
|
|
47
|
-
MAEV**
|
|
43
|
+
- Activate a panel with tools to create annotations on IIIF documents (manifests) containing images **and videos with
|
|
44
|
+
MAEV**
|
|
48
45
|
- Spatial and temporal targets for annotations
|
|
49
46
|
- Overlay annotations (geometric forms, free hand drawing, text and images)
|
|
50
47
|
- Textual/semantic annotations and tags
|
|
51
48
|
- Annotation metadata (based on Dublin Core)
|
|
52
49
|
- Annotation with another manifest -> network of IIIF documents
|
|
53
50
|
|
|
54
|
-
### Technical aspects
|
|
51
|
+
### Technical aspects
|
|
55
52
|
|
|
56
53
|
- Update to Material UI 5 and React 18 to follow latest Mirador upgrades (We support mirador": "4.0.0-alpha.2",
|
|
57
|
-
- The [paperjs](http://paperjs.org/ ) library has been replaced with [Konvas](https://konvajs.org)
|
|
54
|
+
- The [paperjs](http://paperjs.org/ ) library has been replaced with [Konvas](https://konvajs.org)
|
|
58
55
|
- Major refactoring since the original `[mirador-annotations](https://github.com/ProjectMirador/mirador-annotations/)
|
|
59
56
|
plugins`
|
|
60
57
|
- Works with the original [Mirador 4](https://github.com/projectmirador/mirador) if you need only image annotation
|
|
@@ -70,18 +67,22 @@ npm install mirador-annotation-editor
|
|
|
70
67
|
You can override existing annotation plugin with your own versions by using npm. We support React 18 and MUI 5.
|
|
71
68
|
|
|
72
69
|
Update your `package.json` file to include the following dependencies and devDependencies:
|
|
70
|
+
|
|
73
71
|
```js
|
|
74
|
-
"mirador-annotations"
|
|
72
|
+
"mirador-annotations"
|
|
73
|
+
:
|
|
74
|
+
"npm:mirador-annotation-editor-video@^1.0.10",
|
|
75
75
|
```
|
|
76
76
|
|
|
77
77
|
You need also to use the custom version of Mirador 4.
|
|
78
78
|
|
|
79
79
|
```js
|
|
80
|
-
"mirador"
|
|
80
|
+
"mirador"
|
|
81
|
+
:
|
|
82
|
+
"npm@mirador-video@^1.0.17",
|
|
81
83
|
```
|
|
82
84
|
|
|
83
|
-
If you encounter this error :
|
|
84
|
-
|
|
85
|
+
If you encounter this error :
|
|
85
86
|
|
|
86
87
|
## Install (local)
|
|
87
88
|
|
|
@@ -101,23 +102,23 @@ npm start
|
|
|
101
102
|
```
|
|
102
103
|
|
|
103
104
|
## Use MAE with video annotation support
|
|
104
|
-
- If you need video annotation, you can use
|
|
105
|
-
[our fork of Mirador: mirador-video](https://github.com/SCENE-CE/mirador-video)
|
|
106
|
-
- In addition, we have developed a wrapper of MAE to support video annotation. This wrapper is called **MAEV** and is
|
|
107
|
-
available in the [mirador-annotation-editor-video](https://github.com/SCENE-CE/mirador-annotation-editor-video)
|
|
108
|
-
repository.
|
|
109
105
|
|
|
106
|
+
- If you need video annotation, you can use
|
|
107
|
+
[our fork of Mirador: mirador-video](https://github.com/SCENE-CE/mirador-video)
|
|
108
|
+
- In addition, we have developed a wrapper of MAE to support video annotation. This wrapper is called **MAEV** and is
|
|
109
|
+
available in the [mirador-annotation-editor-video](https://github.com/SCENE-CE/mirador-annotation-editor-video)
|
|
110
|
+
repository.
|
|
110
111
|
|
|
111
112
|
## Persisting Annotations
|
|
112
|
-
|
|
113
|
+
|
|
114
|
+
Persisting annotations requires implementing a IIIF annotation server. Several
|
|
113
115
|
[examples of annotation servers](https://github.com/IIIF/awesome-iiif#annotation-servers) are available on iiif-awesome.
|
|
114
116
|
|
|
115
|
-
`mirador-annotation-editor` currently supports adapters for
|
|
116
|
-
[annotot](https://github.com/ProjectMirador/mirador-annotations/blob/master/src/AnnototAdapter.js) and
|
|
117
|
-
[local storage](https://github.com/ProjectMirador/mirador-annotations/blob/master/src/LocalStorageAdapter.js). We
|
|
117
|
+
`mirador-annotation-editor` currently supports adapters for
|
|
118
|
+
[annotot](https://github.com/ProjectMirador/mirador-annotations/blob/master/src/AnnototAdapter.js) and
|
|
119
|
+
[local storage](https://github.com/ProjectMirador/mirador-annotations/blob/master/src/LocalStorageAdapter.js). We
|
|
118
120
|
welcome contributions of adapters for other annotation servers.
|
|
119
121
|
|
|
120
|
-
|
|
121
122
|
## Contribute
|
|
122
123
|
|
|
123
124
|
Our plugin follow the Mirador guidelines. Development, design, and maintenance is driven by community needs and ongoing
|
package/demo/src/index.js
CHANGED
|
@@ -6,7 +6,14 @@ import { manifestsCatalog } from './manifestsCatalog';
|
|
|
6
6
|
const config = {
|
|
7
7
|
annotation: {
|
|
8
8
|
adapter: (canvasId) => new LocalStorageAdapter(`localStorage://?canvasId=${canvasId}`, 'Anonymous User'),
|
|
9
|
-
|
|
9
|
+
commentTemplates: [{
|
|
10
|
+
title: 'Template',
|
|
11
|
+
content: '<h4>Comment</h4><p>comment content</p>',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
title: 'Template 2',
|
|
15
|
+
content: '<h4>Comment2</h4><p>comment content</p>',
|
|
16
|
+
}],
|
|
10
17
|
exportLocalStorageAnnotations: false, // display annotation JSON export button
|
|
11
18
|
tagsSuggestions: ['Mirador', 'Awesome', 'Viewer', 'IIIF'],
|
|
12
19
|
},
|
package/es/TextEditor.js
CHANGED
|
@@ -4,14 +4,12 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.default = void 0;
|
|
7
|
-
var _react =
|
|
7
|
+
var _react = _interopRequireDefault(require("react"));
|
|
8
8
|
var _propTypes = _interopRequireDefault(require("prop-types"));
|
|
9
9
|
var _reactQuill = _interopRequireDefault(require("react-quill"));
|
|
10
10
|
require("react-quill/dist/quill.snow.css");
|
|
11
11
|
var _styles = require("@mui/material/styles");
|
|
12
12
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
13
|
-
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
14
|
-
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
|
15
13
|
const StyledReactQuill = (0, _styles.styled)(_reactQuill.default)(({
|
|
16
14
|
theme
|
|
17
15
|
}) => ({
|
|
@@ -22,20 +20,15 @@ const StyledReactQuill = (0, _styles.styled)(_reactQuill.default)(({
|
|
|
22
20
|
|
|
23
21
|
/** Rich text editor for annotation body */
|
|
24
22
|
function TextEditor({
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
text,
|
|
24
|
+
setText
|
|
27
25
|
}) {
|
|
28
|
-
const [editorHtml, setEditorHtml] = (0, _react.useState)(annoHtml);
|
|
29
|
-
|
|
30
26
|
/**
|
|
31
27
|
* Handle Change On ReactQuil Editor
|
|
32
28
|
* @param html
|
|
33
29
|
*/
|
|
34
30
|
const handleChange = html => {
|
|
35
|
-
|
|
36
|
-
if (updateAnnotationBody) {
|
|
37
|
-
updateAnnotationBody(html);
|
|
38
|
-
}
|
|
31
|
+
setText(html);
|
|
39
32
|
};
|
|
40
33
|
const modules = {
|
|
41
34
|
toolbar: [[{
|
|
@@ -60,7 +53,7 @@ function TextEditor({
|
|
|
60
53
|
return /*#__PURE__*/_react.default.createElement("div", {
|
|
61
54
|
"data-text-editor": "name"
|
|
62
55
|
}, /*#__PURE__*/_react.default.createElement(StyledReactQuill, {
|
|
63
|
-
value:
|
|
56
|
+
value: text,
|
|
64
57
|
onChange: handleChange,
|
|
65
58
|
placeholder: "Your text here",
|
|
66
59
|
bounds: "[data-text-editor=\"name\"]",
|
|
@@ -69,7 +62,7 @@ function TextEditor({
|
|
|
69
62
|
}));
|
|
70
63
|
}
|
|
71
64
|
TextEditor.propTypes = {
|
|
72
|
-
|
|
73
|
-
|
|
65
|
+
setText: _propTypes.default.func.isRequired,
|
|
66
|
+
text: _propTypes.default.string.isRequired
|
|
74
67
|
};
|
|
75
68
|
var _default = exports.default = TextEditor;
|
|
@@ -88,7 +88,7 @@ function AnnotationFormBody({
|
|
|
88
88
|
saveAnnotation: saveAnnotation,
|
|
89
89
|
t: t,
|
|
90
90
|
windowId: windowId,
|
|
91
|
-
commentTemplate: config?.annotation?.
|
|
91
|
+
commentTemplate: config?.annotation?.commentTemplates ?? [],
|
|
92
92
|
tagsSuggestions: config?.annotation?.tagsSuggestions ?? []
|
|
93
93
|
})), debugMode && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_Typography.default, null, playerReferences.getMediaType()), /*#__PURE__*/_react.default.createElement(_Typography.default, null, t('scale'), ":", playerReferences.getScale()), /*#__PURE__*/_react.default.createElement(_Typography.default, null, t('zoom'), ":", playerReferences.getZoom()), /*#__PURE__*/_react.default.createElement(_Typography.default, null, t('image_true_size'), ":", playerReferences.getMediaTrueWidth(), ' ', "x", playerReferences.getMediaTrueHeight()), /*#__PURE__*/_react.default.createElement(_Typography.default, null, t('container_size'), ":", playerReferences.getContainerWidth(), ' ', "x", playerReferences.getContainerHeight()), /*#__PURE__*/_react.default.createElement(_Typography.default, null, t('image_displayed'), ":", playerReferences.getDisplayedMediaWidth(), ' ', "x", playerReferences.getDisplayedMediaHeight())));
|
|
94
94
|
}
|
|
@@ -11,8 +11,8 @@ var _AnnotationFormFooter = _interopRequireDefault(require("./AnnotationFormFoot
|
|
|
11
11
|
var _AnnotationFormUtils = require("./AnnotationFormUtils");
|
|
12
12
|
var _TargetFormSection = _interopRequireDefault(require("./TargetFormSection"));
|
|
13
13
|
var _KonvaUtils = require("./AnnotationFormOverlay/KonvaDrawing/KonvaUtils");
|
|
14
|
-
var _TextFormSection = _interopRequireDefault(require("./TextFormSection"));
|
|
15
14
|
var _MultiTagsInput = require("./MultiTagsInput");
|
|
15
|
+
var _TextCommentInput = require("./TextCommentInput");
|
|
16
16
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
17
17
|
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
18
18
|
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
|
@@ -39,7 +39,7 @@ function MultipleBodyTemplate({
|
|
|
39
39
|
textBody: {
|
|
40
40
|
purpose: 'describing',
|
|
41
41
|
type: 'TextualBody',
|
|
42
|
-
value:
|
|
42
|
+
value: ''
|
|
43
43
|
}
|
|
44
44
|
},
|
|
45
45
|
motivation: 'commenting',
|
|
@@ -106,9 +106,10 @@ function MultipleBodyTemplate({
|
|
|
106
106
|
spacing: 2
|
|
107
107
|
}, /*#__PURE__*/_react.default.createElement(_material.Grid, {
|
|
108
108
|
item: true
|
|
109
|
-
}, /*#__PURE__*/_react.default.createElement(
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
}, /*#__PURE__*/_react.default.createElement(_TextCommentInput.TextCommentInput, {
|
|
110
|
+
commentTemplates: commentTemplate,
|
|
111
|
+
comment: annotationState.maeData.textBody.value,
|
|
112
|
+
setComment: updateAnnotationTextualBodyValue,
|
|
112
113
|
t: t
|
|
113
114
|
})), /*#__PURE__*/_react.default.createElement(_material.Grid, {
|
|
114
115
|
item: true
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.TextCommentInput = TextCommentInput;
|
|
7
|
+
var _react = _interopRequireDefault(require("react"));
|
|
8
|
+
var _propTypes = _interopRequireDefault(require("prop-types"));
|
|
9
|
+
var _material = require("@mui/material");
|
|
10
|
+
var _creatable = _interopRequireDefault(require("react-select/creatable"));
|
|
11
|
+
var _TextEditor = _interopRequireDefault(require("../TextEditor"));
|
|
12
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
13
|
+
/**
|
|
14
|
+
* TextCommentInput component
|
|
15
|
+
* @param commentTemplates - The list of comment templates
|
|
16
|
+
* @param comment - The current comment
|
|
17
|
+
* @param setComment - Function to set the comment
|
|
18
|
+
* @param t - Translation function
|
|
19
|
+
* @constructor
|
|
20
|
+
*/
|
|
21
|
+
function TextCommentInput({
|
|
22
|
+
commentTemplates,
|
|
23
|
+
comment,
|
|
24
|
+
setComment,
|
|
25
|
+
t
|
|
26
|
+
}) {
|
|
27
|
+
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_material.Grid, {
|
|
28
|
+
container: true,
|
|
29
|
+
item: true
|
|
30
|
+
}, /*#__PURE__*/_react.default.createElement(_material.Typography, {
|
|
31
|
+
variant: "formSectionTitle"
|
|
32
|
+
}, t('note'))), commentTemplates.length > 0 && /*#__PURE__*/_react.default.createElement(_material.Grid, {
|
|
33
|
+
item: true,
|
|
34
|
+
style: {
|
|
35
|
+
marginBottom: '10px'
|
|
36
|
+
}
|
|
37
|
+
}, /*#__PURE__*/_react.default.createElement(_creatable.default, {
|
|
38
|
+
options: commentTemplates.map(template => ({
|
|
39
|
+
label: template.title,
|
|
40
|
+
value: template.content,
|
|
41
|
+
title: template.content // Add title attribute for tooltip
|
|
42
|
+
})),
|
|
43
|
+
placeholder: t('useTemplate'),
|
|
44
|
+
onChange: selectedOption => {
|
|
45
|
+
if (selectedOption) {
|
|
46
|
+
setComment(selectedOption.value);
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
isClearable: true,
|
|
50
|
+
isSearchable: true,
|
|
51
|
+
formatOptionLabel: option => /*#__PURE__*/_react.default.createElement("div", {
|
|
52
|
+
title: option.title
|
|
53
|
+
}, option.label),
|
|
54
|
+
styles: {
|
|
55
|
+
marginBottom: '20px'
|
|
56
|
+
}
|
|
57
|
+
})), /*#__PURE__*/_react.default.createElement(_material.Grid, {
|
|
58
|
+
container: true,
|
|
59
|
+
item: true
|
|
60
|
+
}, /*#__PURE__*/_react.default.createElement(_TextEditor.default, {
|
|
61
|
+
text: comment,
|
|
62
|
+
setText: setComment
|
|
63
|
+
})));
|
|
64
|
+
}
|
|
65
|
+
TextCommentInput.propTypes = {
|
|
66
|
+
comment: _propTypes.default.string.isRequired,
|
|
67
|
+
// eslint-disable-next-line react/forbid-prop-types
|
|
68
|
+
commentTemplates: _propTypes.default.arrayOf(_propTypes.default.object).isRequired,
|
|
69
|
+
setComment: _propTypes.default.func.isRequired,
|
|
70
|
+
t: _propTypes.default.func.isRequired
|
|
71
|
+
};
|
package/es/locales/locales_en.js
CHANGED
|
@@ -87,6 +87,7 @@ const en = exports.en = {
|
|
|
87
87
|
textual_note_with_target: 'Textual note with target',
|
|
88
88
|
tool_selection: 'Tool Selection',
|
|
89
89
|
unsupported_media_message: 'Your current canvas media type is not supported by the annotation editor.',
|
|
90
|
+
useTemplate: 'Use a template',
|
|
90
91
|
video_annotation_instruction: 'If you want to annotate video media, you must install MAEV to create and edit annotations on video:',
|
|
91
92
|
your_tag_here: 'Your tag here:',
|
|
92
93
|
zoom: 'Zoom'
|
package/es/locales/locales_fr.js
CHANGED
|
@@ -87,6 +87,7 @@ const fr = exports.fr = {
|
|
|
87
87
|
textual_note_with_target: 'Note textuelle avec cible',
|
|
88
88
|
tool_selection: 'Sélection d’outil',
|
|
89
89
|
unsupported_media_message: 'Le type de média de votre canvas actuel n\'est pas pris en charge par l\'éditeur d\'annotations.',
|
|
90
|
+
useTemplate: 'Utiliser un modèle',
|
|
90
91
|
video_annotation_instruction: 'Si vous souhaitez annoter des vidéos, vous devez installer le plugin Mirador Annotation Editor Video (MAEV) pour créer et modifier des annotations sur les vidéos :',
|
|
91
92
|
your_tag_here: 'Votre étiquette',
|
|
92
93
|
zoom: 'Zoom'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mirador-annotation-editor-video",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Mirador annotation editor video plugin in a React component. Mirador 4 (Alpha 2) compatible ",
|
|
5
5
|
"main": "es/index.js",
|
|
6
6
|
"module": "es/index.js",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"prop-types": "^15.7.2",
|
|
64
64
|
"react": "^18.2.0",
|
|
65
65
|
"react-dom": "^18.0.0",
|
|
66
|
-
"uuid": "^
|
|
66
|
+
"uuid": "^11.0.0"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"@babel/cli": "^7.25.9",
|
package/src/TextEditor.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import ReactQuill from 'react-quill';
|
|
4
4
|
import 'react-quill/dist/quill.snow.css';
|
|
@@ -12,20 +12,15 @@ const StyledReactQuill = styled(ReactQuill)(({ theme }) => ({
|
|
|
12
12
|
|
|
13
13
|
/** Rich text editor for annotation body */
|
|
14
14
|
function TextEditor({
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
text,
|
|
16
|
+
setText,
|
|
17
17
|
}) {
|
|
18
|
-
const [editorHtml, setEditorHtml] = useState(annoHtml);
|
|
19
|
-
|
|
20
18
|
/**
|
|
21
19
|
* Handle Change On ReactQuil Editor
|
|
22
20
|
* @param html
|
|
23
21
|
*/
|
|
24
22
|
const handleChange = (html) => {
|
|
25
|
-
|
|
26
|
-
if (updateAnnotationBody) {
|
|
27
|
-
updateAnnotationBody(html);
|
|
28
|
-
}
|
|
23
|
+
setText(html);
|
|
29
24
|
};
|
|
30
25
|
const modules = {
|
|
31
26
|
toolbar: [
|
|
@@ -63,7 +58,7 @@ function TextEditor({
|
|
|
63
58
|
return (
|
|
64
59
|
<div data-text-editor="name">
|
|
65
60
|
<StyledReactQuill
|
|
66
|
-
value={
|
|
61
|
+
value={text}
|
|
67
62
|
onChange={handleChange}
|
|
68
63
|
placeholder="Your text here"
|
|
69
64
|
bounds='[data-text-editor="name"]'
|
|
@@ -75,8 +70,8 @@ function TextEditor({
|
|
|
75
70
|
}
|
|
76
71
|
|
|
77
72
|
TextEditor.propTypes = {
|
|
78
|
-
|
|
79
|
-
|
|
73
|
+
setText: PropTypes.func.isRequired,
|
|
74
|
+
text: PropTypes.string.isRequired,
|
|
80
75
|
};
|
|
81
76
|
|
|
82
77
|
export default TextEditor;
|
|
@@ -112,7 +112,7 @@ export default function AnnotationFormBody(
|
|
|
112
112
|
saveAnnotation={saveAnnotation}
|
|
113
113
|
t={t}
|
|
114
114
|
windowId={windowId}
|
|
115
|
-
commentTemplate={config?.annotation?.
|
|
115
|
+
commentTemplate={config?.annotation?.commentTemplates ?? []}
|
|
116
116
|
tagsSuggestions={config?.annotation?.tagsSuggestions ?? []}
|
|
117
117
|
/>
|
|
118
118
|
)}
|
|
@@ -28,9 +28,6 @@ export function MultiTagsInput({
|
|
|
28
28
|
<Typography variant="formSectionTitle">
|
|
29
29
|
{t('tags')}
|
|
30
30
|
</Typography>
|
|
31
|
-
{/* Show list of suggestions into a clickable tag */}
|
|
32
|
-
{/* add a toggle to show hide suggestions */}
|
|
33
|
-
|
|
34
31
|
<CreatableSelect
|
|
35
32
|
isMulti
|
|
36
33
|
options={mappedSuggestionsTags}
|
|
@@ -39,11 +36,9 @@ export function MultiTagsInput({
|
|
|
39
36
|
closeMenuOnSelect={false}
|
|
40
37
|
placeholder={t('tagsPlaceholder')}
|
|
41
38
|
/>
|
|
42
|
-
|
|
43
39
|
<Divider
|
|
44
40
|
spacing={2}
|
|
45
41
|
/>
|
|
46
|
-
|
|
47
42
|
</>
|
|
48
43
|
);
|
|
49
44
|
}
|
|
@@ -5,8 +5,8 @@ import AnnotationFormFooter from './AnnotationFormFooter';
|
|
|
5
5
|
import { TEMPLATE } from './AnnotationFormUtils';
|
|
6
6
|
import TargetFormSection from './TargetFormSection';
|
|
7
7
|
import { resizeKonvaStage } from './AnnotationFormOverlay/KonvaDrawing/KonvaUtils';
|
|
8
|
-
import TextFormSection from './TextFormSection';
|
|
9
8
|
import { MultiTagsInput } from './MultiTagsInput';
|
|
9
|
+
import { TextCommentInput } from './TextCommentInput';
|
|
10
10
|
|
|
11
11
|
/** Tagging Template* */
|
|
12
12
|
export default function MultipleBodyTemplate(
|
|
@@ -34,7 +34,7 @@ export default function MultipleBodyTemplate(
|
|
|
34
34
|
textBody: {
|
|
35
35
|
purpose: 'describing',
|
|
36
36
|
type: 'TextualBody',
|
|
37
|
-
value:
|
|
37
|
+
value: '',
|
|
38
38
|
},
|
|
39
39
|
},
|
|
40
40
|
motivation: 'commenting',
|
|
@@ -108,9 +108,10 @@ export default function MultipleBodyTemplate(
|
|
|
108
108
|
return (
|
|
109
109
|
<Grid container direction="column" spacing={2}>
|
|
110
110
|
<Grid item>
|
|
111
|
-
<
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
<TextCommentInput
|
|
112
|
+
commentTemplates={commentTemplate}
|
|
113
|
+
comment={annotationState.maeData.textBody.value}
|
|
114
|
+
setComment={updateAnnotationTextualBodyValue}
|
|
114
115
|
t={t}
|
|
115
116
|
/>
|
|
116
117
|
</Grid>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { Grid, Typography } from '@mui/material';
|
|
4
|
+
import CreatableSelect from 'react-select/creatable';
|
|
5
|
+
import TextEditor from '../TextEditor';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* TextCommentInput component
|
|
9
|
+
* @param commentTemplates - The list of comment templates
|
|
10
|
+
* @param comment - The current comment
|
|
11
|
+
* @param setComment - Function to set the comment
|
|
12
|
+
* @param t - Translation function
|
|
13
|
+
* @constructor
|
|
14
|
+
*/
|
|
15
|
+
export function TextCommentInput({
|
|
16
|
+
commentTemplates,
|
|
17
|
+
comment,
|
|
18
|
+
setComment,
|
|
19
|
+
t,
|
|
20
|
+
}) {
|
|
21
|
+
return (
|
|
22
|
+
<>
|
|
23
|
+
<Grid container item>
|
|
24
|
+
<Typography variant="formSectionTitle">
|
|
25
|
+
{t('note')}
|
|
26
|
+
</Typography>
|
|
27
|
+
</Grid>
|
|
28
|
+
{commentTemplates.length > 0 && (
|
|
29
|
+
<Grid item style={{ marginBottom: '10px' }}>
|
|
30
|
+
<CreatableSelect
|
|
31
|
+
options={commentTemplates.map((template) => ({
|
|
32
|
+
label: template.title,
|
|
33
|
+
value: template.content,
|
|
34
|
+
title: template.content, // Add title attribute for tooltip
|
|
35
|
+
}))}
|
|
36
|
+
placeholder={t('useTemplate')}
|
|
37
|
+
onChange={(selectedOption) => {
|
|
38
|
+
if (selectedOption) {
|
|
39
|
+
setComment(selectedOption.value);
|
|
40
|
+
}
|
|
41
|
+
}}
|
|
42
|
+
isClearable
|
|
43
|
+
isSearchable
|
|
44
|
+
formatOptionLabel={(option) => (
|
|
45
|
+
<div title={option.title}>
|
|
46
|
+
{option.label}
|
|
47
|
+
</div>
|
|
48
|
+
)}
|
|
49
|
+
styles={{
|
|
50
|
+
marginBottom: '20px',
|
|
51
|
+
}}
|
|
52
|
+
/>
|
|
53
|
+
</Grid>
|
|
54
|
+
)}
|
|
55
|
+
|
|
56
|
+
<Grid container item>
|
|
57
|
+
<TextEditor
|
|
58
|
+
text={comment}
|
|
59
|
+
setText={setComment}
|
|
60
|
+
/>
|
|
61
|
+
</Grid>
|
|
62
|
+
</>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
TextCommentInput.propTypes = {
|
|
67
|
+
comment: PropTypes.string.isRequired,
|
|
68
|
+
// eslint-disable-next-line react/forbid-prop-types
|
|
69
|
+
commentTemplates: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
70
|
+
setComment: PropTypes.func.isRequired,
|
|
71
|
+
t: PropTypes.func.isRequired,
|
|
72
|
+
};
|
|
@@ -81,6 +81,7 @@ export const en = {
|
|
|
81
81
|
textual_note_with_target: 'Textual note with target',
|
|
82
82
|
tool_selection: 'Tool Selection',
|
|
83
83
|
unsupported_media_message: 'Your current canvas media type is not supported by the annotation editor.',
|
|
84
|
+
useTemplate: 'Use a template',
|
|
84
85
|
video_annotation_instruction: 'If you want to annotate video media, you must install MAEV to create and edit annotations on video:',
|
|
85
86
|
your_tag_here: 'Your tag here:',
|
|
86
87
|
zoom: 'Zoom',
|
|
@@ -81,6 +81,7 @@ export const fr = {
|
|
|
81
81
|
textual_note_with_target: 'Note textuelle avec cible',
|
|
82
82
|
tool_selection: 'Sélection d’outil',
|
|
83
83
|
unsupported_media_message: 'Le type de média de votre canvas actuel n\'est pas pris en charge par l\'éditeur d\'annotations.',
|
|
84
|
+
useTemplate: 'Utiliser un modèle',
|
|
84
85
|
video_annotation_instruction: 'Si vous souhaitez annoter des vidéos, vous devez installer le plugin Mirador Annotation Editor Video (MAEV) pour créer et modifier des annotations sur les vidéos :',
|
|
85
86
|
your_tag_here: 'Votre étiquette',
|
|
86
87
|
zoom: 'Zoom',
|