labo-components
Version:
369 lines (329 loc) • 13.6 kB
JSX
import React from "react";
import PropTypes from "prop-types";
import IconUtil from "../../../util/IconUtil";
import IDUtil from "../../../util/IDUtil";
import SessionStorageHandler from "../../../util/SessionStorageHandler";
import Strings from "../_Strings";
import Info from "../../shared/Info";
import { AnnotationEvents } from "../AnnotationClient";
import SingleAnnotationTarget from "./SingleAnnotationTarget";
import AnnotationFilter from "./AnnotationFilter";
import SegmentAnnotations from "./SegmentAnnotations";
import TargetHeader from "./TargetHeader";
import {
ANNOTATION_TARGET,
SESSION_STORAGE_ACTIVE_TARGETS,
} from "../../../util/AnnotationConstants";
export default class AnnotationList extends React.PureComponent {
constructor(props) {
super(props);
// get active targets from session storage
const activeTargets = SessionStorageHandler.getSplit(
SESSION_STORAGE_ACTIVE_TARGETS
);
// initial state
this.state = {
activeTargets,
activeLayers: [],
};
// Annotation events to listen to
this.annotationEvents = [
AnnotationEvents.ON_SET_ANNOTATION,
AnnotationEvents.ON_SAVE,
AnnotationEvents.ON_DELETE,
AnnotationEvents.ON_CHANGE_TARGET,
];
this.segmentTitleInput = React.createRef();
}
componentDidMount() {
// Bind to annotation updates
this.annotationEvents.forEach((event) => {
this.props.annotationClient.events.bind(event, this.onUpdate);
});
}
componentWillUnmount() {
// Unbind annotation updatess
this.annotationEvents.forEach((event) => {
this.props.annotationClient.events.unbind(event, this.onUpdate);
});
}
onUpdate = () => this.forceUpdate();
// Wrapper function for retrieving an annotation / all annotations for a given target
getAnnotationsForTarget(allAnnotations, resource, mediaObject) {
return (target) => {
const annotations = allAnnotations.filter(
(annotation) =>
// only with target type
annotation.target && annotation.target.type === target
);
// the checks on current resource/mediaobject are needed
// as sometimes other annotations from the project seem to slip in
switch (target) {
case ANNOTATION_TARGET.RESOURCE: {
const result = annotations.filter(
(annotation) =>
// current resource
annotation.target.source == resource.resourceId
);
// check number of annotations
if (result.length > 1) {
console.error(
"Multiple root annotations for target " +
target +
". Expecting 1 or 0"
);
console.debug(result);
// this.props.annotationClient.delete(result[1]);
}
if (result.length > 0) {
return result[0];
}
return null;
}
case ANNOTATION_TARGET.MEDIAOBJECT: {
let result = annotations.filter(
(annotation) =>
// current mediaObject
mediaObject &&
annotation.target.source == mediaObject.assetId
);
// // DEV: remove duplicate MediaObject annotations
// // You'll need this if you accidentially messed up selection creation and create multiple
// // annotations directly on the MediaObject (only 1 allowed)
// console.log(result);
// // The Media Object id tou want to keep
// const validMediaObjectAnnotationId =
// "19f47bb8-5fe0-4452-9d78-78da9514ee77";
// // Delete unwanted
// result.forEach((r) => {
// if (r.id !== validMediaObjectAnnotationId) {
// console.log(r);
// this.props.annotationClient.delete(r);
// }
// });
// // Filter out unwanted for displaying
// result = result.filter(
// (r) => r.id == validMediaObjectAnnotationId
// );
// check number of annotations
if (result.length > 1) {
console.error(
"Multiple root annotations for target " +
target +
". Expecting 1 or 0"
);
console.debug(result);
// this.props.annotationClient.delete(result[1]);
}
if (result.length > 0) {
return result[0];
}
return null;
}
case ANNOTATION_TARGET.SEGMENT:
// can return multiple in an array
return annotations.filter((annotation) =>
// segment from current mediaObject
resource.playableContent.some(
(mo) => annotation.target.source == mo.assetId
)
);
default:
console.error(
"Could not find annotations for type ",
target
);
return [];
}
};
}
/* ---------------------------------- UI FUNCTIONS -------------------------------- */
// Toggle given target
// Persist the state in session storage
toggleTarget = (target) => {
this.setState(
{
activeTargets: this.state.activeTargets.includes(target)
? this.state.activeTargets.filter((l) => l != target)
: [target, ...this.state.activeTargets],
},
() => {
// store active targets to session storage
SessionStorageHandler.set(
SESSION_STORAGE_ACTIVE_TARGETS,
this.state.activeTargets.join(",")
);
}
);
};
toggleSegmentsTarget = () => {
this.toggleTarget(ANNOTATION_TARGET.SEGMENT);
};
toggleLayer = (layerId) => {
this.setState({
activeLayers: this.state.activeLayers.includes(layerId)
? this.state.activeLayers.filter((l) => l != layerId)
: [layerId, ...this.state.activeLayers],
});
};
// /* ---------------------------------- RENDER FUNCTIONS -------------------------------- */
renderNewSegmentLayer() {
return (
<div className="segment-actions">
<input ref={this.segmentTitleInput} />
<div
key="new-layer-button"
className="button-new-layer btn btn-primary"
onClick={() => {
this.props.annotationClient.segmentLayers.addLayer(
this.segmentTitleInput.current.value
);
this.segmentTitleInput.current.value = "";
}}
>
{Strings.BUTTON_ADD_USER_LAYER}
</div>
</div>
);
}
render() {
const { resource, mediaObject, annotationClient } = this.props;
// at least a resource is required
if (!resource) {
return null;
}
// all annotations for this project
const annotations = annotationClient.annotations;
// create a wrapper for getting all annotations per target
// for the RESOURCE and MEDIAOBJECT targets, there should only be 1 annotation
// for the SEGMENTS, there will be multiple
const getAnnotations = this.getAnnotationsForTarget(
annotations,
resource,
mediaObject
);
// Props used by all targets
const defaultTargetProps = {
annotationClient: annotationClient,
activeTypes: this.props.activeAnnotationTypes,
toggle: this.toggleTarget,
};
// Resource target annotations
const resourceAnnotation = getAnnotations(ANNOTATION_TARGET.RESOURCE);
const resourceActive = this.state.activeTargets.includes(
ANNOTATION_TARGET.RESOURCE
);
const resourceTarget = (
<SingleAnnotationTarget
target={ANNOTATION_TARGET.RESOURCE}
icon={
<Info
id="annotation-target-resource-help"
text={Strings.ANNOTATION_TARGET_HELP_RESOURCE}
className="black left"
/>
}
title={Strings.ANNOTATION_TARGET_TITLE_RESOURCE}
active={resourceActive}
annotation={resourceAnnotation}
{...defaultTargetProps}
/>
);
// Mediaobject target annotations
const mediaObjectAnnotation = getAnnotations(
ANNOTATION_TARGET.MEDIAOBJECT
);
const mediaObjectActive = this.state.activeTargets.includes(
ANNOTATION_TARGET.MEDIAOBJECT
);
const mediaObjectTarget = this.props.mediaObject ? (
<SingleAnnotationTarget
target={ANNOTATION_TARGET.MEDIAOBJECT}
icon={
<span
title={Strings.ANNOTATION_TARGET_HELP_MEDIAOBJECT}
className={IconUtil.getMimeTypeIcon(
this.props.mediaObject.mimeType,
false,
false,
false
)}
/>
}
title={this.props.mediaObject.assetId}
active={mediaObjectActive}
annotation={mediaObjectAnnotation}
{...defaultTargetProps}
/>
) : null;
let segmentHeader = null;
let segmentTargets = null;
const segmentsActive = this.state.activeTargets.includes(
ANNOTATION_TARGET.SEGMENT
);
// Segment header
if (this.props.mediaObject) {
segmentHeader = (
<>
<TargetHeader
active={segmentsActive}
onToggle={this.toggleSegmentsTarget}
title={Strings.ANNOTATION_TARGET_TITLE_SEGMENT}
count={getAnnotations(ANNOTATION_TARGET.SEGMENT).length}
icon={
<Info
id="annotation-target-segment-help"
text={Strings.ANNOTATION_TARGET_HELP_SEGMENT}
className="black left"
/>
}
/>
{segmentsActive && this.renderNewSegmentLayer()}
</>
);
}
// Segments per layer
if (this.props.mediaObject && segmentsActive) {
// Segment target annotations per segment layer
const layers = annotationClient.segmentLayers.getLayersSorted();
segmentTargets = layers.map((layer) => {
return (
<SegmentAnnotations
key={layer.id}
target={ANNOTATION_TARGET.SEGMENT}
layerId={layer.id}
title={layer.title}
active={this.state.activeLayers.includes(layer.id)}
segments={annotationClient.segmentLayers.getSegments(
layer.id
)}
mediaObject={this.props.mediaObject}
annotationClient={annotationClient}
activeTypes={this.props.activeAnnotationTypes}
toggle={this.toggleLayer}
/>
);
});
}
// Full annotation list
return (
<div className={IDUtil.cssClassName("annotation-list")}>
{/* All annotation targets */}
<div className="targets">
{resourceTarget}
{mediaObjectTarget}
{segmentHeader}
{segmentTargets}
</div>
{/* Filter*/}
<AnnotationFilter />
</div>
);
}
}
AnnotationList.propTypes = {
annotationClient: PropTypes.object.isRequired,
activeAnnotationTypes: PropTypes.arrayOf(PropTypes.string).isRequired,
mediaObject: PropTypes.object,
resource: PropTypes.object,
};