Skip to content

Commit

Permalink
Merge pull request #121 from DiSSCo/RefactorVisualAnnotation
Browse files Browse the repository at this point in the history
Refactor visual annotation
  • Loading branch information
TomDijkema authored Nov 11, 2024
2 parents 4db36e9 + 422b8d6 commit fb8e39c
Show file tree
Hide file tree
Showing 12 changed files with 1,635 additions and 1,012 deletions.
2,023 changes: 1,049 additions & 974 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
"test:coverage": "vitest run --coverage --watch=false"
},
"dependencies": {
"@annotorious/annotorious": "^3.0.0-rc.29",
"@annotorious/react": "^3.0.0-rc.29",
"@annotorious/annotorious": "^3.0.11",
"@annotorious/react": "^3.0.11",
"@fortawesome/fontawesome-svg-core": "^6.5.2",
"@fortawesome/free-solid-svg-icons": "^6.5.2",
"@fortawesome/react-fontawesome": "^0.2.2",
Expand Down
7 changes: 7 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,13 @@ p {
transition: 0.15s;
}

/* Box shadow */
.box-shadow {
-webkit-box-shadow: 9px 7px 24px -8px rgba(0, 0, 0, 0.75);
-moz-box-shadow: 9px 7px 24px -8px rgba(0, 0, 0, 0.75);
box-shadow: 9px 7px 24px -8px rgba(0, 0, 0, 0.75);
}

/* Hover */
.hover-primary:hover {
background-color: var(--primary);
Expand Down
86 changes: 74 additions & 12 deletions src/app/utilities/AnnotateUtilities.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
/* Import Dependencies */
import { W3CImageAnnotation } from '@annotorious/annotorious';
import jp from 'jsonpath';
import { isEmpty, cloneDeep } from 'lodash';

/* Import Utilities */
import { ExtractLowestLevelSchema, ExtractClassesAndTermsFromSchema, MakeJsonPathReadableString } from 'app/utilities/SchemaUtilities';

/* Import Types */
import { Annotation } from 'app/types/Annotation';
import { AnnotationFormProperty, AnnotationTarget, AnnotationTemplate, ParentClass, Dict, SuperClass } from "app/Types";


Expand All @@ -21,24 +23,32 @@ import { AnnotationFormProperty, AnnotationTarget, AnnotationTemplate, ParentCla
* - annotationTargetType: The type of the annotation target, being term, class or ROI
* - jsonPath: The JSON path towards the annotation target
* - annotationValues: An array of processed value from the annotation form
* - coordinates: Potential coordinates supplied for a visual annotation
*/
const ConstructAnnotationObject = (params: {
digitalObjectId: string,
digitalObjectType: string,
motivation: 'ods:adding' | 'ods:deleting' | 'oa:assessing' | 'oa:editing' | 'oa:commenting',
annotationTargetType: 'term' | 'class' | 'ROI',
jsonPath: string,
annotationValues: (string | Dict)[]
jsonPath?: string,
annotationValues: (string | Dict)[],
fragments?: {
xFrac: number,
yFrac: number,
widthFrac: number,
heightFrac: number
}
}): AnnotationTemplate => {
const { digitalObjectId, digitalObjectType, motivation, annotationTargetType, jsonPath, annotationValues } = params;
const { digitalObjectId, digitalObjectType, motivation, annotationTargetType, jsonPath, annotationValues, fragments } = params;

let localJsonPath: string = jsonPath;
let localJsonPath: string = jsonPath ?? '';

/* If motivation is adding, check for new index at end of JSON path and removeif it is there */
if (typeof (jp.parse(jsonPath).at(-1).expression.value) === 'number') {
/* If motivation is adding, check for new index at end of JSON path and remove if it is there */
if (jsonPath && typeof (jp.parse(jsonPath).at(-1).expression.value) === 'number') {
localJsonPath = jp.stringify(jp.parse(jsonPath).slice(0, -1));
}

/* Define target type DOI */
let targetTypeDoi: string = '';

if (digitalObjectType === 'ods:DigitalSpecimen') {
Expand All @@ -65,13 +75,13 @@ const ConstructAnnotationObject = (params: {
"@type": 'ods:ClassSelector',
"ods:class": localJsonPath
}),
...(annotationTargetType === 'ROI' && {
...((annotationTargetType === 'ROI' && fragments) && {
"@type": 'oa:FragmentSelector',
"ac:hasROI": {
"ac:xFrac": 0,
"ac:yFrac": 0,
"ac:widthFrac": 0,
"ac:heightFrac": 0
"ac:xFrac": fragments.xFrac,
"ac:yFrac": fragments.yFrac,
"ac:widthFrac": fragments.widthFrac,
"ac:heightFrac": fragments.heightFrac
},
"dcterms:conformsTo": ''
})
Expand Down Expand Up @@ -306,12 +316,64 @@ const ProvideReadableMotivation = (motivation: 'ods:adding' | 'ods:deleting' | '
return annotationMotivations[motivation];
};

/**
* Function to reformat an annotation to the Annotorious format
* @param annotation The annotation to reformat
* @param mediaUrl The URL pointing to the source of the media
* @param imageDimenstions An object containg the x and y dimensions of the image
* @returns Annotation according to Annotorious format
*/
const ReformatToAnnotoriousAnnotation = (annotation: Annotation, mediaUrl: string, imageDimenstions: { x: number, y: number }) => {
let annotoriousAnnotation: W3CImageAnnotation = {} as W3CImageAnnotation;

/* Check if the annotation is a visual one and */
if (annotation['oa:hasTarget']['oa:hasSelector']?.['@type'] === 'oa:FragmentSelector') {
/* Calculate the W3C pixels relative to the TDWG AC position */
const ROI = annotation['oa:hasTarget']['oa:hasSelector']['ac:hasROI'] as {
"ac:xFrac": number,
"ac:yFrac": number,
"ac:widthFrac": number,
"ac:heightFrac": number
};

const x = ROI["ac:xFrac"] * imageDimenstions.x;
const y = ROI["ac:yFrac"] * imageDimenstions.y;
const w = ROI["ac:widthFrac"] * imageDimenstions.x;
const h = ROI["ac:heightFrac"] * imageDimenstions.y;

annotoriousAnnotation = {
id: annotation['ods:ID'],
"@context": 'http://www.w3.org/ns/anno.jsonld',
type: 'Annotation',
body: [{
type: 'TextualBody',
value: annotation['oa:hasBody']['oa:value'][0] ?? '',
purpose: 'commenting',
creator: {
id: annotation['dcterms:creator']['@id'] ?? ''
}
}],
target: {
selector: {
conformsTo: 'http://www.w3.org/TR/media-frags/',
type: 'FragmentSelector',
value: `xywh=pixel:${x},${y},${w},${h}`
},
source: mediaUrl
}
};
}

return annotoriousAnnotation;
};

export {
ConstructAnnotationObject,
ExtractParentClasses,
FormatJsonPathFromFieldName,
GenerateAnnotationFormFieldProperties,
GetAnnotationMotivations,
ProcessAnnotationValues,
ProvideReadableMotivation
ProvideReadableMotivation,
ReformatToAnnotoriousAnnotation
};
3 changes: 1 addition & 2 deletions src/components/demo/Demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,7 @@ const Demo = () => {
</Col>
</Row>

<Formik
initialValues={{
<Formik initialValues={{
DOI: ''
}}
onSubmit={async (form) => {
Expand Down
8 changes: 7 additions & 1 deletion src/components/digitalMedia/DigitalMedia.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const DigitalMedia = () => {
/* Base variables */
const digitalMedia = useAppSelector(getDigitalMedia);
const [annotationMode, setAnnotationMode] = useState<boolean>(false);
const [annotoriousMode, setAnnotoriousMode] = useState<string>('move');

/* OnLoad, fetch digital media data */
fetch.Fetch({
Expand Down Expand Up @@ -99,7 +100,9 @@ const DigitalMedia = () => {
<Col>
<TopBar digitalMedia={digitalMedia}
annotationMode={annotationMode}
annotoriousMode={annotoriousMode}
ToggleAnnotationSidePanel={() => setAnnotationMode(!annotationMode)}
SetAnnotoriousMode={(mode: string) => setAnnotoriousMode(mode)}
/>
</Col>
</Row>
Expand All @@ -115,7 +118,10 @@ const DigitalMedia = () => {
<Col lg={{ span: 9 }}
className="h-100"
>
<ContentBlock digitalMedia={digitalMedia} />
<ContentBlock digitalMedia={digitalMedia}
annotoriousMode={annotoriousMode}
SetAnnotoriousMode={(mode: string) => setAnnotoriousMode(mode)}
/>
</Col>
</Row>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,30 @@ import { DigitalMediaFrame, DigitalMediaList } from "./ContentBlockComponents";

/* Props Type */
type Props = {
digitalMedia: DigitalMedia
digitalMedia: DigitalMedia,
annotoriousMode: string,
SetAnnotoriousMode: Function
};


/**
* Component that renders the content block on the digital media page
* @param digitalMedia The selected digital media
* @param annotoriousMode The currently selected Annotorious mode
* @param SetAnnotoriousMode Function to set the Annotorious mode
* @returns JSX Component
*/
const ContentBlock = (props: Props) => {
const { digitalMedia } = props;
const { digitalMedia, annotoriousMode, SetAnnotoriousMode } = props;

return (
<div className="h-100 d-flex flex-column">
<Row className="flex-grow-1 overflow-hidden">
<Col>
<DigitalMediaFrame digitalMedia={digitalMedia} />
<DigitalMediaFrame digitalMedia={digitalMedia}
annotoriousMode={annotoriousMode}
SetAnnotoriousMode={SetAnnotoriousMode}
/>
</Col>
</Row>
<Row className={`${styles.digitalMediaList} mt-2`}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,43 @@
/* Import Dependencies */
import { Annotorious } from "@annotorious/react";

/* Import Components */
import { ImageViewer } from "components/elements/Elements";

/* Import Types */
import { DigitalMedia } from "app/types/DigitalMedia";

/* Import API */
import GetDigitalMediaAnnotations from "api/digitalMedia/GetDigitalMediaAnnotations";


/* Props Type */
type Props = {
digitalMedia: DigitalMedia
digitalMedia: DigitalMedia,
annotoriousMode: string,
SetAnnotoriousMode: Function
};


/**
* Component that renders the digital media frame with image viewer on the digital media page
* @param digitalMedia The selected digital media item
* @param annotoriousMode The currently selected Annotorious mode
* @param SetAnnotoriousMode Function to set the Annotirous mode
* @returns JSX Component
*/
const DigitalMediaFrame = (props: Props) => {
const { digitalMedia } = props;
const { digitalMedia, annotoriousMode, SetAnnotoriousMode } = props;

return (
<div className="h-100">
<ImageViewer digitalMedia={digitalMedia} />
<Annotorious>
<ImageViewer digitalMedia={digitalMedia}
annotoriousMode={annotoriousMode}
GetAnnotations={() => GetDigitalMediaAnnotations({ handle: digitalMedia["ods:ID"].replace(import.meta.env.VITE_DOI_URL, '') })}
SetAnnotoriousMode={SetAnnotoriousMode}
/>
</Annotorious>
</div>
);
};
Expand Down
20 changes: 17 additions & 3 deletions src/components/digitalMedia/components/topBar/TopBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,30 @@ import GetDigitalMediaVersions from "api/digitalMedia/GetDigitalMediaVersions";

/* Import Components */
import { TopBarActions } from "components/elements/Elements";
import { Dropdown } from "components/elements/customUI/CustomUI";
import { Button, Dropdown } from "components/elements/customUI/CustomUI";


/* Props type */
type Props = {
digitalMedia: DigitalMedia,
annotationMode: boolean,
ToggleAnnotationSidePanel: Function
annotoriousMode: string,
ToggleAnnotationSidePanel: Function,
SetAnnotoriousMode: Function
};


/**
* Component that renders the top bar on the digital specimen page
* @param digitalSpecimen The selected digital specimen
* @param annotationMode Boolean that indicates if the annotation mode is toggled
* @param annotoriousMode String indicating the Annotorious mode
* @param ToggleAnnotationSidePanel Function to toggle the annotation side panel
* @param SetAnnotoriousMode Function to set the Annotorious mode
* @returns JSX Component
*/
const TopBar = (props: Props) => {
const { digitalMedia, annotationMode, ToggleAnnotationSidePanel } = props;
const { digitalMedia, annotationMode, annotoriousMode, ToggleAnnotationSidePanel, SetAnnotoriousMode } = props;

/* Hooks */
const fetch = useFetch();
Expand Down Expand Up @@ -129,6 +133,16 @@ const TopBar = (props: Props) => {
</Col>
</Row>
</Col>
<Col lg="auto">
<Button type="button"
variant="primary"
OnClick={() => SetAnnotoriousMode(annotoriousMode === 'move' ? 'draw' : 'move')}
>
<p>
{annotoriousMode === 'move' ? 'Visual Annotation' : 'Cancel visual annotation'}
</p>
</Button>
</Col>
<Col>
<TopBarActions actionDropdownItems={actionDropdownItems}
annotationMode={annotationMode}
Expand Down
7 changes: 7 additions & 0 deletions src/components/elements/digitalMedia/DigitalMedia.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/* Custom styling for digital media elements */

.imagePopup {
min-width: 400px;
height: 150px;
max-height: 150px;
}
Loading

0 comments on commit fb8e39c

Please sign in to comment.