labo-components
Version:
223 lines (196 loc) • 6.87 kB
JSX
import React from 'react';
import PropTypes from 'prop-types';
import IconUtil from '../../util/IconUtil';
import trunc from '../../util/Trunc';
import IDUtil from '../../util/IDUtil';
import RegexUtil from '../../util/RegexUtil';
export default class SearchSnippet extends React.Component {
constructor(props) {
super(props);
this.CLASS_PREFIX = "ss";
}
static createMarkup(text) {
return { __html: text };
}
//NOTE: when needed this function can be extended with an anonymous user check in combination with:
//collectionConfig.getAnonymousUserRestrictions().prohibitThumbnails === true
renderPosterImage = posterURL => {
if (!posterURL) return null;
return (
<div className={IDUtil.cssClassName("poster", this.CLASS_PREFIX)}>
<img
className="media-object"
src="/static/images/placeholder.2b77091b.svg"
data-src={posterURL}
alt="Could not find image"
/>
</div>
);
};
renderMediaIcons = mediaTypes => {
if (!mediaTypes) return null;
return mediaTypes.map(mt => {
let title = null;
switch(mt) {
case 'video' : title = "Video content"; break;
case 'audio' : title = "Audio content"; break;
case 'image' : title = "Image content"; break;
case 'text' : title = "Textual content"; break;
}
return (
<span key={'__mi__' + mt} className={IconUtil.getMimeTypeIcon(mt, false, true, false)} title={title}/>
);
});
};
renderAccessabilityIcon = isPlayable => {
//Note: assigning a media type (to the result data) automatically means it's accessible in the media suite!
if (isPlayable) {
// Data should be visible by default, so prevent any clutter
// by showing for all items they are visible.
return null;
} else {
return (
<span className={IconUtil.getMediaObjectAccessIcon(false, false, true, true, false)} title="Media object(s) not accessible"/>
);
}
};
renderTags = tags => {
if (!tags) return null;
return tags.map(t => {
return <span>{t}</span>;
});
};
renderFragmentIcon = () => {
return (
<span
className={IconUtil.getMimeTypeIcon("fragment", true, true)}
title="Media fragment"
/>
);
};
renderFragmentSnippet = fragment => {
if (!fragment) return null;
return (
<div className={IDUtil.cssClassName("fragment", this.CLASS_PREFIX)}>
{fragment.snippet}
</div>
);
};
renderHighlights = (collectionConfig, snippets, highlightRegex) => {
if(!snippets) return null;
//TODO use collectionConfig.getNumberOfHighlightsToDisplay to cull the amount of snippets shown
const markup = Object.keys(snippets).map(field => {
return "</br><span class=fieldNameText>" +
collectionConfig.toPrettyFieldName(field) +
"</span>: " +
snippets[field].map(hl => RegexUtil.highlightText(hl, highlightRegex))
});
return <div
className={IDUtil.cssClassName("snippet-description", this.CLASS_PREFIX)}
dangerouslySetInnerHTML={SearchSnippet.createMarkup(markup)}
/>
};
fixMsg = s => (s ? s.replace(new RegExp(/\|$/,"g"), "") : null);
//possible default fields: posterURL, title, description, tags
render() {
const poster = this.renderPosterImage(this.props.data.posterURL);
const mediaTypes = this.renderMediaIcons(this.props.data.mediaTypes);
const accessIcon = this.renderAccessabilityIcon(
this.props.data.playable
);
const tags = this.renderTags(this.props.data.tags);
let fragmentIcon,
fragmentSnippet = null;
if (this.props.data.type === "media_fragment") {
fragmentIcon = this.renderFragmentIcon();
fragmentSnippet = this.renderFragmentSnippet(
this.props.data.mediaFragment
);
}
const classNames = [IDUtil.cssClassName("search-snippet")];
const title = this.props.data.title ? this.props.data.title + " " : "";
const date = this.props.data.date ? this.props.data.date : "";
const subHeading = (
<div>
<i>{date}</i>
<span>{this.fixMsg(this.props.data.highlightMsg)}</span>
</div>
);
const highlights = this.renderHighlights(
this.props.collectionConfig,
this.props.data.matchesPerField,
this.props.data.highlightRegex
);
return (
<div className={classNames.join(" ")} onClick={this.props.onClick}>
{poster}
<div className={IDUtil.cssClassName('media-body', this.CLASS_PREFIX)}>
{/* Title */}
<h3 className={IDUtil.cssClassName('title', this.CLASS_PREFIX)} title={this.props.data.id}>
<span dangerouslySetInnerHTML={
SearchSnippet.createMarkup(RegexUtil.highlightText(
title ? trunc(title, 200) : "",
this.props.searchRegex
))
}/>
{/* Icons access */}
<span className={IDUtil.cssClassName("icon-access", this.CLASS_PREFIX)}>{accessIcon}</span>
</h3>
{/* Description snippet with possible highlights */}
<div
className={IDUtil.cssClassName("snippet-description", this.CLASS_PREFIX)}
dangerouslySetInnerHTML={SearchSnippet.createMarkup(
RegexUtil.highlightText(
RegexUtil.findFirstHighlightInText(
this.props.data.description,
this.props.searchRegex,
35
),
this.props.searchRegex
)
)}
/>
{highlights}
{fragmentSnippet}
{/* Info heading/icons */}
<div className={IDUtil.cssClassName("info", this.CLASS_PREFIX)}>
{/* Icons type */}
<div className={IDUtil.cssClassName('icon-type', this.CLASS_PREFIX)}>
{mediaTypes}{fragmentIcon}
</div>
{subHeading}
</div>
{tags}
</div>
</div>
);
}
}
SearchSnippet.propTypes = {
onClick: PropTypes.func.isRequired, // click callback
collectionConfig: PropTypes.object.isRequired,
searchRegex: PropTypes.instanceOf(RegExp), //the search regex that was used to find this hit (if there is a search term)
data: PropTypes.shape({
//all the data required to draw the information of this result snippet (see SearchResult.toSearchResultSnippet())
id: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
description: PropTypes.string,
date: PropTypes.string,
posterURL: PropTypes.string, //for loading a poster image
playable: PropTypes.bool, //for drawing the accessability icon
mediaTypes: PropTypes.arrayOf(PropTypes.string), //for drawing media type icons
tags: PropTypes.array, //for drawing any type of annotations (e.g. based on a the current user)
highlightRegex : PropTypes.regexp,
matchesPerField: PropTypes.object, //key = field name, value = array with highlights {text : '', regex : ''}
highlightMsg: PropTypes.string,
//this is only available when the search is configured to return media fragments (inner_hits)
mediaFragment: PropTypes.shape({
assetId: PropTypes.string,
url: PropTypes.string,
start: PropTypes.number,
end: PropTypes.number,
snippet: PropTypes.string,
layer: PropTypes.string
})
}).isRequired
};