labo-components
Version:
473 lines (441 loc) • 18.7 kB
JSX
import IDUtil from '../../../../util/IDUtil';
import IconUtil from '../../../../util/IconUtil';
import AnnotationUtil from '../../../../util/AnnotationUtil';
import {
CUSTOM,
} from "../../../../util/AnnotationConstants";
import { secToTime } from '../../helpers/time';
import { AnnotationTranslator } from '../../helpers/AnnotationTranslator';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
/**
* A row with bookmark information, and actions, and sub level annotations
*/
class BookmarkRow extends React.PureComponent {
constructor(props) {
super(props);
// bind functions
this.onDelete = this.onDelete.bind(this);
this.toggleSubMediaObject = this.toggleSubMediaObject.bind(this);
this.toggleSubSegment = this.toggleSubSegment.bind(this);
}
onDelete() {
this.props.onDelete([this.props.bookmark.resourceId]);
}
openResourceViewer = (e, startTime=null) => {
if (this.props.bookmark.object) {
this.props.openResourceViewer(
{
resourceId: this.props.bookmark.object.id,
index: this.props.bookmark.object.dataset,
startTime
},
e.shiftKey
);
}
};
onSelectChange(e) {
this.props.onSelect(this.props.bookmark, e.target.checked);
}
toggleSubMediaObject(e) {
this.props.toggleSubMediaObject(this.props.bookmark.resourceId);
}
toggleSubSegment(e) {
this.props.toggleSubSegment(this.props.bookmark.resourceId);
}
renderSubMediaObject(bookmark, annotations, showHeader) {
// sort annotations by type
annotations.sort((a, b) =>
a.annotationType > b.annotationType ? 1 : -1
);
return !annotations || annotations.length === 0 ? (
<p>No annotations available</p>
) : (
<table>
{showHeader ? (
<thead>
<tr>
<th>Type</th>
<th>Content</th>
<th>Details</th>
</tr>
</thead>
) : null}
<tbody>
{annotations.map(annotation => {
return (
<tr>
<td className="type bold">
<Link
to={
'/workspace/projects/' +
this.props.projectId +
'/annotations#' +
annotation.annotationType +
'-centric'
}
>
{AnnotationTranslator(
annotation.annotationType
)}
</Link>
</td>
<td className="content">
{AnnotationUtil.annotationBodyToPrettyText(
annotation
)}
</td>
<td className="details">
{annotation.vocabulary
? 'Vocabulary: ' + annotation.vocabulary
: null}
{annotation.annotationType === 'comment'
? annotation.created
: null}
{annotation.url ? (
<a
rel="noopener noreferrer"
target="_blank"
href={'https:' + annotation.url}
>
{annotation.url
? annotation.url.replace(
/^\/\//i,
''
)
: ''}
</a>
) : null}
{annotation.annotationTemplate
? 'Template: ' +
annotation.annotationTemplate
: null}
</td>
</tr>
);
})}
</tbody>
</table>
);
}
renderSubSegment(bookmark, segments) {
return !segments || segments.length === 0 ? (
<p>
This {bookmark.object.type.toLowerCase() || 'object'} has no
fragments yet
</p>
) : (
<table>
<thead>
<tr>
<th className="time">Start/End time</th>
<th>
<table>
<thead>
<tr>
<th className="type">Type</th>
<th className="content">Content</th>
<th className="details">Details</th>
</tr>
</thead>
</table>
</th>
</tr>
</thead>
<tbody>
{segments.map(segment => (
<tr>
<td className="time">
{
segment.selector && segment.selector.refinedBy ? (
<span
onClick={() => this.openResourceViewer(this, parseInt(segment.selector.refinedBy.start))}
title={'View segment in resource viewer'}
className="go-to-segment-link"
>
{
secToTime(Math.round(segment.selector.refinedBy.start || 0)) +
' - ' +
secToTime(Math.round(segment.selector.refinedBy.end || 0))
}
</span>
) : '-'
}
</td>
<td>{this.renderSubMediaObject(segment, segment.annotations, false)}</td>
</tr>
))}
</tbody>
</table>
);
}
render() {
const bookmark = this.props.bookmark;
// prepare annotations
let annotations = bookmark.annotations || [];
// filter out CUSTOM annotations
annotations = annotations.filter((annotation)=>(annotation.annotationType !== CUSTOM));
if (this.props.annotationTypeFilter) {
// only show annotations of the type specified by the current filter
annotations = annotations.filter(
a => a.annotationType === this.props.annotationTypeFilter
);
}
const hasAnnotations = annotations.length > 0;
// prepare segments
const segments = bookmark.segments || [];
const hasSegments = segments.length > 0;
const showSub =
this.props.showSubMediaObject || this.props.showSubSegment;
//populate the foldable annotation block
let foldableBlock = null;
// render correct foldable block, if visible
switch (true) {
case this.props.showSubMediaObject:
foldableBlock = (
<div className="sublevel">
{this.renderSubMediaObject(bookmark, annotations, true)}
</div>
);
break;
case this.props.showSubSegment:
foldableBlock = (
<div className="sublevel">
{this.renderSubSegment(bookmark, segments)}
</div>
);
}
//format the date of the resource/target (i.e. bookmark.object.formattedDate, which is always an array)
const resourceDate = bookmark.object.formattedDate
? bookmark.object.formattedDate.map(d => (
<span>
{d}
<br />
</span>
))
: null;
//show the user what media can be expected
let mediaIcon = null;
let playableIcon = (
<span
className={IconUtil.getMediaObjectAccessIcon(
false,
false,
true,
true,
false
)}
title="Media object(s) not accessible"
></span>
);
if (bookmark.object.mediaTypes) {
mediaIcon = bookmark.object.mediaTypes.map(mt => {
if (mt === 'video') {
return (
<span
className={IconUtil.getMimeTypeIcon(
'video',
true,
true,
false
)}
title="Video content"
></span>
);
} else if (mt === 'audio') {
return (
<span
className={IconUtil.getMimeTypeIcon(
'audio',
true,
true,
false
)}
title="Audio content"
></span>
);
} else if (mt === 'image') {
return (
<span
className={IconUtil.getMimeTypeIcon(
'image',
true,
true,
false
)}
title="Image content"
></span>
);
} else if (mt === 'text') {
return (
<span
className={IconUtil.getMimeTypeIcon(
'text',
true,
true,
false
)}
title="Textual content"
></span>
);
}
});
}
if (bookmark.object.playable) {
playableIcon = (
<span
className={IconUtil.getMediaObjectAccessIcon(
true,
true,
true,
true,
false
)}
title="Media object(s) can be viewed"
></span>
);
}
return (
<div
className={classNames(
IDUtil.cssClassName('bookmark-row'),
'item-row'
)}
>
<div className="item">
<div className="selector">
<input
type="checkbox"
checked={this.props.selected}
onChange={this.onSelectChange.bind(this)}
title={
'Select this bookmark with resource ID:\n' +
bookmark.resourceId
}
/>
</div>
<div
className="image"
title={'Resource ID: ' + bookmark.resourceId}
onClick={this.openResourceViewer}
style={{
backgroundImage:
'url(' + bookmark.object.placeholderImage + ')'
}}
/>
<ul className="info">
<li className="primary content-title">
<h4 className="label">Title</h4>
<p
onClick={this.openResourceViewer}
title={'Resource ID: ' + bookmark.resourceId}
>
{bookmark.object.error
? 'error: source catalogue not available'
: bookmark.object.title}
</p>
</li>
<li className="content-date">
<h4 className="label">Date</h4>
<p title={bookmark.object.dateField}>
{resourceDate}
</p>
</li>
<li className="content-media">
<h4 className="label">Media</h4>
<p>
{mediaIcon} {playableIcon}
</p>
</li>
<li className="content-dataset">
<h4 className="label">Dataset</h4>
<p>{bookmark.object.dataset}</p>
</li>
<li>
<h4 className="label">Groups</h4>
<p className="groups">
{bookmark.groups
? bookmark.groups.map(g => (
<span>{g.label}</span>
))
: null}
</p>
</li>
</ul>
<div className="actions">
<div
className="btn primary"
onClick={this.openResourceViewer}
title="View item (go to resource viewer)"
>
View
</div>
<div className="row-menu">
<span>⋮</span>
<ul>
<li
onClick={this.props.onDelete.bind(
this,
bookmark
)}
>
Delete
</li>
<li
onClick={this.props.onExport.bind(
this,
bookmark
)}
>
Export
</li>
</ul>
</div>
<div className="sublevel-button-container">
<div
title="Fragments"
className={classNames('sublevel-button', {
active: this.props.showSubSegment,
zero: !hasSegments,
lowered: this.props.showSubMediaObject
})}
onClick={this.toggleSubSegment}
>
<span className="icon segment" />
<span className="count">{segments.length}</span>
</div>
<div
title="MediaObject annotations"
className={classNames('sublevel-button facet', {
active: this.props.showSubMediaObject,
zero: !hasAnnotations,
lowered: this.props.showSubSegment
})}
onClick={this.toggleSubMediaObject}
>
<span className="icon annotation" />
<span className="count">
{annotations.length}
</span>
</div>
</div>
</div>
</div>
{foldableBlock}
</div>
);
}
}
BookmarkRow.propTypes = {
bookmark: PropTypes.object.isRequired,
toggleSub: PropTypes.func.isRequired,
showSub: PropTypes.bool.isRequired,
onDelete: PropTypes.func.isRequired,
onExport: PropTypes.func.isRequired,
onSelect: PropTypes.func.isRequired,
openResourceViewer: PropTypes.func.isRequired,
selected: PropTypes.bool,
annotationTypeFilter: PropTypes.string
};
export default BookmarkRow;