-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #55 from CaptainFact/improvement/history
Display history as a table and add video actions to history
- Loading branch information
Showing
16 changed files
with
439 additions
and
209 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,25 @@ | ||
{ | ||
"compare_show": "Compare", | ||
"compare_hide": "Hide comparison", | ||
"compare_hide": "Hide", | ||
"compareAll": "Compare all", | ||
"hideAll": "Hide all", | ||
"when": "When", | ||
"who": "Who", | ||
"changes": "Changes", | ||
"revert": "Revert", | ||
"entity": "Entity", | ||
"moderation": "Moderation", | ||
"action": { | ||
"1": "created", | ||
"2": "removed", | ||
"3": "updated", | ||
"4": "deleted", | ||
"5": "added", | ||
"6": "restored" | ||
"1": "Created", | ||
"2": "Removed", | ||
"3": "Updated", | ||
"4": "Deleted", | ||
"5": "Added", | ||
"6": "Reverted" | ||
}, | ||
"this": { | ||
"2": "this speaker", | ||
"3": "this statement" | ||
"1": "Video", | ||
"2": "Speaker", | ||
"3": "Statement" | ||
} | ||
} |
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 |
---|---|---|
@@ -1,16 +1,25 @@ | ||
{ | ||
"compare_show": "Comparer", | ||
"compare_hide": "Masquer la comparaison", | ||
"compare_hide": "Masquer", | ||
"compareAll": "Tout comparer", | ||
"hideAll": "Tout masquer", | ||
"when": "Quand", | ||
"who": "Qui", | ||
"changes": "Changements", | ||
"revert": "Restaurer", | ||
"entity": "Entité", | ||
"moderation": "Modération", | ||
"action": { | ||
"1": "a créé", | ||
"2": "a retiré", | ||
"3": "a mis à jour", | ||
"4": "a supprimé", | ||
"5": "a ajouté", | ||
"6": "a restauré" | ||
"1": "Créé", | ||
"2": "Retiré", | ||
"3": "Mis à jour", | ||
"4": "Supprimé", | ||
"5": "Ajouté", | ||
"6": "Restauré" | ||
}, | ||
"this": { | ||
"2": "cet intervenant", | ||
"3": "cette affirmation" | ||
"1": "Vidéo", | ||
"2": "Intervenant", | ||
"3": "Affirmation" | ||
} | ||
} |
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,60 @@ | ||
import React, { PureComponent } from 'react' | ||
import PropTypes from 'prop-types' | ||
import Immutable from 'immutable' | ||
|
||
import titleCase from '../../lib/title_case' | ||
import { ENTITY_SPEAKER } from '../../constants' | ||
import EntityTitle from './EntityTitle' | ||
|
||
|
||
class ActionDiff extends PureComponent { | ||
render() { | ||
return ( | ||
<div className="action-diff"> | ||
{this.props.diff.entrySeq().map(([key, changes]) => ( | ||
<div key={ key } className="diff-entry"> | ||
<span className="diff-key"> | ||
{ titleCase(this.formatChangeKey(key)) } | ||
</span> | ||
<pre className="diff-view"> | ||
{ this.renderKeyDiff(key, changes) } | ||
</pre> | ||
</div> | ||
))} | ||
</div> | ||
) | ||
} | ||
|
||
renderKeyDiff(key, changes) { | ||
// Value completely changed, show it like prev -> new | ||
if (changes.size === 2 && changes.first().removed && changes.last().added) | ||
return <div> | ||
<span className="removed">{ this.formatChangeValue(changes.first().value, key) }</span>, | ||
<span> -> </span>, | ||
<span className="added">{ this.formatChangeValue(changes.last().value, key) }</span> | ||
</div> | ||
// Generate a real diff | ||
return changes.map((change, idx) => ( | ||
<span key={idx} | ||
className={ change.added ? 'added' : change.removed ? 'removed' : '' }> | ||
{ this.formatChangeValue(change.value, key) } | ||
</span> | ||
)) | ||
} | ||
|
||
formatChangeKey(key) { | ||
return key.replace('_id', '').replace('_', ' ') | ||
} | ||
|
||
formatChangeValue(value, key) { | ||
if (key === "speaker_id") | ||
return <EntityTitle entity={ENTITY_SPEAKER} entityId={value} withPrefix={false}/> | ||
return value | ||
} | ||
} | ||
|
||
ActionDiff.propTypes = { | ||
diff: PropTypes.instanceOf(Immutable.Map).isRequired | ||
} | ||
|
||
export default ActionDiff |
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,148 @@ | ||
import React, { PureComponent } from 'react' | ||
import Immutable from 'immutable' | ||
import PropTypes from 'prop-types' | ||
import { translate } from 'react-i18next' | ||
import { connect } from 'react-redux' | ||
|
||
import { TimeSince } from '../Utils/TimeSince' | ||
import UserAppellation from '../Users/UserAppellation' | ||
import { Icon } from '../Utils/Icon' | ||
import ActionDiff from './ActionDiff' | ||
import { generateAllDiffs, generateDiff, hideAllDiffs, hideDiff } from '../../state/user_actions/reducer' | ||
import EntityTitle from './EntityTitle' | ||
import { ACTION_DELETE, ACTION_REMOVE } from '../../constants' | ||
import { revertVideoDebateUserAction } from '../../state/video_debate/history/effects' | ||
import { LoadingFrame } from '../Utils/LoadingFrame' | ||
|
||
|
||
const ACTIONS_ICONS = [ | ||
"plus", // Create | ||
"times", // Remove | ||
"pencil", // Update | ||
"times", // Delete | ||
"plus", // Add | ||
"undo" // Restore | ||
] | ||
|
||
@translate(['history', 'main']) | ||
@connect( | ||
state => ({diffs: state.UsersActions.diffs, lastActionsIds: state.UsersActions.lastActionsIds}), | ||
{generateDiff, hideDiff, generateAllDiffs, hideAllDiffs, revertVideoDebateUserAction} | ||
) | ||
class ActionsTable extends PureComponent { | ||
render() { | ||
const availableDiffs = new Immutable.List(this.props.diffs.keys()) | ||
return ( | ||
<table className="actions-list table"> | ||
<thead>{this.renderHeader(availableDiffs)}</thead> | ||
<tbody>{this.renderBody(availableDiffs)}</tbody> | ||
</table> | ||
) | ||
} | ||
|
||
// ---- Table header ---- | ||
|
||
renderHeader = availableDiffs => { | ||
const { t, actions, showRestore, showEntity } = this.props | ||
const isMostlyComparing = availableDiffs.count() / actions.count() > 0.5 | ||
return ( | ||
<tr> | ||
<th>{t('when')}</th> | ||
<th>{t('who')}</th> | ||
<th>Action</th> | ||
{showEntity && <th>{t('entity')}</th>} | ||
<th>{this.renderCompareAllButton(isMostlyComparing)}</th> | ||
{showRestore && <th>{t('revert')}</th>} | ||
<th>{t('moderation')}</th> | ||
</tr> | ||
) | ||
} | ||
|
||
renderCompareAllButton = isMostlyComparing => | ||
<a className="button" title={this.props.t(isMostlyComparing ? 'hideAll' : 'compareAll')} | ||
onClick={isMostlyComparing ? this.props.hideAllDiffs : this.props.generateAllDiffs}> | ||
{this.props.t('changes')} | ||
</a> | ||
|
||
// ---- Table body ---- | ||
|
||
renderBody = (availableDiffs) => { | ||
if (this.props.isLoading) | ||
return <tr style={{background: 'none'}}><td colSpan={this.getNbCols()}><LoadingFrame/></td></tr> | ||
return this.props.actions.map(a => this.renderAction(availableDiffs, a)) | ||
} | ||
|
||
renderAction = (availableDiffs, action) => { | ||
if (availableDiffs.includes(action.id)) | ||
return [this.renderActionLine(action, true), this.renderDiffLine(action)] | ||
return this.renderActionLine(action) | ||
} | ||
|
||
renderActionLine(action, isDiffing=false) { | ||
const {showRestore, showEntity, t, revertVideoDebateUserAction} = this.props | ||
const reversible = showRestore && this.props.lastActionsIds.includes(action.id) && | ||
([ACTION_DELETE, ACTION_REMOVE].includes(action.type)) | ||
|
||
return ( | ||
<tr key={action.id}> | ||
<td><TimeSince time={ action.time }/></td> | ||
<td><UserAppellation user={action.user}/></td> | ||
<td>{this.renderActionIcon(action.type)}<strong>{ t(`action.${action.type}`) }</strong></td> | ||
{showEntity && <td><EntityTitle entity={action.entity} entityId={action.entity_id}/></td>} | ||
<td> | ||
<a className='button' onClick={() => this.toggleDiff(action, isDiffing)}> | ||
<Icon size="small" name="indent"/> | ||
<span>{ t(isDiffing ? 'compare_hide' : 'compare_show') } </span> | ||
</a> | ||
</td> | ||
{showRestore && <td>{reversible && | ||
<a className="button" onClick={() => revertVideoDebateUserAction(action)}> | ||
<Icon size="small" name="undo"/> | ||
<span>{t('revert')}</span> | ||
</a> | ||
}</td>} | ||
<td> | ||
<a className="is-disabled button"> | ||
<Icon size="small" name="check"/> | ||
<span>{t('main:actions.approve')}</span> | ||
</a> | ||
<a key="flag" className="is-disabled button"> | ||
<Icon size="small" name="flag"/> | ||
<span>{t('main:actions.flag')}</span> | ||
</a> | ||
</td> | ||
</tr> | ||
) | ||
} | ||
|
||
renderDiffLine = action => | ||
<tr key={`${action.id}-diff`}> | ||
<td colSpan={this.getNbCols()} style={{padding: 0}}> | ||
<ActionDiff action={action} diff={this.props.diffs.get(action.id)}/> | ||
</td> | ||
</tr> | ||
|
||
renderActionIcon = type => | ||
type <= ACTIONS_ICONS.length ? <Icon name={ACTIONS_ICONS[type - 1]} size="mini"/> : null | ||
|
||
toggleDiff = (action, isDiffing) => | ||
isDiffing ? this.props.hideDiff(action) : this.props.generateDiff(action) | ||
|
||
getNbCols = () => | ||
7 - !this.props.showRestore - !this.props.showEntity | ||
} | ||
|
||
ActionsTable.defaultProps = { | ||
isLoading: false, | ||
showRestore: true, | ||
showEntity: true | ||
} | ||
|
||
ActionsTable.propTypes = { | ||
actions: PropTypes.instanceOf(Immutable.List).isRequired, | ||
isLoading: PropTypes.bool, | ||
showRestore: PropTypes.bool, | ||
showEntity: PropTypes.bool | ||
} | ||
|
||
export default ActionsTable |
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,37 @@ | ||
import React, { PureComponent } from 'react' | ||
import PropTypes from 'prop-types' | ||
import { translate } from 'react-i18next' | ||
import { Link } from 'react-router' | ||
|
||
import { ENTITY_SPEAKER, ENTITY_VIDEO } from '../../constants' | ||
|
||
|
||
@translate('history') | ||
class EntityTitle extends PureComponent { | ||
render() { | ||
const {t, entity, entityId, withPrefix} = this.props | ||
let label = null | ||
|
||
if (entity === ENTITY_VIDEO) | ||
return t(`this.${entity}`) | ||
if (withPrefix) | ||
label = t(`this.${entity}`) + ` #${entityId}` | ||
else | ||
label = `#${entityId}` | ||
if (entity === ENTITY_SPEAKER) | ||
return <Link to={`/s/${entityId}`}>{label}</Link> | ||
return label | ||
} | ||
} | ||
|
||
EntityTitle.propTypes = { | ||
entity: PropTypes.number.isRequired, | ||
entityId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), | ||
withPrefix: PropTypes.bool, | ||
} | ||
|
||
EntityTitle.defaultProps = { | ||
withPrefix: true | ||
} | ||
|
||
export default EntityTitle |
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
Oops, something went wrong.