@searpent/react-image-annotate 2.0.1 → 2.0.4
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/package.json +4 -1
- package/src/Annotator/examplePhotos.js +7601 -0
- package/src/Annotator/index.js +83 -3
- package/src/Annotator/index.story.js +74 -25
- package/src/Annotator/reducers/general-reducer.js +99 -17
- package/src/Editor/annotation-plugin/annotation.css +46 -0
- package/src/Editor/annotation-plugin/annotation.js +515 -0
- package/src/Editor/index.js +24 -0
- package/src/Editor/tools.js +6 -0
- package/src/GroupSelectorSidebarBox/index.js +48 -0
- package/src/ImageCanvas/index.js +31 -25
- package/src/ImageCanvas/index.story.js +100 -0
- package/src/MainLayout/index.js +250 -160
- package/src/MainLayout/types.js +64 -56
- package/src/MetadataEditorSidebarBox/index.js +98 -0
- package/src/PageSelector/index.js +76 -0
- package/src/PageSelector/page-selector.css +69 -0
- package/src/RegionLabel/index.js +21 -0
- package/src/RegionShapes/index.js +34 -16
- package/src/RegionTags/index.js +7 -3
- package/src/SettingsProvider/index.js +7 -3
- package/src/hooks/use-colors.js +74 -0
- package/src/utils/filter-only-unique.js +5 -0
- package/src/utils/photosToImages.js +40 -0
- package/src/utils/regions-to-blocks.js +14 -0
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build styles
|
|
3
|
+
*/
|
|
4
|
+
require('./annotation.css').toString();
|
|
5
|
+
|
|
6
|
+
// Possible classes
|
|
7
|
+
// ================
|
|
8
|
+
|
|
9
|
+
class Annotation {
|
|
10
|
+
/**
|
|
11
|
+
* Render plugin`s main Element and fill it with saved data
|
|
12
|
+
*
|
|
13
|
+
* @param {{data: HeaderData, config: HeaderConfig, api: object}}
|
|
14
|
+
* data — previously saved data
|
|
15
|
+
* config - user config for Tool
|
|
16
|
+
* api - Editor.js API
|
|
17
|
+
* readOnly - read only mode flag
|
|
18
|
+
*/
|
|
19
|
+
constructor({ data, config, api, readOnly }) {
|
|
20
|
+
this.api = api;
|
|
21
|
+
this.readOnly = readOnly;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Styles
|
|
25
|
+
*
|
|
26
|
+
* @type {object}
|
|
27
|
+
*/
|
|
28
|
+
this._CSS = {
|
|
29
|
+
block: this.api.styles.block,
|
|
30
|
+
settingsButton: this.api.styles.settingsButton,
|
|
31
|
+
settingsButtonActive: this.api.styles.settingsButtonActive,
|
|
32
|
+
wrapper: 'ce-header'
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Tool's settings passed from Editor
|
|
37
|
+
*
|
|
38
|
+
* @type {HeaderConfig}
|
|
39
|
+
* @private
|
|
40
|
+
*/
|
|
41
|
+
this._settings = config;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Block's data
|
|
45
|
+
*
|
|
46
|
+
* @type {HeaderData}
|
|
47
|
+
* @private
|
|
48
|
+
*/
|
|
49
|
+
this._data = this.normalizeData(data);
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* List of settings buttons
|
|
53
|
+
*
|
|
54
|
+
* @type {HTMLElement[]}
|
|
55
|
+
*/
|
|
56
|
+
this.settingsButtons = [];
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Main Block wrapper
|
|
60
|
+
*
|
|
61
|
+
* @type {HTMLElement}
|
|
62
|
+
* @private
|
|
63
|
+
*/
|
|
64
|
+
this._element = this.getTag();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Normalize input data
|
|
69
|
+
*
|
|
70
|
+
* @param {HeaderData} data - saved data to process
|
|
71
|
+
*
|
|
72
|
+
* @returns {HeaderData}
|
|
73
|
+
* @private
|
|
74
|
+
*/
|
|
75
|
+
normalizeData(data) {
|
|
76
|
+
const newData = {};
|
|
77
|
+
|
|
78
|
+
if (typeof data !== 'object') {
|
|
79
|
+
data = {};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
newData.text = data.text || '';
|
|
83
|
+
newData.labelName = data.labelName || this.defaultLabel.labelName;
|
|
84
|
+
|
|
85
|
+
return newData;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Return Tool's view
|
|
90
|
+
*
|
|
91
|
+
* @returns {HTMLHeadingElement}
|
|
92
|
+
* @public
|
|
93
|
+
*/
|
|
94
|
+
render() {
|
|
95
|
+
return this._element;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Create Block's settings block
|
|
100
|
+
*
|
|
101
|
+
* @returns {HTMLElement}
|
|
102
|
+
*/
|
|
103
|
+
renderSettings() {
|
|
104
|
+
const holder = document.createElement('DIV');
|
|
105
|
+
|
|
106
|
+
// do not add settings button, when only one label is configured
|
|
107
|
+
if (this.labels.length <= 1) {
|
|
108
|
+
return holder;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Add type selectors */
|
|
112
|
+
this.labels.forEach(label => {
|
|
113
|
+
const selectTypeButton = document.createElement('DIV');
|
|
114
|
+
|
|
115
|
+
selectTypeButton.classList.add(this._CSS.settingsButton);
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Highlight current label button
|
|
119
|
+
*/
|
|
120
|
+
if (this.currentLabel.labelName === label.labelName) {
|
|
121
|
+
selectTypeButton.classList.add(this._CSS.settingsButtonActive);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Add SVG icon
|
|
126
|
+
*/
|
|
127
|
+
selectTypeButton.innerHTML = `${label.labelName}`;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Save label to its button
|
|
131
|
+
*/
|
|
132
|
+
selectTypeButton.dataset.labelName = label.labelName;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Set up click handler
|
|
136
|
+
*/
|
|
137
|
+
selectTypeButton.addEventListener('click', () => {
|
|
138
|
+
this.setLabelName(label.labelName);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Append settings button to holder
|
|
143
|
+
*/
|
|
144
|
+
holder.appendChild(selectTypeButton);
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Save settings buttons
|
|
148
|
+
*/
|
|
149
|
+
this.settingsButtons.push(selectTypeButton);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
return holder;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Callback for Block's settings buttons
|
|
157
|
+
*
|
|
158
|
+
* @param {labelName} labelName - labelName to set
|
|
159
|
+
*/
|
|
160
|
+
setLabelName(labelName) {
|
|
161
|
+
this.data = {
|
|
162
|
+
labelName,
|
|
163
|
+
text: this.data.text
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Highlight button by selected labelName
|
|
168
|
+
*/
|
|
169
|
+
this.settingsButtons.forEach(button => {
|
|
170
|
+
button.classList.toggle(
|
|
171
|
+
this._CSS.settingsButtonActive,
|
|
172
|
+
button.dataset.labelName === labelName
|
|
173
|
+
);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Method that specified how to merge two Text blocks.
|
|
179
|
+
* Called by Editor.js by backspace at the beginning of the Block
|
|
180
|
+
*
|
|
181
|
+
* @param {HeaderData} data - saved data to merger with current block
|
|
182
|
+
* @public
|
|
183
|
+
*/
|
|
184
|
+
merge(data) {
|
|
185
|
+
const newData = {
|
|
186
|
+
text: this.data.text + data.text,
|
|
187
|
+
labelName: this.data.labelName
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
this.data = newData;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Validate Text block data:
|
|
195
|
+
* - check for emptiness
|
|
196
|
+
*
|
|
197
|
+
* @param {HeaderData} blockData — data received after saving
|
|
198
|
+
* @returns {boolean} false if saved data is not correct, otherwise true
|
|
199
|
+
* @public
|
|
200
|
+
*/
|
|
201
|
+
validate(blockData) {
|
|
202
|
+
return blockData.text.trim() !== '';
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Extract Tool's data from the view
|
|
207
|
+
*
|
|
208
|
+
* @param {HTMLHeadingElement} toolsContent - Text tools rendered view
|
|
209
|
+
* @returns {HeaderData} - saved data
|
|
210
|
+
* @public
|
|
211
|
+
*/
|
|
212
|
+
save(toolsContent) {
|
|
213
|
+
return {
|
|
214
|
+
text: toolsContent.innerHTML,
|
|
215
|
+
labelName: this.currentLabel.labelName
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Allow Header to be converted to/from other blocks
|
|
221
|
+
*/
|
|
222
|
+
static get conversionConfig() {
|
|
223
|
+
return {
|
|
224
|
+
export: 'text', // use 'text' property for other blocks
|
|
225
|
+
import: 'text' // fill 'text' property from other block's export string
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Sanitizer Rules
|
|
231
|
+
*/
|
|
232
|
+
static get sanitize() {
|
|
233
|
+
return {
|
|
234
|
+
labelName: false,
|
|
235
|
+
text: {}
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Returns true to notify core that read-only is supported
|
|
241
|
+
*
|
|
242
|
+
* @returns {boolean}
|
|
243
|
+
*/
|
|
244
|
+
static get isReadOnlySupported() {
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Get current Tools`s data
|
|
250
|
+
*
|
|
251
|
+
* @returns {HeaderData} Current data
|
|
252
|
+
* @private
|
|
253
|
+
*/
|
|
254
|
+
get data() {
|
|
255
|
+
this._data.text = this._element.innerHTML;
|
|
256
|
+
this._data.labelName = this.currentLabel.labelName;
|
|
257
|
+
|
|
258
|
+
return this._data;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Store data in plugin:
|
|
263
|
+
* - at the this._data property
|
|
264
|
+
* - at the HTML
|
|
265
|
+
*
|
|
266
|
+
* @param {HeaderData} data — data to set
|
|
267
|
+
* @private
|
|
268
|
+
*/
|
|
269
|
+
set data(data) {
|
|
270
|
+
this._data = this.normalizeData(data);
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* If labelName is set and block in DOM
|
|
274
|
+
* then replace it to a new block
|
|
275
|
+
*/
|
|
276
|
+
if (data.labelName !== undefined && this._element.parentNode) {
|
|
277
|
+
/**
|
|
278
|
+
* Create a new tag
|
|
279
|
+
*
|
|
280
|
+
* @type {HTMLHeadingElement}
|
|
281
|
+
*/
|
|
282
|
+
const newHeader = this.getTag();
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Save Block's content
|
|
286
|
+
*/
|
|
287
|
+
newHeader.innerHTML = this._element.innerHTML;
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Replace blocks
|
|
291
|
+
*/
|
|
292
|
+
this._element.parentNode.replaceChild(newHeader, this._element);
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Save new block to private variable
|
|
296
|
+
*
|
|
297
|
+
* @type {HTMLHeadingElement}
|
|
298
|
+
* @private
|
|
299
|
+
*/
|
|
300
|
+
this._element = newHeader;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* If data.text was passed then update block's content
|
|
305
|
+
*/
|
|
306
|
+
if (data.text !== undefined) {
|
|
307
|
+
this._element.innerHTML = this._data.text || '';
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Get tag for target label
|
|
313
|
+
* By default returns second-labelled header
|
|
314
|
+
*
|
|
315
|
+
* @returns {HTMLElement}
|
|
316
|
+
*/
|
|
317
|
+
getTag() {
|
|
318
|
+
/**
|
|
319
|
+
* Create element for current Block's label
|
|
320
|
+
*/
|
|
321
|
+
const tag = document.createElement(this.currentLabel.tag);
|
|
322
|
+
if (this.currentLabel.backgroundColor) {
|
|
323
|
+
tag.style.backgroundColor = this.currentLabel.backgroundColor;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Add text to block
|
|
328
|
+
*/
|
|
329
|
+
tag.innerHTML = this._data.text || '';
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Add styles class
|
|
333
|
+
*/
|
|
334
|
+
tag.classList.add(this._CSS.wrapper);
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Make tag editable
|
|
338
|
+
*/
|
|
339
|
+
tag.contentEditable = this.readOnly ? 'false' : 'true';
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Add Placeholder
|
|
343
|
+
*/
|
|
344
|
+
tag.dataset.placeholder = this.api.i18n.t(this._settings.placeholder || '');
|
|
345
|
+
|
|
346
|
+
return tag;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Get current label
|
|
351
|
+
*
|
|
352
|
+
* @returns {label}
|
|
353
|
+
*/
|
|
354
|
+
get currentLabel() {
|
|
355
|
+
let label = this.labels.find(
|
|
356
|
+
labelItem => labelItem.labelName === this._data.labelName
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
if (!label) {
|
|
360
|
+
label = this.defaultLabel;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return label;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Return default label
|
|
368
|
+
*
|
|
369
|
+
* @returns {label}
|
|
370
|
+
*/
|
|
371
|
+
get defaultLabel() {
|
|
372
|
+
/**
|
|
373
|
+
* User can specify own default label value
|
|
374
|
+
*/
|
|
375
|
+
if (this._settings.defaultLabel) {
|
|
376
|
+
const userSpecified = this.labels.find(labelItem => {
|
|
377
|
+
return labelItem.labelName === this._settings.defaultLabel;
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
if (userSpecified) {
|
|
381
|
+
return userSpecified;
|
|
382
|
+
}
|
|
383
|
+
console.warn(
|
|
384
|
+
"(ง'̀-'́)ง Annotation Tool: the default label specified was not found in available labels"
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* With no additional options, there will be H2 by default
|
|
390
|
+
*
|
|
391
|
+
* @type {label}
|
|
392
|
+
*/
|
|
393
|
+
return this.labels[1];
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* @typedef {object} label
|
|
398
|
+
* @property {labelName} labelName - label labelName
|
|
399
|
+
* @property {string} tag - tag corresponds with label labelName
|
|
400
|
+
* @property {string} svg - icon
|
|
401
|
+
*/
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Available header labels
|
|
405
|
+
*
|
|
406
|
+
* @returns {label[]}
|
|
407
|
+
*/
|
|
408
|
+
get labels() {
|
|
409
|
+
const availableLabels = [
|
|
410
|
+
{
|
|
411
|
+
labelName: 'title',
|
|
412
|
+
tag: 'h1',
|
|
413
|
+
name: 'title'
|
|
414
|
+
// backgroundColor: '#d0fffe'
|
|
415
|
+
},
|
|
416
|
+
{ labelName: 'subtitle', tag: 'h2', name: 'subtitle' },
|
|
417
|
+
{ labelName: 'text', tag: 'p', name: 'text' },
|
|
418
|
+
{ labelName: 'author', tag: 'i', name: 'author' },
|
|
419
|
+
{ labelName: 'appendix', tag: 'p', name: 'appendix' },
|
|
420
|
+
{ labelName: 'photo_author', tag: 'p', name: 'photo_author' },
|
|
421
|
+
{ labelName: 'photo_caption', tag: 'p', name: 'photo_caption' },
|
|
422
|
+
{ labelName: 'advertisement', tag: 'p', name: 'advertisement' },
|
|
423
|
+
{ labelName: 'other_graphics', tag: 'p', name: 'other_graphics' },
|
|
424
|
+
{ labelName: 'unknown', tag: 's', name: 'unknown' },
|
|
425
|
+
{ labelName: 'about_author', tag: 'p', name: 'about_author' },
|
|
426
|
+
{ labelName: 'image', tag: 'p', name: 'image' },
|
|
427
|
+
{ labelName: 'interview', tag: 'p', name: 'interview' },
|
|
428
|
+
{ labelName: 'table', tag: 'p', name: 'table' }
|
|
429
|
+
];
|
|
430
|
+
|
|
431
|
+
return this._settings.labels
|
|
432
|
+
? availableLabels.filter(l => this._settings.labels.includes(l.labelName))
|
|
433
|
+
: availableLabels;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Handle H1-H6 tags on paste to substitute it with header Tool
|
|
438
|
+
*
|
|
439
|
+
* @param {PasteEvent} event - event with pasted content
|
|
440
|
+
*/
|
|
441
|
+
onPaste(event) {
|
|
442
|
+
const content = event.detail.data;
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Define default label value
|
|
446
|
+
*
|
|
447
|
+
* @type {labelName}
|
|
448
|
+
*/
|
|
449
|
+
let { labelName } = this.defaultLabel;
|
|
450
|
+
|
|
451
|
+
switch (content.tagName) {
|
|
452
|
+
case 'H1':
|
|
453
|
+
labelName = 1;
|
|
454
|
+
break;
|
|
455
|
+
case 'H2':
|
|
456
|
+
labelName = 2;
|
|
457
|
+
break;
|
|
458
|
+
case 'H3':
|
|
459
|
+
labelName = 3;
|
|
460
|
+
break;
|
|
461
|
+
case 'H4':
|
|
462
|
+
labelName = 4;
|
|
463
|
+
break;
|
|
464
|
+
case 'H5':
|
|
465
|
+
labelName = 5;
|
|
466
|
+
break;
|
|
467
|
+
case 'H6':
|
|
468
|
+
labelName = 6;
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// if (this._settings.labels) {
|
|
473
|
+
// // Fallback to nearest label when specified not available
|
|
474
|
+
// label = this._settings.labels.reduce((prevLabel, currLabel) => {
|
|
475
|
+
// return Math.abs(currLabel - label) < Math.abs(prevLabel - label)
|
|
476
|
+
// ? currLabel
|
|
477
|
+
// : prevLabel;
|
|
478
|
+
// });
|
|
479
|
+
// }
|
|
480
|
+
|
|
481
|
+
this.data = {
|
|
482
|
+
labelName,
|
|
483
|
+
text: content.innerHTML
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Used by Editor.js paste handling API.
|
|
489
|
+
* Provides configuration to handle H1-H6 tags.
|
|
490
|
+
*
|
|
491
|
+
* @returns {{handler: (function(HTMLElement): {text: string}), tags: string[]}}
|
|
492
|
+
*/
|
|
493
|
+
static get pasteConfig() {
|
|
494
|
+
return {
|
|
495
|
+
tags: ['H1', 'H2', 'H3', 'H4', 'H5', 'H6']
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Get Tool toolbox settings
|
|
501
|
+
* icon - Tool icon's SVG
|
|
502
|
+
* title - title to show in toolbox
|
|
503
|
+
*
|
|
504
|
+
* @returns {{icon: string, title: string}}
|
|
505
|
+
*/
|
|
506
|
+
static get toolbox() {
|
|
507
|
+
return {
|
|
508
|
+
icon:
|
|
509
|
+
'<svg width="17" height="15" viewBox="0 0 336 276" xmlns="http://www.w3.org/2000/svg"><path d="M291 150V79c0-19-15-34-34-34H79c-19 0-34 15-34 34v42l67-44 81 72 56-29 42 30zm0 52l-43-30-56 30-81-67-66 39v23c0 19 15 34 34 34h178c17 0 31-13 34-29zM79 0h178c44 0 79 35 79 79v118c0 44-35 79-79 79H79c-44 0-79-35-79-79V79C0 35 35 0 79 0z"/></svg>',
|
|
510
|
+
title: 'Annotation'
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
export default Annotation;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { createReactEditorJS } from 'react-editor-js'
|
|
3
|
+
import { EDITOR_JS_TOOLS } from './tools'
|
|
4
|
+
|
|
5
|
+
const ReactEditorJS = createReactEditorJS()
|
|
6
|
+
|
|
7
|
+
function Editor({ blocks, onChange, imageIndex }) {
|
|
8
|
+
const handleChange = async instance => {
|
|
9
|
+
const data = await instance.saver.save();
|
|
10
|
+
onChange({ imageIndex, data })
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<ReactEditorJS defaultValue={{
|
|
15
|
+
blocks
|
|
16
|
+
}}
|
|
17
|
+
tools={EDITOR_JS_TOOLS}
|
|
18
|
+
onChange={handleChange}
|
|
19
|
+
enableReInitialize
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default Editor;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import React, { memo } from "react"
|
|
4
|
+
import { makeStyles } from "@mui/styles"
|
|
5
|
+
import { createTheme, ThemeProvider } from "@mui/material/styles"
|
|
6
|
+
import SidebarBoxContainer from "../SidebarBoxContainer"
|
|
7
|
+
import CollectionsIcon from "@mui/icons-material/Collections"
|
|
8
|
+
import { grey } from "@mui/material/colors"
|
|
9
|
+
import List from "@mui/material/List"
|
|
10
|
+
import ListItem from "@mui/material/ListItem"
|
|
11
|
+
import ListItemText from "@mui/material/ListItemText"
|
|
12
|
+
import isEqual from "lodash/isEqual"
|
|
13
|
+
|
|
14
|
+
const theme = createTheme()
|
|
15
|
+
const useStyles = makeStyles((theme) => ({
|
|
16
|
+
img: { width: 40, height: 40, borderRadius: 8 },
|
|
17
|
+
}))
|
|
18
|
+
|
|
19
|
+
export const GroupSelectorSidebarBox = ({ title, subtitle, groups, onSelect, selectedGroupId }) => {
|
|
20
|
+
const classes = useStyles()
|
|
21
|
+
return (
|
|
22
|
+
<ThemeProvider theme={theme}>
|
|
23
|
+
<SidebarBoxContainer
|
|
24
|
+
title={title || ''}
|
|
25
|
+
subTitle={subtitle || ''}
|
|
26
|
+
icon={<CollectionsIcon style={{ color: grey[700] }} />}
|
|
27
|
+
>
|
|
28
|
+
<List>
|
|
29
|
+
{groups.map(({ id, title: groupTitle, subtitle: groupSubtitle, color }, i) => (
|
|
30
|
+
<ListItem button onClick={() => onSelect(id)} dense key={id} style={{
|
|
31
|
+
backgroundColor: id === selectedGroupId ? '#bbdefb' : null
|
|
32
|
+
}}>
|
|
33
|
+
<ListItemText
|
|
34
|
+
primary={<strong style={{
|
|
35
|
+
color
|
|
36
|
+
}}>{groupTitle}</strong>}
|
|
37
|
+
secondary={groupSubtitle}
|
|
38
|
+
/>
|
|
39
|
+
</ListItem>
|
|
40
|
+
))}
|
|
41
|
+
</List>
|
|
42
|
+
|
|
43
|
+
</SidebarBoxContainer>
|
|
44
|
+
</ThemeProvider>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default GroupSelectorSidebarBox
|
package/src/ImageCanvas/index.js
CHANGED
|
@@ -87,6 +87,8 @@ type Props = {
|
|
|
87
87
|
onChangeVideoTime: (number) => any,
|
|
88
88
|
onRegionClassAdded: () => {},
|
|
89
89
|
onChangeVideoPlaying?: Function,
|
|
90
|
+
hideNotEditingLabel?: boolean,
|
|
91
|
+
allowedGroups?: Object
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
const getDefaultMat = (allowedArea = null, { iw, ih } = {}) => {
|
|
@@ -142,6 +144,8 @@ export const ImageCanvas = ({
|
|
|
142
144
|
modifyingAllowedArea = false,
|
|
143
145
|
keypointDefinitions,
|
|
144
146
|
allowComments,
|
|
147
|
+
hideNotEditingLabel = false,
|
|
148
|
+
allowedGroups,
|
|
145
149
|
}: Props) => {
|
|
146
150
|
const classes = useStyles()
|
|
147
151
|
|
|
@@ -288,10 +292,10 @@ export const ImageCanvas = ({
|
|
|
288
292
|
!zoomStart || !zoomEnd
|
|
289
293
|
? null
|
|
290
294
|
: {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
+
...mat.clone().inverse().applyToPoint(zoomStart.x, zoomStart.y),
|
|
296
|
+
w: (zoomEnd.x - zoomStart.x) / mat.a,
|
|
297
|
+
h: (zoomEnd.y - zoomStart.y) / mat.d,
|
|
298
|
+
}
|
|
295
299
|
if (zoomBox) {
|
|
296
300
|
if (zoomBox.w < 0) {
|
|
297
301
|
zoomBox.x += zoomBox.w
|
|
@@ -326,14 +330,14 @@ export const ImageCanvas = ({
|
|
|
326
330
|
cursor: createWithPrimary
|
|
327
331
|
? "crosshair"
|
|
328
332
|
: dragging
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
333
|
+
? "grabbing"
|
|
334
|
+
: dragWithPrimary
|
|
335
|
+
? "grab"
|
|
336
|
+
: zoomWithPrimary
|
|
337
|
+
? mat.a < 1
|
|
338
|
+
? "zoom-out"
|
|
339
|
+
: "zoom-in"
|
|
340
|
+
: undefined,
|
|
337
341
|
}}
|
|
338
342
|
>
|
|
339
343
|
{showCrosshairs && (
|
|
@@ -346,19 +350,19 @@ export const ImageCanvas = ({
|
|
|
346
350
|
!modifyingAllowedArea || !allowedArea
|
|
347
351
|
? regions
|
|
348
352
|
: [
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
353
|
+
{
|
|
354
|
+
type: "box",
|
|
355
|
+
id: "$$allowed_area",
|
|
356
|
+
cls: "allowed_area",
|
|
357
|
+
highlighted: true,
|
|
358
|
+
x: allowedArea.x,
|
|
359
|
+
y: allowedArea.y,
|
|
360
|
+
w: allowedArea.w,
|
|
361
|
+
h: allowedArea.h,
|
|
362
|
+
visible: true,
|
|
363
|
+
color: "#ff0",
|
|
364
|
+
},
|
|
365
|
+
]
|
|
362
366
|
}
|
|
363
367
|
mouseEvents={mouseEvents}
|
|
364
368
|
projectRegionBox={projectRegionBox}
|
|
@@ -393,6 +397,8 @@ export const ImageCanvas = ({
|
|
|
393
397
|
RegionEditLabel={RegionEditLabel}
|
|
394
398
|
onRegionClassAdded={onRegionClassAdded}
|
|
395
399
|
allowComments={allowComments}
|
|
400
|
+
hideNotEditingLabel={hideNotEditingLabel}
|
|
401
|
+
allowedGroups={allowedGroups}
|
|
396
402
|
/>
|
|
397
403
|
</PreventScrollToParents>
|
|
398
404
|
)}
|