Skip to content
Snippets Groups Projects
Commit c60a48be authored by Anthony's avatar Anthony
Browse files

Refactoring saving annotation

parent f88a29c5
No related branches found
No related tags found
1 merge request!10Draft: MigratingAnnotationCreation to MUI5.
import React, { useEffect, useLayoutEffect, useState } from 'react'; import React, { useEffect, useLayoutEffect, useState } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import {
Button,
} from '@mui/material';
import { styled } from '@mui/material/styles'; import { styled } from '@mui/material/styles';
import { v4 as uuid } from 'uuid';
import { exportStageSVG } from 'react-konva-to-svg';
import CompanionWindow from 'mirador/dist/es/src/containers/CompanionWindow'; import CompanionWindow from 'mirador/dist/es/src/containers/CompanionWindow';
import { OSDReferences } from 'mirador/dist/es/src/plugins/OSDReferences'; import { OSDReferences } from 'mirador/dist/es/src/plugins/OSDReferences';
import { VideosReferences } from 'mirador/dist/es/src/plugins/VideosReferences'; import { VideosReferences } from 'mirador/dist/es/src/plugins/VideosReferences';
...@@ -16,13 +11,13 @@ import LocalOfferIcon from '@mui/icons-material/LocalOffer'; ...@@ -16,13 +11,13 @@ import LocalOfferIcon from '@mui/icons-material/LocalOffer';
import HubIcon from '@mui/icons-material/Hub'; import HubIcon from '@mui/icons-material/Hub';
import { TabContext, TabList, TabPanel } from '@mui/lab'; import { TabContext, TabList, TabPanel } from '@mui/lab';
import AnnotationDrawing from './annotationForm/AnnotationDrawing'; import AnnotationDrawing from './annotationForm/AnnotationDrawing';
import { secondsToHMS } from './utils';
import AnnotationFormContent from './annotationForm/AnnotationFormContent'; import AnnotationFormContent from './annotationForm/AnnotationFormContent';
import AnnotationFormTime from './annotationForm/AnnotationFormTime'; import AnnotationFormTime from './annotationForm/AnnotationFormTime';
import { import {
geomFromAnnoTarget, getJPG, getSvg, saveAnnotation, timeFromAnnoTarget, geomFromAnnoTarget, timeFromAnnoTarget,
} from './AnnotationCreationUtils'; } from './AnnotationCreationUtils';
import AnnotationFormOverlay from './annotationForm/AnnotationFormOverlay/AnnotationFormOverlay.js'; import AnnotationFormOverlay from './annotationForm/AnnotationFormOverlay/AnnotationFormOverlay';
import AnnotationFormFooter from './annotationForm/AnnotationFormFooter';
const TARGET_VIEW = 'target'; const TARGET_VIEW = 'target';
const OVERLAY_VIEW = 'layer'; const OVERLAY_VIEW = 'layer';
...@@ -302,54 +297,14 @@ function AnnotationCreation(props) { ...@@ -302,54 +297,14 @@ function AnnotationCreation(props) {
} }
}; };
/** const closeFormCompanionWindow = () => {
* Validate form and save annotation closeCompanionWindow('annotationCreation', {
*/ id: props.id,
const submitForm = async (e) => {
console.log('submitForm');
e.preventDefault();
// TODO Possibly problem of syncing
// TODO Improve this code
// If we are in edit mode, we have the transformer on the stage saved in the annotation
/* if (viewTool === OVERLAY_VIEW && state.activeTool === 'edit') {
setState((prevState) => ({
...prevState,
activeTool: 'cursor',
}));
return;
} */
const {
annotation,
canvases,
receiveAnnotation,
config,
} = props;
const drawingStateSerialized = JSON.stringify(drawingState);
const {
textBody,
tags,
xywh,
tstart,
tend,
image,
} = state;
// TODO rename variable for better comprenhension
const svg = await getSvg(props.windowId);
// const jpg = await getJPG(props.windowId);
const drawingImageExport = svg;
const t = (tstart && tend) ? `${tstart},${tend}` : null;
const body = { value: (!textBody.length && t) ? `${secondsToHMS(tstart)} -> ${secondsToHMS(tend)}` : textBody };
saveAnnotation(canvases, config, receiveAnnotation, annotation, body, t, xywh, image, drawingStateSerialized, drawingImageExport, tags);
props.closeCompanionWindow('annotationCreation', {
id,
position: 'right', position: 'right',
}); });
};
const resetStateAfterSave = () => {
// TODO this create a re-render too soon for react and crash the app // TODO this create a re-render too soon for react and crash the app
setState({ setState({
image: { id: null }, image: { id: null },
...@@ -360,7 +315,7 @@ function AnnotationCreation(props) { ...@@ -360,7 +315,7 @@ function AnnotationCreation(props) {
tstart: 0, tstart: 0,
xywh: null, xywh: null,
}); });
}; }
/** */ /** */
const { const {
...@@ -447,7 +402,6 @@ function AnnotationCreation(props) { ...@@ -447,7 +402,6 @@ function AnnotationCreation(props) {
setDrawingState={setDrawingState} setDrawingState={setDrawingState}
/> />
<StyledForm <StyledForm
onSubmit={submitForm}
> >
<TabContext value={viewTool}> <TabContext value={viewTool}>
<TabList value={viewTool} onChange={tabHandler} aria-label="icon tabs"> <TabList value={viewTool} onChange={tabHandler} aria-label="icon tabs">
...@@ -515,30 +469,22 @@ function AnnotationCreation(props) { ...@@ -515,30 +469,22 @@ function AnnotationCreation(props) {
value={MANIFEST_LINK_VIEW} value={MANIFEST_LINK_VIEW}
/> />
</TabContext> </TabContext>
<StyledButtonDivSaveOrCancel> <AnnotationFormFooter
<Button onClick={closeCompanionWindow}> annotation={annotation}
Cancel canvases={props.canvases}
</Button> closeFormCompanionWindow={closeFormCompanionWindow}
<Button config={props.config}
variant="contained" drawingState={drawingState}
color="primary" receiveAnnotation={props.receiveAnnotation}
type="submit" resetStateAfterSave={resetStateAfterSave}
onMouseOver={() => setIsMouseOverSave(true)} state={state}
onMouseOut={() => setIsMouseOverSave(false)} windowId={windowId}
> />
Save
</Button>
</StyledButtonDivSaveOrCancel>
</StyledForm> </StyledForm>
</CompanionWindow> </CompanionWindow>
); );
} }
const StyledButtonDivSaveOrCancel = styled('div')(({ theme }) => ({
display: 'flex',
justifyContent: 'flex-end',
}));
const StyledForm = styled('form')(({ theme }) => ({ const StyledForm = styled('form')(({ theme }) => ({
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
......
import { exportStageSVG } from 'react-konva-to-svg';
import { v4 as uuid } from 'uuid';
import axios from 'axios'; import axios from 'axios';
import WebAnnotation from './WebAnnotation';
const fileUploaderUrl = 'https://scene-uploads.tetras-libre.fr/upload'; export const fileUploaderUrl = 'https://scene-uploads.tetras-libre.fr/upload';
const fileReaderUrl = 'https://scene-uploads.tetras-libre.fr/static/'; export const fileReaderUrl = 'https://scene-uploads.tetras-libre.fr/static/';
/*const fileUploaderUrl = 'http://localhost:3000/upload'; /*const fileUploaderUrl = 'http://localhost:3000/upload';
const fileReaderUrl = 'http://localhost:3000/static/';*/ const fileReaderUrl = 'http://localhost:3000/static/';*/
...@@ -52,55 +49,28 @@ export function isShapesTool(activeTool) { ...@@ -52,55 +49,28 @@ export function isShapesTool(activeTool) {
return Object.values(SHAPES_TOOL).find((tool) => tool === activeTool); return Object.values(SHAPES_TOOL).find((tool) => tool === activeTool);
} }
/** /** Save annotation in the storage adapter */
* Get SVG picture containing all the stuff draw in the stage (Konva Stage). export async function saveAnnotation(canvas, storageAdapter, receiveAnnotation, annotationToSaved, isNewAnnotation) {
* This image will be put in overlay of the iiif media if (isNewAnnotation) {
*/ storageAdapter.update(annotationToSaved)
export async function getSvg(windowId) {
const stage = window.Konva.stages.find((s) => s.attrs.id === windowId);
const svg = await exportStageSVG(stage, false); // TODO clean
console.log('SVG:', svg);
return svg;
}
export async function getJPG(windowId) {
const stage = window.Konva.stages.find((s) => s.attrs.id === windowId);
const jpg = await stage.toImage({ mimeType: 'image/jpeg', quality: 1 });
console.log('JPG:', jpg);
return jpg;
}
export async function saveAnnotation(canvases, config, receiveAnnotation, annotation, body, t, xywh, image, drawingStateSerialized, drawingImageExport, tags) {
console.log('Send file :', drawingImageExport);
const filename = await sendFile(drawingImageExport);
canvases.forEach(async (canvas) => {
const storageAdapter = config.annotation.adapter(canvas.id);
const anno = {
body: {
id: fileReaderUrl + filename,
type: 'Image',
format: 'image/svg+xml',
value: body.value,
},
drawingState: drawingStateSerialized,
id: (annotation && annotation.id) || `${uuid()}`,
motivation: 'commenting',
target: `${canvas.id}#xywh=${xywh}&t=${t}`,
type: 'Annotation',
};
if (annotation) {
storageAdapter.update(anno)
.then((annoPage) => { .then((annoPage) => {
receiveAnnotation(canvas.id, storageAdapter.annotationPageId, annoPage); receiveAnnotation(canvas.id, storageAdapter.annotationPageId, annoPage);
}); });
} else { } else {
storageAdapter.create(anno) storageAdapter.create(annotationToSaved)
.then((annoPage) => { .then((annoPage) => {
receiveAnnotation(canvas.id, storageAdapter.annotationPageId, annoPage); receiveAnnotation(canvas.id, storageAdapter.annotationPageId, annoPage);
}); });
} }
}
export async function saveAnnotationInEachCanvas(canvases, config, receiveAnnotation, annotationToSaved, target, isNewAnnotation) {
canvases.forEach(async (canvas) => {
// Adapt target to the canvas
// eslint-disable-next-line no-param-reassign
annotationToSaved.target = `${canvas.id}#xywh=${target.xywh}&t=${target.t}`;
const storageAdapter = config.annotation.adapter(canvas.id);
saveAnnotation(canvas, storageAdapter, receiveAnnotation, annotationToSaved, isNewAnnotation);
}); });
} }
...@@ -110,7 +80,6 @@ const sendFile = async (fileContent) => { ...@@ -110,7 +80,6 @@ const sendFile = async (fileContent) => {
const formData = new FormData(); const formData = new FormData();
formData.append('file', blob); formData.append('file', blob);
try { try {
const response = await axios.post(fileUploaderUrl, formData, { const response = await axios.post(fileUploaderUrl, formData, {
headers: { headers: {
...@@ -128,20 +97,11 @@ const sendFile = async (fileContent) => { ...@@ -128,20 +97,11 @@ const sendFile = async (fileContent) => {
}; };
// export function dataURLtoBlob(dataurl) { function dataURLtoBlob(dataurl) {
// // Split the Data URL to get the metadata and the actual data var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
// console.log('Data URL:', dataurl); bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
// var arr = dataurl.split(','), while(n--){
// mime = arr[0].match(/:(.*?);/)[1], // Extract MIME type u8arr[n] = bstr.charCodeAt(n);
// bstr = atob(arr[1]), // Decode base64 }
// n = bstr.length, return new Blob([u8arr], {type:mime});
// u8arr = new Uint8Array(n); // Create a new ArrayBuffer
//
// // Convert the binary string to an ArrayBuffer
// while(n--){
// u8arr[n] = bstr.charCodeAt(n);
// }
//
// // Return a Blob object
// return new Blob([u8arr], {type:mime});
} }
import { Button } from '@mui/material';
import PropTypes from 'prop-types';
import { styled } from '@mui/material/styles';
import React from 'react';
import { v4 as uuid } from 'uuid';
import {
saveAnnotationInEachCanvas,
} from '../AnnotationCreationUtils';
import { secondsToHMS } from '../utils';
import {
getJPGAsDataURL,
getKonvaAsDataURL
} from './AnnotationFormOverlay/KonvaDrawing/KonvaUtils';
const StyledButtonDivSaveOrCancel = styled('div')(({ theme }) => ({
display: 'flex',
justifyContent: 'flex-end',
}));
/** Annotation form footer, save or cancel the edition/creation of an annotation */
function AnnotationFormFooter({
annotation,
canvases,
closeFormCompanionWindow,
config,
drawingState,
receiveAnnotation,
resetStateAfterSave,
state,
windowId,
}) {
/**
* Validate form and save annotation
*/
const submitAnnotationForm = async (e) => {
console.log('submitForm');
e.preventDefault();
// TODO Possibly problem of syncing
// TODO Improve this code
// If we are in edit mode, we have the transformer on the stage saved in the annotation
/* if (viewTool === OVERLAY_VIEW && state.activeTool === 'edit') {
setState((prevState) => ({
...prevState,
activeTool: 'cursor',
}));
return;
} */
const {
textBody,
tags,
xywh,
tstart,
tend,
image,
} = state;
// Save annotation drawing in svg and sent it to the server
// const svg = await getSvg(windowId);
// const drawingImageExport = jpg;
// const filename = await sendFile(drawingImageExport);
// const annotationBodyImageId = fileReaderUrl + filename;
// Save jpg image of the drawing in a data url
const annotationBodyImageId = getKonvaAsDataURL(windowId);
// Temporal target of the annotation
const target = {
t: (tstart && tend) ? `${tstart},${tend}` : null,
xywh, // TODO retrouver calcul de xywh
};
const annotationText = (!textBody.length && target.t) ? `${secondsToHMS(tstart)} -> ${secondsToHMS(tend)}` : textBody;
console.log('annotationBodyImageId:', annotationBodyImageId);
const annotationToSaved = {
body: {
id: annotationBodyImageId,
type: 'Image',
format: 'image/svg+xml',
value: annotationText,
},
drawingState: JSON.stringify(drawingState),
id: (annotation && annotation.id) || `${uuid()}`,
motivation: 'commenting',
target: null,
type: 'Annotation', // Will be updated in saveAnnotationInEachCanvas
};
console.log('Annotation to save:', annotationToSaved);
console.log('target:', target);
const isNewAnnotation = !annotation;
saveAnnotationInEachCanvas(canvases, config, receiveAnnotation, annotationToSaved, target, isNewAnnotation);
closeFormCompanionWindow();
resetStateAfterSave();
};
return (
<StyledButtonDivSaveOrCancel>
<Button onClick={closeFormCompanionWindow}>
Cancel
</Button>
<Button
variant="contained"
color="primary"
type="submit"
onClick={submitAnnotationForm}
>
Save
</Button>
</StyledButtonDivSaveOrCancel>
);
}
AnnotationFormFooter.propTypes = {
annotation: PropTypes.object, // eslint-disable-line react/forbid-prop-types
canvases: PropTypes.arrayOf(PropTypes.object).isRequired,
closeFormCompanionWindow: PropTypes.func.isRequired,
config: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
drawingState: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
receiveAnnotation: PropTypes.func.isRequired,
resetStateAfterSave: PropTypes.func.isRequired,
state: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
windowId: PropTypes.string.isRequired,
};
export default AnnotationFormFooter;
import { exportStageSVG } from 'react-konva-to-svg';
/**
* Get SVG picture containing all the stuff draw in the stage (Konva Stage).
* This image will be put in overlay of the iiif media
*/
export async function getSvg(windowId) {
const stage = window.Konva.stages.find((s) => s.attrs.id === windowId);
const svg = await exportStageSVG(stage, false); // TODO clean
console.log('SVG:', svg);
return svg;
}
/** Export the stage as a JPG image in a data url */
export async function getKonvaAsDataURL(windowId) {
const stage = window.Konva.stages.find((s) => s.attrs.id === windowId);
const dataURL = await stage.toDataURL({ mimeType: 'image/png', quality: 1 });
console.log('dataURL:', dataURL);
return dataURL;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment