├── CNAME ├── web ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ └── fontawesome-webfont.woff2 └── css │ ├── style.css │ ├── font-awesome.css.map │ ├── font-awesome.min.css │ └── font-awesome.css ├── .gitignore ├── .npmignore ├── .babelrc ├── params.json ├── src ├── types.ts ├── Circle.tsx ├── Marker.tsx ├── Highlight.tsx ├── Square.tsx ├── ModeToggle.tsx ├── Input.tsx ├── Content.tsx ├── Annotation.tsx └── Container.tsx ├── LICENSE ├── README.md ├── package.json ├── stylesheets ├── github-light.css ├── stylesheet.css └── normalize.css └── tsconfig.json /CNAME: -------------------------------------------------------------------------------- 1 | carbondream.zeroarc.com 2 | -------------------------------------------------------------------------------- /web/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZeroarcSoftware/carbondream/HEAD/web/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /web/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZeroarcSoftware/carbondream/HEAD/web/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /web/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZeroarcSoftware/carbondream/HEAD/web/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /web/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZeroarcSoftware/carbondream/HEAD/web/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /web/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZeroarcSoftware/carbondream/HEAD/web/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directory 2 | node_modules 3 | 4 | # Don't check in build artifacts, this is only published to npm 5 | lib/ 6 | dist/ 7 | 8 | .DS_Store 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Don't publish jsx source 2 | /src/ 3 | 4 | # Don't check in new buids of test 5 | test/ 6 | 7 | # Don't check gh-pages related code 8 | stylesheets/ 9 | index.html 10 | params.json 11 | CNAME 12 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/react", 4 | "@babel/typescript", 5 | [ 6 | "@babel/env", 7 | { 8 | "modules": "commonjs" 9 | } 10 | ] 11 | ], 12 | "plugins": [ 13 | "@babel/plugin-proposal-object-rest-spread" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /params.json: -------------------------------------------------------------------------------- 1 | {"name":"Carbondream","tagline":"Reactjs Annotation Component","body":"# carbondream\r\n\r\nReactjs Annotation Engine\r\n\r\n## Setup environment\r\n\r\n npm install\r\n\r\n## Development\r\nRun single build:\r\n\r\n npm run build\r\n\r\nRunning demo build:\r\n\r\n npm run demo-build\r\n\r\nRunning demo watch:\r\n\r\n npm run demo-watch\r\n","google":"","note":"Don't delete this file! It's used internally to help with page regeneration."} -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | // carbondream - Copyright 2021 Zeroarc Software, LLC 2 | 3 | import { Map } from 'immutable'; 4 | 5 | export interface ImmutableMap extends Map { 6 | get(name: K): T[K]; 7 | } 8 | 9 | export type Annotation = ImmutableMap; 10 | 11 | export type AnnotationPoint = { 12 | id?: number; 13 | content: string; 14 | author: string; 15 | timeStamp: Date; 16 | x1: number; 17 | y1: number; 18 | x2: number; 19 | y2: number; 20 | type: 'marker' | 'highlight'; 21 | drawing: boolean; 22 | }; 23 | 24 | export type Offset = { 25 | horizontal: number; 26 | vertical: number; 27 | shadow: number | null; 28 | }; 29 | 30 | export type Mode = 'marker' | 'square' | 'circle' | 'highlight'; 31 | -------------------------------------------------------------------------------- /src/Circle.tsx: -------------------------------------------------------------------------------- 1 | // carbondream - Copyright 2021 Zeroarc Software, LLC 2 | // Annotation circle shape 3 | 'use strict'; 4 | 5 | import ClassNames from 'classnames'; 6 | import React from 'react'; 7 | 8 | type Props = { 9 | width: number, 10 | height: number, 11 | priority: number, 12 | deemphasize: boolean, 13 | }; 14 | 15 | export const Circle = (props: Props) => { 16 | const divStyle = { 17 | height: props.height, 18 | width: props.width, 19 | zIndex: props.priority, 20 | }; 21 | 22 | const classes = ClassNames({ 23 | 'cd-circle': true, 24 | 'deemphasize': props.deemphasize, 25 | }); 26 | 27 | return ( 28 |
29 |
30 |
31 | ); 32 | }; 33 | 34 | export default Circle; -------------------------------------------------------------------------------- /src/Marker.tsx: -------------------------------------------------------------------------------- 1 | // carbondream - Copyright 2021 Zeroarc Software, LLC 2 | // Annotation marker shape 3 | 'use strict'; 4 | 5 | import ClassNames from 'classnames'; 6 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 7 | import React from 'react'; 8 | 9 | type Props = { 10 | deemphasize: boolean; 11 | priority: number; 12 | }; 13 | 14 | export const Marker = (props: Props) => { 15 | const divStyle = { 16 | zIndex: props.priority, 17 | }; 18 | 19 | const classes = ClassNames({ 20 | 'cd-marker': true, 21 | deemphasize: props.deemphasize, 22 | }); 23 | 24 | return ( 25 |
26 | 27 |
28 | ); 29 | }; 30 | 31 | export default Marker; 32 | -------------------------------------------------------------------------------- /src/Highlight.tsx: -------------------------------------------------------------------------------- 1 | // carbondream - Copyright 2021 Zeroarc Software, LLC 2 | // Annotation highlight shape 3 | 'use strict'; 4 | 5 | import ClassNames from 'classnames'; 6 | import React from 'react'; 7 | 8 | type Props = { 9 | width: number; 10 | deemphasize: boolean; 11 | priority: number; 12 | height: number; 13 | }; 14 | 15 | export const Highlight = (props: Props) => { 16 | const divStyle = { 17 | width: props.width, 18 | zIndex: props.priority, 19 | height: props.height, 20 | }; 21 | 22 | const classes = ClassNames({ 23 | 'cd-highlight': true, 24 | deemphasize: props.deemphasize, 25 | }); 26 | 27 | if (props.height <= 0 || props.width <= 0) return null; 28 | 29 | return ( 30 |
31 |
32 |
33 | ); 34 | }; 35 | export default Highlight; 36 | -------------------------------------------------------------------------------- /src/Square.tsx: -------------------------------------------------------------------------------- 1 | // carbondream - Copyright 2021 Zeroarc Software, LLC 2 | // Annotation square shape 3 | 'use strict'; 4 | 5 | import ClassNames from 'classnames'; 6 | import React from 'react'; 7 | 8 | type Props = { 9 | width: number; 10 | height: number; 11 | priority: number; 12 | deemphasize: boolean; 13 | }; 14 | 15 | export const Square = (props: Props) => { 16 | const divStyle = { 17 | height: props.height, 18 | width: props.width, 19 | zIndex: props.priority, 20 | }; 21 | 22 | const classes = ClassNames({ 23 | 'cd-square': true, 24 | deemphasize: props.deemphasize, 25 | }); 26 | 27 | // Not possible to have a 0 width or height unless the object is off the page entirely. 28 | if (props.height <= 0 || props.width <= 0) return null; 29 | 30 | return ( 31 |
32 |
33 |
34 | ); 35 | }; 36 | 37 | export default Square; 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Zeroarc Software, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # carbondream 2 | 3 | ## Reactjs Annotation Engine 4 | 5 | Carbondream is an annotation engine written purely in ReactJS. It was inspired by [Annotator](http://annotatorjs.org) and heavily borrows from the UX. 6 | 7 | ## Using it 8 | 9 | To use Carbondream: 10 | 11 | - Install the component through NPM 12 | - Require it 13 | - Pass it a list of annotations and the following props: 14 | 15 | 30 | 31 | ## Contributing 32 | 33 | First, setup your local environment: 34 | 35 | git clone git@github.com:ZeroarcSoftware/carbondream.git 36 | cd carbondream 37 | npm install 38 | 39 | Link the project to your local target environment: 40 | 41 | sudo npm link 42 | 43 | Next, build the project: 44 | 45 | npm run build 46 | 47 | Or, alternatively, use babel watch to continously watch for changes: 48 | 49 | NODE_ENV=production npx babel src/ -d dist/ --extensions '.ts,.tsx' -w 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "carbondream", 3 | "version": "5.2.0", 4 | "description": "React web annotation engine", 5 | "homepage": "http://carbondream.zeroarc.com/", 6 | "main": "dist/Container.js", 7 | "directories": { 8 | "test": "test" 9 | }, 10 | "scripts": { 11 | "build": "NODE_ENV=production npx babel src/ -d dist/ --extensions '.ts,.tsx'", 12 | "watch": "npx babel src/ -d dist/ --extensions '.ts,.tsx' --watch", 13 | "prepare": "npm run build", 14 | "test": "echo \"Error: no test specified\" && exit 1" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/ZeroarcSoftware/carbondream.git" 19 | }, 20 | "keywords": [ 21 | "react", 22 | "react-component", 23 | "annotation" 24 | ], 25 | "author": "Zeroarc Software", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/ZeroarcSoftware/carbondream/issues" 29 | }, 30 | "devDependencies": { 31 | "@babel/cli": "^7.14.5", 32 | "@babel/core": "^7.14.6", 33 | "@babel/plugin-proposal-object-rest-spread": "^7.14.7", 34 | "@babel/preset-env": "^7.14.7", 35 | "@babel/preset-react": "^7.14.5", 36 | "@babel/preset-typescript": "^7.14.5", 37 | "@types/react": "^18.0.1", 38 | "@types/react-dom": "^18.0.0", 39 | "@types/react-timeago": "^4.1.3", 40 | "babel-loader": "^9.1.2", 41 | "json-loader": "^0.5.4", 42 | "react": "^18.0.0", 43 | "react-dom": "^18.0.0", 44 | "typescript": "^5.0.2" 45 | }, 46 | "dependencies": { 47 | "@fortawesome/fontawesome-svg-core": "^6.1.1", 48 | "@fortawesome/react-fontawesome": "^0.2.0", 49 | "classnames": "^2.2.6", 50 | "font-awesome": "^4.5.0", 51 | "immutable": "^4.0.0-rc.14", 52 | "react-timeago": "^7.1.0" 53 | }, 54 | "peerDependencies": { 55 | "react": "^17.0.0 || ^18.0.0", 56 | "react-dom": "^17.0.0 || ^18.0.0" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/ModeToggle.tsx: -------------------------------------------------------------------------------- 1 | // carbondream - Copyright 2021 Zeroarc Software, LLC 2 | // Controls to toggle annotation modes 3 | 'use strict'; 4 | 5 | import React, { SyntheticEvent } from 'react'; 6 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 7 | import type { Mode } from './types'; 8 | 9 | type Props = { 10 | mode: string; 11 | verticalOffset: number; 12 | switchMode: (mode: Mode) => void; 13 | }; 14 | 15 | // This is necessary to prevent mouseup/down from triggering actions on parents 16 | const blockEvent = (e: React.MouseEvent) => { 17 | e.stopPropagation(); 18 | }; 19 | 20 | export const ModeToggle = ({ mode, verticalOffset, switchMode }: Props) => { 21 | const handleClick = (mode: Mode) => { 22 | return (e: React.MouseEvent) => { 23 | e.stopPropagation(); 24 | switchMode(mode); 25 | }; 26 | }; 27 | 28 | return ( 29 |
36 | 43 | 50 | 57 | 64 |
65 | ); 66 | }; 67 | 68 | export default ModeToggle; 69 | -------------------------------------------------------------------------------- /stylesheets/github-light.css: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 GitHub Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | */ 17 | 18 | .pl-c /* comment */ { 19 | color: #969896; 20 | } 21 | 22 | .pl-c1 /* constant, markup.raw, meta.diff.header, meta.module-reference, meta.property-name, support, support.constant, support.variable, variable.other.constant */, 23 | .pl-s .pl-v /* string variable */ { 24 | color: #0086b3; 25 | } 26 | 27 | .pl-e /* entity */, 28 | .pl-en /* entity.name */ { 29 | color: #795da3; 30 | } 31 | 32 | .pl-s .pl-s1 /* string source */, 33 | .pl-smi /* storage.modifier.import, storage.modifier.package, storage.type.java, variable.other, variable.parameter.function */ { 34 | color: #333; 35 | } 36 | 37 | .pl-ent /* entity.name.tag */ { 38 | color: #63a35c; 39 | } 40 | 41 | .pl-k /* keyword, storage, storage.type */ { 42 | color: #a71d5d; 43 | } 44 | 45 | .pl-pds /* punctuation.definition.string, string.regexp.character-class */, 46 | .pl-s /* string */, 47 | .pl-s .pl-pse .pl-s1 /* string punctuation.section.embedded source */, 48 | .pl-sr /* string.regexp */, 49 | .pl-sr .pl-cce /* string.regexp constant.character.escape */, 50 | .pl-sr .pl-sra /* string.regexp string.regexp.arbitrary-repitition */, 51 | .pl-sr .pl-sre /* string.regexp source.ruby.embedded */ { 52 | color: #183691; 53 | } 54 | 55 | .pl-v /* variable */ { 56 | color: #ed6a43; 57 | } 58 | 59 | .pl-id /* invalid.deprecated */ { 60 | color: #b52a1d; 61 | } 62 | 63 | .pl-ii /* invalid.illegal */ { 64 | background-color: #b52a1d; 65 | color: #f8f8f8; 66 | } 67 | 68 | .pl-sr .pl-cce /* string.regexp constant.character.escape */ { 69 | color: #63a35c; 70 | font-weight: bold; 71 | } 72 | 73 | .pl-ml /* markup.list */ { 74 | color: #693a17; 75 | } 76 | 77 | .pl-mh /* markup.heading */, 78 | .pl-mh .pl-en /* markup.heading entity.name */, 79 | .pl-ms /* meta.separator */ { 80 | color: #1d3e81; 81 | font-weight: bold; 82 | } 83 | 84 | .pl-mq /* markup.quote */ { 85 | color: #008080; 86 | } 87 | 88 | .pl-mi /* markup.italic */ { 89 | color: #333; 90 | font-style: italic; 91 | } 92 | 93 | .pl-mb /* markup.bold */ { 94 | color: #333; 95 | font-weight: bold; 96 | } 97 | 98 | .pl-md /* markup.deleted, meta.diff.header.from-file */ { 99 | background-color: #ffecec; 100 | color: #bd2c00; 101 | } 102 | 103 | .pl-mi1 /* markup.inserted, meta.diff.header.to-file */ { 104 | background-color: #eaffea; 105 | color: #55a532; 106 | } 107 | 108 | .pl-mdr /* meta.diff.range */ { 109 | color: #795da3; 110 | font-weight: bold; 111 | } 112 | 113 | .pl-mo /* meta.output */ { 114 | color: #1d3e81; 115 | } 116 | 117 | -------------------------------------------------------------------------------- /src/Input.tsx: -------------------------------------------------------------------------------- 1 | // carbondream - Copyright 2021 Zeroarc Software, LLC 2 | // Input dialog for annotation 3 | 'use strict'; 4 | 5 | import ClassNames from 'classnames'; 6 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 7 | import React, { useState } from 'react'; 8 | 9 | import type { Offset } from './types'; 10 | 11 | type Props = { 12 | cancelAnnotation: () => void; 13 | content: string; 14 | invert: boolean; 15 | offset: Offset; 16 | top: number; 17 | left: number; 18 | pending: boolean; 19 | pullHorizontal: boolean; 20 | pushHorizontal: boolean; 21 | saveAnnotation: (value: string) => void; 22 | }; 23 | 24 | export const Input = (props: Props) => { 25 | const [value, setValue] = useState(props.content); 26 | 27 | const handleChange = (e: React.ChangeEvent) => { 28 | e.stopPropagation(); 29 | setValue(e.target.value); 30 | }; 31 | 32 | const handleSaveClick = (e: React.MouseEvent) => { 33 | e.stopPropagation(); 34 | props.saveAnnotation(value); 35 | }; 36 | 37 | const handleCancelClick = (e: React.MouseEvent) => { 38 | e.stopPropagation(); 39 | props.cancelAnnotation(); 40 | }; 41 | 42 | const handleBlur = (e: React.FocusEvent) => { 43 | e.stopPropagation(); 44 | 45 | // If the textarea blurs with no input, the user has clicked or tabbed out. Cancel. 46 | if (value.length === 0) props.cancelAnnotation(); 47 | }; 48 | 49 | const editorClasses = ClassNames({ 50 | 'cd-annotation-editor': true, 51 | hidden: !props.pending, 52 | }); 53 | 54 | const inputClasses = ClassNames({ 55 | 'cd-annotation-input': true, 56 | }); 57 | 58 | const shadowClasses = ClassNames({ 59 | 'cd-shadow-bubble': true, 60 | invert: props.invert, 61 | }); 62 | 63 | // Apply offsets for shadow bubble. Trial and error to figure 64 | // out the maximums 65 | let shadowStyle: { left?: number } = {}; 66 | if (props.pushHorizontal || props.pullHorizontal) { 67 | shadowStyle.left = props.offset.shadow || -props.offset.horizontal - 4; 68 | 69 | if (shadowStyle.left < 6) shadowStyle.left = 6; 70 | else if (shadowStyle.left > 234) shadowStyle.left = 234; 71 | } 72 | 73 | const saveClasses = ClassNames('btn btn-sm btn-outline-primary', { 74 | disabled: !value.length, 75 | }); 76 | 77 | const cancelClasses = ClassNames('btn btn-sm btn-outline-danger'); 78 | 79 | return ( 80 |
88 |
89 |
90 |