Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(StatementsList): add resume autoscroll button #822

Open
wants to merge 12 commits into
base: staging
Choose a base branch
from
Open
85 changes: 56 additions & 29 deletions app/components/Statements/StatementContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react'
import { connect } from 'react-redux'
import { withNamespaces } from 'react-i18next'
import classNames from 'classnames'
import IntersectionVisible from 'react-intersection-visible'

import { StatementForm } from './StatementForm'
import ModalConfirmDelete from '../Modal/ModalConfirmDelete'
Expand All @@ -20,7 +21,6 @@ import { withLoggedInUser } from '../LoggedInUser/UserProvider'
speaker: statementSelectors.getStatementSpeaker(state, props),
isFocused: statementSelectors.isStatementFocused(state, props),
scrollTo: state.VideoDebate.statements.scrollTo,
autoscrollEnabled: state.UserPreferences.enableAutoscroll,
formEnabled: state.VideoDebate.statements.formsCount > 0,
}),
{ updateStatement, deleteStatement }
Expand All @@ -42,39 +42,61 @@ export default class StatementContainer extends React.PureComponent {

render() {
const { isDeleting, replyTo } = this.state
const { statement, isFocused, speaker, isAuthenticated, loggedInUser, t } = this.props
const {
statement,
isFocused,
speaker,
isAuthenticated,
loggedInUser,
t,
autoscrollEnabled,
toggleAutoscroll,
} = this.props

return (
<div
className={classNames('statement-container', { 'is-focused': isFocused })}
ref="container"
<IntersectionVisible
onShow={() => {
if (isFocused && !autoscrollEnabled) {
toggleAutoscroll()
}
}}
onHide={() => {
if (isFocused && autoscrollEnabled) {
toggleAutoscroll()
}
}}
>
<div className="card statement">
{this.renderStatementOrEditForm(speaker, statement)}
<StatementComments
statement={statement}
speaker={speaker}
setReplyToComment={this.setReplyToComment}
/>
<CommentForm
statementID={statement.id}
replyTo={replyTo}
setReplyToComment={this.setReplyToComment}
user={isAuthenticated ? loggedInUser : null}
/>
{isDeleting && (
<ModalConfirmDelete
title={t('statement.remove')}
className="is-small"
isAbsolute
isRemove
message={t('statement.confirmRemove')}
handleAbort={() => this.setState({ isDeleting: false })}
handleConfirm={() => this.props.deleteStatement({ id: statement.id })}
<div
className={classNames('statement-container', { 'is-focused': isFocused })}
ref="container"
>
<div className="card statement">
{this.renderStatementOrEditForm(speaker, statement)}
<StatementComments
statement={statement}
speaker={speaker}
setReplyToComment={this.setReplyToComment}
/>
)}
<CommentForm
statementID={statement.id}
replyTo={replyTo}
setReplyToComment={this.setReplyToComment}
user={isAuthenticated ? loggedInUser : null}
/>
{isDeleting && (
<ModalConfirmDelete
title={t('statement.remove')}
className="is-small"
isAbsolute
isRemove
message={t('statement.confirmRemove')}
handleAbort={() => this.setState({ isDeleting: false })}
handleConfirm={() => this.props.deleteStatement({ id: statement.id })}
/>
)}
</div>
</div>
</div>
</IntersectionVisible>
)
}

Expand Down Expand Up @@ -116,6 +138,11 @@ export default class StatementContainer extends React.PureComponent {
return false
}

// Prevent unwanted autoscroll on autoscrollEnabled toggle
if (!props.scrollToFocusedStatement) {
return false
}

// Get previous state
const wasEnabled = prevProps.autoscrollEnabled
const wasTarget = this.isTarget(prevProps)
Expand Down
72 changes: 67 additions & 5 deletions app/components/Statements/StatementsList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { connect } from 'react-redux'
import { withNamespaces } from 'react-i18next'
import { withRouter } from 'react-router'
import FlipMove from 'react-flip-move'
import styled from 'styled-components'

import { StatementForm } from './StatementForm'
import { closeStatementForm, setScrollTo } from '../../state/video_debate/statements/reducer'
Expand All @@ -11,6 +12,33 @@ import { statementFormValueSelector } from '../../state/video_debate/statements/
import StatementContainer from './StatementContainer'
import { FULLHD_WIDTH_THRESHOLD } from '../../constants'

const ResumeAutoScrollButton = styled.button`
position: sticky;
z-index: 100;
bottom: 40px;
left: 50%;
transform: translateX(-50%);
background-color: #828282;
color: #ffffff;
border: 0;
padding: 12px 24px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
box-shadow: 1px 1px 2px #656565;
transition: background-color 0.1s ease-out;

&:hover {
background-color: #646464;
}

@media (max-width: 500px) {
width: 100%;
left: 0;
bottom: 5px;
transform: translateX(0);
}
`
@connect(
(state) => ({
speakers: state.VideoDebate.video.data.speakers,
Expand All @@ -23,6 +51,8 @@ import { FULLHD_WIDTH_THRESHOLD } from '../../constants'
@withNamespaces('videoDebate')
@withRouter
export default class StatementsList extends React.PureComponent {
state = { autoscrollEnabled: true, scrollToFocusedStatement: true }

componentDidMount() {
if (this.props.location.query.statement) {
this.props.setScrollTo({
Expand All @@ -43,9 +73,20 @@ export default class StatementsList extends React.PureComponent {
}

render() {
const { speakers, statementFormSpeakerId, statements, offset } = this.props
const {
speakers,
statementFormSpeakerId,
statements,
offset,
postStatement,
closeStatementForm,
t,
} = this.props
const { autoscrollEnabled, scrollToFocusedStatement } = this.state

const speakerId =
speakers.size === 1 && !statementFormSpeakerId ? speakers.get(0).id : statementFormSpeakerId

return (
<div className="statements-list">
{statementFormSpeakerId !== undefined && (
Expand All @@ -54,11 +95,11 @@ export default class StatementsList extends React.PureComponent {
initialValues={{ speaker_id: speakerId }}
enableReinitialize
keepDirtyOnReinitialize
handleAbort={() => this.props.closeStatementForm()}
handleAbort={() => closeStatementForm()}
handleConfirm={(s) =>
this.props.postStatement(s).then((e) => {
postStatement(s).then((e) => {
if (!e.error) {
this.props.closeStatementForm()
closeStatementForm()
}
return e
})
Expand All @@ -70,9 +111,30 @@ export default class StatementsList extends React.PureComponent {
disableAllAnimations={window.innerWidth < FULLHD_WIDTH_THRESHOLD}
>
{statements.map((statement) => (
<StatementContainer key={statement.id} statement={statement} />
<StatementContainer
key={statement.id}
statement={statement}
autoscrollEnabled={autoscrollEnabled}
scrollToFocusedStatement={scrollToFocusedStatement}
toggleAutoscroll={() => {
this.setState({
autoscrollEnabled: !autoscrollEnabled,
scrollToFocusedStatement: false,
})
}}
/>
))}
</FlipMove>
{!autoscrollEnabled && (
<ResumeAutoScrollButton
type="button"
onClick={() => {
this.setState({ autoscrollEnabled: true, scrollToFocusedStatement: true })
}}
>
{t('statement.autoscroll_resume')}
</ResumeAutoScrollButton>
)}
</div>
)
}
Expand Down
2 changes: 0 additions & 2 deletions app/components/VideoDebate/ActionBubbleMenu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ import { withLoggedInUser } from '../LoggedInUser/UserProvider'

@connect(
(state) => ({
hasAutoscroll: state.UserPreferences.enableAutoscroll,
soundOnBackgroundFocus: state.UserPreferences.enableSoundOnBackgroundFocus,
hasStatementForm: hasStatementForm(state),
}),
{
Expand Down
16 changes: 1 addition & 15 deletions app/components/VideoDebate/Actions/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { withRouter } from 'react-router'
import {
BellSlash,
Bell,
ArrowsAltV,
VolumeMute,
VolumeUp,
ShareAlt,
Expand All @@ -20,7 +19,7 @@ import { hasStatementForm } from '../../../state/video_debate/statements/selecto
import { destroyStatementForm } from '../../../state/video_debate/statements/effects'
import { changeSubscription } from '../../../state/video_debate/effects'
import { changeStatementFormSpeaker } from '../../../state/video_debate/statements/reducer'
import { toggleAutoscroll, toggleBackgroundSound } from '../../../state/user_preferences/reducer'
import { toggleBackgroundSound } from '../../../state/user_preferences/reducer'
import { addModal } from '../../../state/modals/reducer'
import { MIN_REPUTATION_UPDATE_VIDEO } from '../../../constants'
import { withLoggedInUser } from '../../LoggedInUser/UserProvider'
Expand Down Expand Up @@ -48,10 +47,7 @@ const Actions = ({
isAuthenticated,
isSubscribed,
changeSubscription,
hasAutoscroll,
toggleAutoscroll,
soundOnBackgroundFocus,
toggleBackgroundSound,
addModal,
router,
}) => (
Expand All @@ -65,14 +61,6 @@ const Actions = ({
/>
)}

<StyledAction
activated={hasAutoscroll}
onClick={() => toggleAutoscroll()}
activatedIcon={<ArrowsAltV />}
label={t('statement.autoscroll', {
context: hasAutoscroll ? 'disable' : 'enable',
})}
/>
<StyledAction
activated={soundOnBackgroundFocus}
onClick={() => toggleBackgroundSound()}
Expand Down Expand Up @@ -108,15 +96,13 @@ const Actions = ({
)

const mapStateToProps = (state) => ({
hasAutoscroll: state.UserPreferences.enableAutoscroll,
soundOnBackgroundFocus: state.UserPreferences.enableSoundOnBackgroundFocus,
isSubscribed: state.VideoDebate.video.isSubscribed,
hasStatementForm: hasStatementForm(state),
})

const mapDispatchToProps = {
changeStatementFormSpeaker,
toggleAutoscroll,
toggleBackgroundSound,
addModal,
destroyStatementForm,
Expand Down
1 change: 1 addition & 0 deletions app/components/VideoDebate/ResizableColumn.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const StyledResizable = styled(Resizable)`
@media screen and (max-width: 1279px) {
width: 100% !important;
max-width: 100% !important;
min-width: auto !important;
}

.right-resizable-handle {
Expand Down
1 change: 1 addition & 0 deletions app/i18n/en/videoDebate.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
"abortAdd": "Close Statement Form",
"autoscroll_enable": "Enable Autoscroll",
"autoscroll_disable": "Disable Autoscroll",
"autoscroll_resume": "Resume autoscroll",
"soundOnBackgroundFocus_enable": "Play a sound when the focus change in background",
"soundOnBackgroundFocus_disable": "Don't play a sound when the focus change in background",
"reverseTimeLock_lock": "Unlock time marker",
Expand Down
1 change: 1 addition & 0 deletions app/i18n/fr/videoDebate.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
"abortAdd": "Annuler l'ajout de citation",
"autoscroll_enable": "Activer le défilement automatique",
"autoscroll_disable": "Désactiver le défilement automatique",
"autoscroll_resume": "Reprendre le défilement automatique",
"soundOnBackgroundFocus_enable": "Jouer un son quand la citation change en arriere plan",
"soundOnBackgroundFocus_disable": "Ne pas jouer de son quand la citation change en arriere plan",
"reverseTimeLock_lock": "Déverrouiller la position",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ exports[`has correct defaults 1`] = `
Immutable.Record {
"sidebarExpended": true,
"locale": "en",
"enableAutoscroll": true,
"enableSoundOnBackgroundFocus": true,
"videosLanguageFilter": null,
"videosFilter": "ONLY_FEATURED",
Expand Down
5 changes: 1 addition & 4 deletions app/state/user_preferences/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,14 @@ export const changeVideosLanguageFilter = createAction(
'USER_PREFERENCES/CHANGE_VIDEOS_LANGUAGE_FILTER'
)
export const setVideosFilter = createAction('USER_PREFERENCES/SET_VIDEOS_FILTER')
export const toggleAutoscroll = createAction('STATEMENTS/TOGGLE_AUTOSCROLL')
export const toggleBackgroundSound = createAction('STATEMENTS/TOGGLE_BACKGROUND_SOUND')

const isMobile = window.innerWidth <= MOBILE_WIDTH_THRESHOLD

const Preferences = new Record({
// Disable autoscroll and sidebar expended by default on mobile
// Disable sidebar expended by default on mobile
sidebarExpended: !isMobile,
locale: browserLocale(),
enableAutoscroll: !isMobile,
enableSoundOnBackgroundFocus: true,
videosLanguageFilter: null,
videosFilter: ONLY_FEATURED,
Expand Down Expand Up @@ -58,7 +56,6 @@ const UserPreferencesReducer = handleActions(
[changeVideosLanguageFilter]: (state, { payload }) =>
updateState(state, 'videosLanguageFilter', payload),
[setVideosFilter]: (state, { payload }) => updateState(state, 'videosFilter', payload),
[toggleAutoscroll]: (state) => updateState(state, 'enableAutoscroll', !state.enableAutoscroll),
[toggleBackgroundSound]: (state) => {
return updateState(state, 'enableSoundOnBackgroundFocus', !state.enableSoundOnBackgroundFocus)
},
Expand Down
1 change: 0 additions & 1 deletion app/styles/_components/statements.sass
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ $statement-text-background: #31455d
$statement-text-color: #e0e7f1

.statements-list
overflow-y: auto
height: 100%
padding: 0 20px 20px 20px
+mobile
Expand Down
8 changes: 8 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
"react-helmet": "6.1.0",
"react-i18next": "~9.0.1",
"react-instantsearch-dom": "6.10.3",
"react-intersection-visible": "^2.1.0",
"react-markdown": "~4.3.1",
"react-player": "~1.11.1",
"react-redux": "~5.1.1",
Expand Down