An emendable web-based plugin that is used in annotating the media files (images & videos) with consideration of your media player's design/controls.
TBD.....
- ⏳ History management (Undo/Redo).
- 🎨 Image/Video Annotating & Drawing.
- 🧊 A variety of annotating shapes.
- 🏗️ Easy, Focused & Simple UI for better UX.
- ➕ Ability to customize.
- 🤹🏼 And more to discover by yourself...
You have to install the following packages (if you haven't them installed in your project) as they are required as peer dependencies,
- react, react-dom >= v19.0.0
- styled-components >= 6.1.15",
- @scaleflex/ui >= 3.0.2
- @scaleflex/icons >= 3.0.1
- reactjs-eventemitter >= 1.0.6
Prerequisites Installation (Click to show)
- react, react-dom:
npm install --save react react-dom
. - styled-components:
npm install --save styled-components
. - scaleflex/ui, @scaleflex/icons:
npm install --save @scaleflex/ui @scaleflex/icons
. - reactjs-eventemitter:
npm install --save reactjs-eventemitter
.
npm install --save react-filerobot-media-annotator
import React, { useRef } from 'react';
import { Canvas, AnnotatingField, Comments } from 'react-filerobot-media-annotator';
import translations from './translations';
import {
createCommentRequest,
deleteCommentsRequest,
dislikeCommentRequest,
editCommentTextRequest,
fetchCommentsRequest,
likeCommentRequest,
subscribeToCommentRequest,
unsubscribeToCommentRequest,
} from './services';
const Annotator = ({ config }) => {
const mediaRef = useRef(null);
const rootContainerRef = useRef(null);
//First of all we need to map the fetched comment to the plugin comments format!
const getFormattedComment = (fetchedComment) => ({
id: fetchedComment.id,
shapes: fetchedComment.shapes || [],
user: {
id: fetchedComment.user.id,
name: fetchedComment.user.userName,
photo_uri: fetchedComment.user.photo,
},
createdAt: fetchedComment.createdAt,
body: fetchedComment.body,
meta: {
likers: fetchedComment.meta.likers || [],
subscribers: fetchedComment.meta.subscribers || [],
resolved: fetchedComment.meta.resolved || false,
canvasDimensions: fetchedComment.meta.canvasDimensions,
displayTimeStamp: fetchedComment.meta.displayTimeStamp || null,
},
});
//Then fetch the backend comments and get our mapped comments!
const getComments = () =>
//do not forget to import your fetchCommentRequest function!
fetchCommentsRequest().then(({ comments: fetchedComments = [] }) => {
const updatedComments = fetchedComments.map((fetchedComment) => {
return getFormattedComment(fetchedComment);
});
return updatedComments;
});
//We need a way to handle our various updates! here we used actions object
//you can handle the updateComment function they way you prefer ⚡
//do not forget to import your commentRequests or equivalent logic functions!
const ACTIONS = {
[ACTIONS_IDS.TOGGLE_LIKE || 'toggleLike']: {
action: (updatedComment, updates) => {
const newLikers = updates;
if (newLikers.includes(config.currentUserId)) {
return likeCommentRequest(updatedComment.id).then((res) => res);
}
return dislikeCommentRequest(updatedComment.id).then((res) => res);
},
},
[ACTIONS_IDS.TOGGLE_SUBSCRIBE || 'toggleSubscribe']: {
action: (updatedComment, updates) => {
const newSubscribers = updates;
if (newSubscribers.includes(config.currentUserId)) {
return subscribeToCommentRequest(updatedComment.id).then((res) => res);
}
return unsubscribeToCommentRequest(updatedComment.id).then((res) => res);
},
},
[ACTIONS_IDS.UPDATE_COMMENT_PROPS || 'updateCommentProps']: {
action: (updatedComment, updates) => {
const newResolve = updates;
return editCommentTextRequest(updatedComment.id, { resolved: newResolve });
},
},
[ACTIONS_IDS.UPDATE_COMMENT_TEXT || 'updateCommentText']: {
action: (updatedComment, updates) => {
const newText = updates;
return editCommentTextRequest(updatedComment.id, { text: newText });
},
},
};
const updateComment = ({ actionId, updatedComment, updates }) =>
ACTIONS?.[actionId]?.action(updatedComment, updates);
//We need a function to handle submitting of new comment then returning new, mapped comment
// do not forget to import your createCommentRequest function!
const onSubmitComment = (newCommentProps) =>
createCommentRequest(newCommentProps).then((newComment) => {
return getFormattedComment(newComment);
});
//Also a function to handle replying to comment then returning new, mapped comment
// do not forget to import your createReplyRequest function!
const createReply = (newCommentProps) =>
createReplyRequest(newCommentProps).then((newComment) => {
return getFormattedComment(newComment);
});
//Finally, we need a function to handle delete comment and then return the new Comments!
// do not forget to import your deleteCommentsRequest function!
const deleteComment = (commentId) =>
deleteCommentsRequest([commentId]).then(() =>
getComments().then((newComments) => newComments)
);
return (
<div ref={rootContainerRef}>
<video ref={mediaRef} muted>
<track src={'http://www.w3schools.com/html/mov_bbb.mp4'} />
<source src={'captions_en.vtt'} />
</video>
<Canvas
isVideoShapesAutoStreamed={true}
mediaRef={mediaRef}
shapeStreamingDuration={1000}
/>
<AnnotatingField
annotatingTools={['Color', 'Brush', 'Circle', 'Square', 'Line', 'Arrow']}
translations={translations}
onSubmit={onSubmitComment}
/>
<Comments
isVideoAutoPlayed
translations={translations}
getComments={getComments}
createReply={createReply}
updateComment={updateComment}
deleteComment={deleteComment}
currentUserId={currentUserId}
/>
</div>
);
};
export default Annotator;
NOTE: The plugin respects the container/wrapper HTML element through CSS by having both
width
&height
set100%
so you could change the width/height of the plugin through adding/changing width/height of the wrapper HTML element.
Type: object
Supported version: +v0.1.0
Default: {}
.
Defines the theme used in the plugin, utilized by @scaleflex/ui
library, u'll find the theme object form here.
Type: string
Supported version: +v0.1.0
Default: undefined
.
The id of the current user using the plugin.
Type: array
Supported version: +v0.1.0
The tools and shapes used to annotate shapes on media, available tools are [Color, Brush, Circle, Square, Line, Arrow], if not supported all of them will be used
Type: number
Supported version: +v0.1.0
Default: 1000
.
The duration in milliseconds which the shape will be streamed until and then removed.
Type: boolean
Supported version: +v0.1.4
Default: true
.
The flag to stream the play the loaded video automatically after comments loads.
Type: boolean
Supported version: +v0.1.0
Default: true
.
The flag to stream the shapes automatically on video play or just by clicking on comment.
Type: object
Supported version: +v0.1.0
Default: undefined
.
Translations keys and equivalent string supported to the plugin, if not supported the plugin will use the default keys as displayed in the following table see available translations
Type: async function
Supported version: +v0.1.0
Backend request function to get comments mapped to our comment object.
Type: async function
Supported version: +v0.1.0
Backend request function to reply to comment and get the new comments mapped to our comment object.
Type: async function
Supported version: +v0.1.0
Backend request function to update comment.
Type: async function
Supported version: +v0.1.0
Backend request function to delete comment.
To run in development mode, follow the next steps:
- Clone the repository.
- Open the repository's folder & run
yarn
to install the project's dependencies. - Run
yarn dev
to start the project in development. - Happy coding🕺🏽!
This project is used by the following companies:
Fork the repoistory, append your company's name with the URL in above format inside the README.md file and make a PR! or create a GitHub issue mentioning (Site's name & domain).
Create an issue on github repo. and mention the details there.
All contributions are super welcomed!
Filerobot Image Editor is provided under MIT License