-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Text spanner renderer and Article Viewing and paging in Researcher UI (…
…#170) * Stub in ArticleView container. * Add basic layout styling to ArticleView. * Stub in Spanner and SpannerState. * Stub in props for Spanner. Rename SpannerState to EditorState. * Code for creating and tracking DisplayState. * Add prop-types package that replaces deprecated React.PropTypes. * Framing in convertToSpanner, EditorState, ContentState, and Block. * Implement block rendering. By default use line breaks to create blocks. * Refactor Blocks to be part of DisplayState. * Change block function to return props to merge instead of just style. * Add topic name and order to Highlight Group serializer. Assign order numbers when creating topics for unmatched TUA types. * Begining of revisions to enable Spanner to render nested blocks. * Add classes for modeling layers of annotations to Spanner. Add articleView code to load highlights into EditorState. * Add algorithm to generate character spans from potentially overlapping layers. * Add span rendering to TextSpanner. * Create SpanSegment and cache per block. Cache CharacterClasses once per EditorState. * DisplayState provides ordered layers to mergeStyleFn. Improve naming and cache management of EditorState charAnnotations. * Cleanups. * Add component to display article metadata. * Now using django_filter on endpoints to query for multiple article_numbers at a time. URLs moved to /researcher/articles/ and /api/articles/ Formerly article endpoints retrieved a single article dataset based on the Django DB id. * Add slider control to ArticleView. * Add pager controls to ArticleView. * Improve Pager control. Minor tweaks to ArticleSlider, ArticleView, ArticleMetaData components. * Add validation for annotation and block offsets in EditorState. * ArticleView passes state change function to Pager so Pager can be a pure component. * Use a good key for span. * Add 'View Article Highlights' link to Researcher menu, and as action to Article admin. * Create ConsistentColors class so ArticleView topic colors are consistent between articles. Use top and bottom border colors for overlapping annotations. Generalize params for displayLinesAsBlocks and move to TextSpanner/utils. * Sort layers by topic name and case number in Article View. * Add wrapSpanFn prop to Spanner. Provide default no-op functions for Spanner function props. * Allow LayerState to be labeled with any object caller wants to use as label. This is to support highlights as topics or highlights as answers. Move domain specific TopicLabel class out of TextSpanner tree.
- Loading branch information
1 parent
2236132
commit c6a2d43
Showing
37 changed files
with
1,114 additions
and
51 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
|
||
function appendIfExists(metadata, fieldOrder) { | ||
let sequence = []; | ||
for (let field of fieldOrder) { | ||
if (field.name in metadata | ||
&& metadata[field.name] !== null | ||
&& metadata[field.name] !== undefined) { | ||
if (field.label !== '') { | ||
sequence.push(field.label + ": " + metadata[field.name]); | ||
} else {; | ||
sequence.push(metadata[field.name]); | ||
}; | ||
}; | ||
}; | ||
return sequence.join(', ');; | ||
} | ||
|
||
export class ArticleMetaData extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
} | ||
|
||
static propTypes = { | ||
metadata: PropTypes.object.isRequired, | ||
} | ||
|
||
render() { | ||
let metadata = this.props.metadata; | ||
|
||
let fieldOrder = [ | ||
{name: 'article_number', label: 'Article'}, | ||
{name: 'periodical', label: ''}, | ||
{name: 'periodical_code', label: 'Periodical code'}, | ||
{name: 'city', label: ''}, | ||
{name: 'state', label: ''}, | ||
{name: 'date_published', label: 'Published'}, | ||
{name: 'version', label: 'version'}, | ||
]; | ||
let elements = []; | ||
let citation = appendIfExists(metadata, fieldOrder); | ||
if (citation !== '') { | ||
elements.push( | ||
<div key='1' className="article-citation"> | ||
{citation} | ||
</div> | ||
); | ||
}; | ||
|
||
// Only Deciding Force articles have annotators in their metadata. | ||
if ('annotators' in metadata) { | ||
let annotators = metadata['annotators'].join(' '); | ||
if (annotators !== '') { | ||
elements.push( | ||
<div key='2' className="article-annotator"> | ||
Annotators: {annotators} | ||
</div> | ||
); | ||
}; | ||
}; | ||
|
||
fieldOrder = [{name: 'filename', label: 'Filename'}]; | ||
let filename = appendIfExists(metadata, fieldOrder); | ||
if (filename !== '') { | ||
elements.push( | ||
<div key='3' className="article-filename"> | ||
{filename} | ||
</div> | ||
); | ||
}; | ||
return ( | ||
<div> | ||
{elements} | ||
</div> | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
|
||
export class ArticleSlider extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
} | ||
|
||
static propTypes = { | ||
article_index: PropTypes.number.isRequired, | ||
article_ids: PropTypes.array.isRequired, | ||
onChange: PropTypes.func.isRequired, | ||
} | ||
|
||
render() { | ||
return ( | ||
<input type="range" | ||
value={this.props.article_index} | ||
min={0} | ||
max={this.props.article_ids.length - 1} | ||
step={1} | ||
onChange={this.props.onChange} | ||
/> | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
|
||
export class Pager extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
} | ||
|
||
static propTypes = { | ||
result: PropTypes.object.isRequired, | ||
fetchArticles: PropTypes.func.isRequired, | ||
} | ||
|
||
render() { | ||
let elem = []; | ||
let result = this.props.result; | ||
if (result.previous !== null) { | ||
elem.push( | ||
<a key="prev" | ||
onClick={ (e) => { | ||
this.props.fetchArticles(result.previous); | ||
e.preventDefault(); | ||
}} | ||
style={{'cursor': 'pointer'}} | ||
className="previous-group-button"> | ||
Previous Group | ||
</a>); | ||
}; | ||
if (result.next !== null) { | ||
elem.push( | ||
<a key="next" | ||
onClick={ (e) => { | ||
this.props.fetchArticles(result.next); | ||
e.preventDefault(); | ||
}} | ||
style={{'cursor': 'pointer', 'float': 'right'}} | ||
className="next-group-button"> | ||
Next Group | ||
</a>); | ||
}; | ||
return ( | ||
<div className="prev-next-pager clearfix"> | ||
{elem} | ||
</div> | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
|
||
import { EditorState } from '../model/EditorState'; | ||
import { DisplayState } from '../model/DisplayState'; | ||
|
||
const debug = require('debug')('thresher:TextSpanner'); | ||
|
||
export class Spanner extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
} | ||
|
||
static propTypes = { | ||
editorState: PropTypes.instanceOf(EditorState).isRequired, | ||
displayState: PropTypes.instanceOf(DisplayState).isRequired, | ||
blockPropsFn: PropTypes.func, | ||
mergeStyleFn: PropTypes.func, | ||
wrapSpanFn: PropTypes.func, | ||
} | ||
|
||
static defaultProps = { | ||
blockPropsFn: ((block, sequence_number) => {}), | ||
mergeStyleFn: ((orderedLayers) => {}), | ||
wrapSpanFn: ((span) => span) | ||
}; | ||
|
||
render() { | ||
const editorState = this.props.editorState; | ||
const displayState = this.props.displayState; | ||
const blockPropsFn = this.props.blockPropsFn; | ||
const mergeStyleFn = this.props.mergeStyleFn; | ||
const wrapSpanFn = this.props.wrapSpanFn; | ||
const text = editorState.getText(); | ||
|
||
function renderSpans(block) { | ||
let spans = editorState.getSpans(block); | ||
return (spans.map( (span, i) => { | ||
let orderedLayers = displayState.getOrderedLayersFor(span.spanAnnotations); | ||
let mergedStyle = mergeStyleFn(orderedLayers); | ||
let titleList = orderedLayers.map( (ola) => ola.annotation.topicName ); | ||
let title = titleList.join(', '); | ||
return (wrapSpanFn( | ||
<span key={span.key} | ||
data-offset-start={span.start} | ||
data-offset-end={span.end} | ||
style={mergedStyle} | ||
title={title}> | ||
{text.substring(span.start, span.end)} | ||
</span> | ||
)); | ||
})); | ||
}; | ||
|
||
function renderBlock(block, i) { | ||
const mergeProps = blockPropsFn(block, i); | ||
return React.cloneElement( | ||
<div key={block.key} | ||
data-offset-start={block.start} | ||
data-offset-end={block.end}> | ||
{renderSpans(block)} | ||
</div>, | ||
mergeProps | ||
); | ||
}; | ||
|
||
return ( | ||
<div> | ||
{displayState.getBlockList().map( (block, i) => { | ||
return renderBlock(block, i); | ||
})} | ||
</div> | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export {Spanner} from './components/Spanner'; | ||
export {EditorState} from './model/EditorState'; | ||
export {displayLinesAsBlocks} from './utils'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { Record } from 'immutable'; | ||
|
||
import generateRandomKey from '../utils/generateRandomKey'; | ||
|
||
const debug = require('debug')('thresher:TextSpanner'); | ||
|
||
const defaultAnnotationRecord = { | ||
key: '', | ||
topicName: '', | ||
topicOrder: 0, | ||
caseNumber: 0, | ||
start: 0, | ||
end: 0, | ||
contributor: {}, | ||
extra: {}, | ||
} | ||
|
||
const AnnotationRecord = Record(defaultAnnotationRecord); | ||
|
||
export class Annotation extends AnnotationRecord { | ||
constructor(annotation) { | ||
annotation['key'] = generateRandomKey(); | ||
super(annotation); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { Record } from 'immutable'; | ||
|
||
import generateRandomKey from '../utils/generateRandomKey'; | ||
|
||
const debug = require('debug')('thresher:TextSpanner'); | ||
|
||
const defaultBlockRecord = { | ||
key: '', | ||
blockType: 'unstyled', | ||
start: 0, | ||
end: 0, | ||
depth: 0, | ||
options: {} | ||
} | ||
|
||
const BlockRecord = Record(defaultBlockRecord); | ||
|
||
export class Block extends BlockRecord { | ||
constructor(block) { | ||
block['key'] = generateRandomKey(); | ||
super(block); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { Record } from 'immutable'; | ||
|
||
import generateRandomKey from '../utils/generateRandomKey'; | ||
|
||
const debug = require('debug')('thresher:TextSpanner'); | ||
|
||
export class ContentState { | ||
constructor() { | ||
this._text = ""; | ||
this.setText = this._setText.bind(this); | ||
this.getText = this._getText.bind(this); | ||
} | ||
|
||
_setText(text) { | ||
this._text = text; | ||
} | ||
|
||
_getText() { | ||
return this._text; | ||
} | ||
|
||
} |
Oops, something went wrong.