Skip to content

Commit

Permalink
Merge pull request #322 from CaptainFact/feat/shift-statements-per-pr…
Browse files Browse the repository at this point in the history
…ovider

Timecodes shifting is now specific to video provider
  • Loading branch information
Betree authored Dec 19, 2018
2 parents dc46541 + 1eebc84 commit ca9605d
Show file tree
Hide file tree
Showing 21 changed files with 357 additions and 158 deletions.
3 changes: 1 addition & 2 deletions app/API/graphql_queries.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ export const VideosQuery = gql`
totalPages
entries {
hash_id: hashId
provider_id: providerId
provider
youtube_id: youtubeId
title
insertedAt
isPartner
Expand Down
10 changes: 10 additions & 0 deletions app/components/FormUtils/StyledLabel.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import styled from 'styled-components'
import { fontSize, space } from 'styled-system'

const StyledLabel = styled.label`
${fontSize}
${space}
`

/** @component */
export default StyledLabel
4 changes: 2 additions & 2 deletions app/components/Statements/Statement.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ import StatementHeader from './StatementHeader'
@withNamespaces('videoDebate')
export default class Statement extends React.PureComponent {
render() {
const { statement, speaker, handleEdit, handleDelete } = this.props
const { statement, speaker, handleEdit, handleDelete, offset = 0 } = this.props

return (
<div>
<StatementHeader
statement={statement}
statementTime={statement.time + offset}
speaker={speaker}
handleEdit={handleEdit}
handleDelete={handleDelete}
Expand Down
8 changes: 5 additions & 3 deletions app/components/Statements/StatementContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ import { handleFormEffectResponse } from '../../lib/handle_effect_response'
import StatementComments from './StatementComments'
import { CommentForm } from '../Comments/CommentForm'
import Statement from './Statement'
import { getTimecodesOffset } from '../../lib/video_utils'

@connect(
(state, props) => ({
offset: state.VideoDebate.video.offset,
speaker: statementSelectors.getStatementSpeaker(state, props),
isFocused: statementSelectors.isStatementFocused(state, props),
scrollTo: state.VideoDebate.statements.scrollTo,
Expand All @@ -42,9 +44,7 @@ export default class StatementContainer extends React.PureComponent {

return (
<div
className={classNames('statement-container', {
'is-focused': isFocused
})}
className={classNames('statement-container', { 'is-focused': isFocused })}
ref="container"
>
<div className="card statement">
Expand Down Expand Up @@ -75,6 +75,7 @@ export default class StatementContainer extends React.PureComponent {
<StatementForm
form={`StatementForm-${statement.id}`}
initialValues={statement.toJS()}
offset={this.props.offset}
isBundled
handleAbort={() => this.setState({ isEditing: false })}
handleConfirm={s =>
Expand All @@ -94,6 +95,7 @@ export default class StatementContainer extends React.PureComponent {
speaker={speaker}
handleEdit={() => this.setState({ isEditing: true })}
handleDelete={() => this.setState({ isDeleting: true })}
offset={this.props.offset}
/>
)
}
Expand Down
35 changes: 26 additions & 9 deletions app/components/Statements/StatementForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ export class StatementForm extends React.PureComponent {
constructor(props) {
super(props)
const lockedTime =
props.initialValues.time === undefined ? props.position : props.initialValues.time
props.initialValues.time === undefined
? props.position
: props.initialValues.time + props.offset

this.state = { lockedTime }
}

Expand All @@ -48,9 +51,11 @@ export class StatementForm extends React.PureComponent {
}

toggleLock() {
if (this.state.lockedTime === false)
if (this.state.lockedTime === false) {
this.setState({ lockedTime: this.props.position || 0 })
else this.setState({ lockedTime: false })
} else {
this.setState({ lockedTime: false })
}
}

moveTimeMarker(position) {
Expand All @@ -59,12 +64,23 @@ export class StatementForm extends React.PureComponent {
}

handleSubmit(statement) {
const currentTime =
this.state.lockedTime === false ? this.props.position : this.state.lockedTime
if (currentTime !== 0 && !currentTime)
statement.time = this.props.initialValues.time || 0
else statement.time = currentTime || 0
if (!statement.speaker_id) statement.speaker_id = null
const { position, initialValues, offset } = this.props
const { lockedTime } = this.state

// Get the best value for statement time and apply the reverse offset
// to use absolute timecode.
if (lockedTime !== false) {
statement.time = lockedTime - offset
} else if (position) {
statement.time = position - offset
} else {
statement.time = -offset
}

if (!statement.speaker_id) {
statement.speaker_id = null
}

this.props.handleConfirm(statement).then(
handleFormEffectResponse({
onSuccess: ({ id }) => this.props.setScrollTo({ id, __forceAutoScroll: true })
Expand All @@ -75,6 +91,7 @@ export class StatementForm extends React.PureComponent {
render() {
const {
position,
offset = 0,
handleSubmit,
valid,
speakers,
Expand Down
4 changes: 2 additions & 2 deletions app/components/Statements/StatementHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import ReputationGuardTooltip from '../Utils/ReputationGuardTooltip'
export default withNamespaces('videoDebate')(
({
t,
statement,
statementTime,
speaker,
handleTimeClick,
handleShowHistory,
Expand All @@ -21,7 +21,7 @@ export default withNamespaces('videoDebate')(
}) => (
<header className="card-header">
<p className="card-header-title">
<TimeDisplay time={statement.time} handleClick={handleTimeClick} />
<TimeDisplay time={statementTime} handleClick={handleTimeClick} />
{speaker && speaker.picture && (
<img className="speaker-mini" src={speaker.picture} alt="" />
)}
Expand Down
6 changes: 4 additions & 2 deletions app/components/Statements/StatementsList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import { FULLHD_WIDTH_THRESHOLD } from '../../constants'
@connect(
state => ({
statements: state.VideoDebate.statements.data,
statementFormSpeakerId: statementFormValueSelector(state, 'speaker_id')
statementFormSpeakerId: statementFormValueSelector(state, 'speaker_id'),
offset: state.VideoDebate.video.offset
}),
{ closeStatementForm, postStatement, setScrollTo }
)
Expand All @@ -34,11 +35,12 @@ export default class StatementsList extends React.PureComponent {
}

render() {
const { statementFormSpeakerId, statements } = this.props
const { statementFormSpeakerId, statements, offset } = this.props
return (
<div className="statements-list">
{statementFormSpeakerId !== undefined && (
<StatementForm
offset={offset}
initialValues={{ speaker_id: statementFormSpeakerId }}
enableReinitialize
keepDirtyOnReinitialize
Expand Down
44 changes: 44 additions & 0 deletions app/components/StyledUtils/Title.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import styled from 'styled-components'
import { fontSize, space, themeGet } from 'styled-system'

export const StyledH1 = styled.h1`
font-size: ${themeGet('fontSizes.0')};
margin-bottom: ${themeGet('space.4')};
${fontSize}
${space}
`

export const StyledH2 = styled.h2`
font-size: ${themeGet('fontSizes.1')};
margin-bottom: ${themeGet('space.4')};
${fontSize}
${space}
`

export const StyledH3 = styled.h3`
font-size: ${themeGet('fontSizes.2')};
margin-bottom: ${themeGet('space.3')};
${fontSize}
${space}
`

export const StyledH4 = styled.h4`
font-size: ${themeGet('fontSizes.3')};
margin-bottom: ${themeGet('space.2')};
${fontSize}
${space}
`

export const StyledH5 = styled.h5`
font-size: ${themeGet('fontSizes.4')};
margin-bottom: ${themeGet('space.2')};
${fontSize}
${space}
`

export const StyledH6 = styled.h6`
font-size: ${themeGet('fontSizes.5')};
margin-bottom: ${themeGet('space.1')};
${fontSize}
${space}
`
108 changes: 67 additions & 41 deletions app/components/Videos/EditVideoModal.jsx
Original file line number Diff line number Diff line change
@@ -1,58 +1,84 @@
import React from 'react'
import { connect } from 'react-redux'
import { withNamespaces } from 'react-i18next'
import { Field, reduxForm } from 'redux-form'
import { Formik } from 'formik'
import { Flex } from '@rebass/grid'

import { Edit } from 'styled-icons/fa-regular/Edit'

import { shiftStatements } from '../../state/video_debate/effects'
import Modal from '../Modal/Modal'
import { popModal } from '../../state/modals/reducer'
import { Icon } from '../Utils/Icon'
import { flashErrorMsg, flashSuccessMsg } from '../../state/flashes/reducer'
import FieldWithButton from '../FormUtils/FieldWithButton'
import { shiftStatements } from '../../state/video_debate/statements/effects'
import StyledLabel from '../FormUtils/StyledLabel'
import { StyledH3 } from '../StyledUtils/Title'

const TimeShiftForm = reduxForm({
form: 'shiftStatements',
initialValues: { offset: 0 }
})(
withNamespaces('main')(({ handleSubmit, t }) => (
<form onSubmit={handleSubmit}>
<Field
component={FieldWithButton}
name="offset"
type="number"
placeholder="+0s"
buttonLabel={t('actions.apply')}
validate={offset => !offset}
/>
</form>
))
)
class EditVideoModal extends React.PureComponent {
renderTitle() {
return (
<span>
<Edit size="1em" /> {this.props.t('video.edit')}
</span>
)
}

@connect(
null,
{ popModal, flashErrorMsg, flashSuccessMsg, shiftStatements }
)
@withNamespaces('videoDebate')
export default class EditVideoModal extends React.PureComponent {
render() {
const { t, popModal, video } = this.props

return (
<Modal
handleCloseClick={this.props.popModal}
title={
<span>
<Icon name="pencil" /> {this.props.t('video.edit')}
</span>
}
>
<h4 className="title is-4">{this.props.t('video.shiftStatements')}</h4>
<TimeShiftForm onSubmit={this.shiftSubmit} />
<Modal handleCloseClick={popModal} title={this.renderTitle()}>
<Flex flexDirection="column">
<StyledH3>{t('video.shiftStatements')}</StyledH3>
<Formik
initialValues={{ youtube_offset: video.youtube_offset }}
onSubmit={async (values, { setSubmitting }) => {
setSubmitting(true)
const reply = await this.props.shiftStatements(values)
setSubmitting(false)
this.props.popModal()
return reply
}}
>
{({ handleSubmit, isSubmitting, values, handleChange }) => (
<form onSubmit={handleSubmit}>
<Flex>
<StyledLabel fontSize={3} mr={3}>
Youtube
</StyledLabel>
<FieldWithButton
type="number"
input={{
name: 'youtube_offset',
max: '10000000',
value: values.youtube_offset,
onChange: handleChange
}}
meta={{ submitting: isSubmitting }}
placeholder="+0s"
buttonLabel={t('main:actions.apply')}
buttonClickHandler={handleSubmit}
/>
</Flex>
</form>
)}
</Formik>
</Flex>
</Modal>
)
}
}

shiftSubmit = ({ offset }) => {
if (offset) {
return this.props.shiftStatements(offset).then(() => this.props.popModal())
}
}
const mapDispatchToProps = {
popModal,
flashErrorMsg,
flashSuccessMsg,
shiftStatements
}

export default withNamespaces(['videoDebate', 'main'])(
connect(
state => ({ video: state.VideoDebate.video.data }),
mapDispatchToProps
)(EditVideoModal)
)
10 changes: 5 additions & 5 deletions app/components/Videos/VideoCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { videoURL } from '../../lib/cf_routes'
export class VideoCard extends React.PureComponent {
render() {
const { t, video } = this.props
const { hash_id, title, provider, provider_id } = video
const { hash_id, title } = video
const linkTarget = videoURL(hash_id)

return (
Expand All @@ -25,7 +25,7 @@ export class VideoCard extends React.PureComponent {
<RawIcon name="play-circle" />
</div>
<figure className="image is-16by9">
<img alt="" src={VideoCard.videoThumb(provider, provider_id)} />
<img alt="" src={VideoCard.videoThumb(video)} />
</figure>
</Link>
}
Expand Down Expand Up @@ -93,9 +93,9 @@ export class VideoCard extends React.PureComponent {
return <Link to={`/s/${speaker.slug || speaker.id}`}>{speaker.full_name}</Link>
}

static videoThumb(provider, provider_id) {
if (provider === 'youtube') {
return `https://img.youtube.com/vi/${provider_id}/mqdefault.jpg`
static videoThumb({youtube_id}) {
if (youtube_id) {
return `https://img.youtube.com/vi/${youtube_id}/mqdefault.jpg`
}
return ''
}
Expand Down
3 changes: 3 additions & 0 deletions app/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,6 @@ export const STATEMENT_LENGTH = [10, 255]
export const ALL_VIDEOS = 'ALL_VIDEOS'
export const ONLY_PARTNERS = 'ONLY_PARTNERS'
export const ONLY_COMMUNITY = 'ONLY_COMMUNITY'

/* ------ Third party providers ------ */
export const VIDEO_PLAYER_YOUTUBE = 'youtube'
Loading

0 comments on commit ca9605d

Please sign in to comment.