labo-components
Version:
246 lines (215 loc) • 7.76 kB
JSX
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import IDUtil from '../../../util/IDUtil';
import { SELECTION_TEMPORAL, SELECTION_SPATIAL } from '../../../util/AnnotationConstants';
import { MSAnnotationUtil } from '../AnnotationClient';
import { getSegmentTitle } from '../AnnotationHelpers';
import { ResourceViewerContext } from '../ResourceViewerContext';
import TimeInput from './TimeInput';
import Strings from '../_Strings';
export default class SegmentHeader extends React.PureComponent {
static contextType = ResourceViewerContext;
constructor(props) {
super(props);
this.state = {
edit: false,
};
this.editSelection = null;
}
componentDidUpdate() {
// stop edit mode when not highlighted
if (!this.props.highlight && this.state.edit) {
this.stopEdit();
}
}
// INLINE EDITING
// return an update function for editing a temporal selection
updateTemporalEditSelection = (field) => {
return (value) => {
this.editSelection[field] = value;
};
};
// return an update function for editing a spatial selection
updateSpatialEditSelection = (field) => {
return (e) => {
this.editSelection.rect[field] = e.target.value;
};
};
// return a save/setter function for the
saveSelection = (segment) => {
return () => {
const selection = Object.assign({}, this.editSelection);
this.props.updateSegment(segment, selection);
this.stopEdit();
};
};
// delete segment
deleteSegment = (segment) => {
return (e) => {
e.stopPropagation();
if (!confirm(Strings.SEGMENT_DELETE_CONFIRM)) {
return;
}
// sending null to the update function results in the segment getting deleted
this.props.updateSegment(segment, null);
this.stopEdit();
};
};
onTitleClick = (e) => {
e.stopPropagation();
if (!this.props.highlight) {
this.props.onSelect(this.props.segment);
}
};
onTitleDoubleClick = (e) => {
e.stopPropagation();
this.setState({ edit: true });
};
stopEdit = () => {
this.setState({ edit: false });
};
stopEvent = (e) => {
e.stopPropagation();
};
// Render form for inline editing
renderForm = (segment) => {
// Check for valid selection
const selection = MSAnnotationUtil.extractSelectionFromTarget(
segment.target
);
if (!selection) {
console.warn("Could not find selection for target", segment.target);
return null;
}
// store selection that is being edited
this.editSelection = selection;
switch (selection.type) {
case SELECTION_TEMPORAL:
return (
<div
className="form temporal"
onClick={this.stopEvent}
title={Strings.SEGMENT_TITLE_EDIT_SUBMIT}
>
<TimeInput
time={selection.start}
onUpdate={this.updateTemporalEditSelection("start")}
onFinish={this.saveSelection(segment)}
/>
{" - "}
<TimeInput
time={selection.end}
onUpdate={this.updateTemporalEditSelection("end")}
onFinish={this.saveSelection(segment)}
/>
</div>
);
case SELECTION_SPATIAL:
const inputs = ["x", "y", "w", "h"].map((v) => (
<span key={v}>
<input
type="number"
ref={this[v]}
defaultValue={selection.rect[v]}
onFocus={(e) => {
e.target.select();
}}
onChange={this.updateSpatialEditSelection(v)}
onKeyDown={(e) => {
if (e.keyCode == 13) {
this.saveSelection(segment)();
}
}}
/>
</span>
));
return (
<div
className="form spatial"
onClick={this.stopEvent}
title={Strings.SEGMENT_TITLE_EDIT_SUBMIT}
>
{inputs}
</div>
);
default:
return null;
}
};
playSegment = (startTime) => {
if (startTime && this.context.playerAPI) {
const resourceStart =
this.context.activeMediaObject &&
this.context.activeMediaObject.resourceStart
? this.context.activeMediaObject.resourceStart + startTime
: startTime;
this.context.playerAPI.seek(resourceStart);
}
};
render() {
// Delete button
const deleteButton = (
<span
className="delete"
title={Strings.SEGMENT_DELETE_TITLE}
onClick={this.deleteSegment(this.props.segment)}
/>
);
// content is a titletext or an editable form
const content = this.state.edit ? (
// edit mode
this.renderForm(this.props.segment)
) : (
// view mode
<span
className="title"
title={Strings.SEGMENT_TITLE_EDIT}
onClick={this.onTitleClick}
onDoubleClick={this.onTitleDoubleClick}
>
{getSegmentTitle(this.props.segment, this.props.activeTypes)}
</span>
);
const startTime =
this.props.segment.target.selector &&
this.props.segment.target.selector.refinedBy
? this.props.segment.target.selector.refinedBy.start
: null;
return (
<div
className={classNames(IDUtil.cssClassName("segment-header"), {
active: this.props.active,
highlight: this.props.highlight,
})}
onClick={this.props.onToggle}
>
<span
className="play-segment"
onClick={(e) => {
this.playSegment(startTime);
e.stopPropagation();
}}
>
<i className="fas fa-play" title="Play segment" />
</span>
<span className="left">{content}</span>
<span className="count">
{this.props.segment.body
? this.props.segment.body.length
: 0}
</span>
{deleteButton}
</div>
);
}
}
SegmentHeader.propTypes = {
segment: PropTypes.object.isRequired,
active: PropTypes.bool.isRequired,
highlight: PropTypes.bool.isRequired,
onToggle: PropTypes.func.isRequired,
onSelect: PropTypes.func.isRequired,
updateSegment: PropTypes.func.isRequired,
activeTypes: PropTypes.array.isRequired,
};