Progress
added composer query string functionality for url, text. fixed issue with no user in base_controller, cleaned up video component, albums starting
This commit is contained in:
parent
47d57960a4
commit
47cd60f851
|
@ -98,6 +98,7 @@ class Api::BaseController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def superapp?
|
def superapp?
|
||||||
|
return true if doorkeeper_token.nil?
|
||||||
doorkeeper_token && doorkeeper_token.application.superapp? || false
|
doorkeeper_token && doorkeeper_token.application.superapp? || false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import { NavLink } from 'react-router-dom'
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||||
import { CX } from '../constants'
|
import { CX } from '../constants'
|
||||||
|
@ -15,17 +16,30 @@ class Album extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { album } = this.props
|
const { album, isDummy } = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<div className={[_s.d, _s.minW162PX, _s.px5, _s.flex1].join(' ')}>
|
||||||
to={to}
|
{
|
||||||
href={href}
|
!isDummy &&
|
||||||
onClick={this.handleOnClick}
|
<NavLink
|
||||||
noClasses
|
className={[_s.d, _s.noUnderline].join(' ')}
|
||||||
|
to='/'
|
||||||
>
|
>
|
||||||
|
<div className={[_s.d, _s.w100PC, _s.mt5, _s.mb10].join(' ')}>
|
||||||
</Button>
|
<div className={[_s.d, _s.w100PC, _s.pt100PC].join(' ')}>
|
||||||
|
<div className={[_s.d, _s.posAbs, _s.top0, _s.w100PC, _s.right0, _s.bottom0, _s.left0].join(' ')}>
|
||||||
|
<div className={[_s.d, _s.w100PC, _s.h100PC, _s.radiusSmall, _s.bgTertiary, _s.border1PX, _s.borderColorSecondary].join(' ')} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={[_s.d, _s.w100PC, _s.pt7, _s.mb15].join(' ')}>
|
||||||
|
<Text weight='bold'>Profile Photos</Text>
|
||||||
|
<Text color='secondary' size='small' className={_s.mt5}>1 Item</Text>
|
||||||
|
</div>
|
||||||
|
</NavLink>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +48,7 @@ class Album extends React.PureComponent {
|
||||||
Album.propTypes = {
|
Album.propTypes = {
|
||||||
album: ImmutablePropTypes.map,
|
album: ImmutablePropTypes.map,
|
||||||
isAddable: PropTypes.bool,
|
isAddable: PropTypes.bool,
|
||||||
|
isDummy: PropTypes.bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Album
|
export default Album
|
|
@ -3,7 +3,10 @@ import PropTypes from 'prop-types'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import { length } from 'stringz'
|
import { length } from 'stringz'
|
||||||
import { openPopover } from '../../actions/popover'
|
import { openPopover } from '../../actions/popover'
|
||||||
import { MAX_POST_CHARACTER_COUNT } from '../../constants'
|
import {
|
||||||
|
CX,
|
||||||
|
MAX_POST_CHARACTER_COUNT,
|
||||||
|
} from '../../constants'
|
||||||
import Heading from '../heading'
|
import Heading from '../heading'
|
||||||
import Button from '../button'
|
import Button from '../button'
|
||||||
import BackButton from '../back_button'
|
import BackButton from '../back_button'
|
||||||
|
@ -23,15 +26,27 @@ class ComposeNavigationBar extends React.PureComponent {
|
||||||
isSubmitting,
|
isSubmitting,
|
||||||
anyMedia,
|
anyMedia,
|
||||||
text,
|
text,
|
||||||
|
isXS,
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
const disabledButton = isSubmitting || isUploading || isChangingUpload || length(text) > MAX_POST_CHARACTER_COUNT || (length(text.trim()) === 0 && !anyMedia)
|
const disabledButton = isSubmitting || isUploading || isChangingUpload || length(text) > MAX_POST_CHARACTER_COUNT || (length(text.trim()) === 0 && !anyMedia)
|
||||||
|
const innerClasses = CX({
|
||||||
|
d: 1,
|
||||||
|
flexRow: 1,
|
||||||
|
saveAreaInsetPT: 1,
|
||||||
|
saveAreaInsetPL: 1,
|
||||||
|
saveAreaInsetPR: 1,
|
||||||
|
w100PC: 1,
|
||||||
|
maxW640PX: !isXS,
|
||||||
|
mlAuto: !isXS,
|
||||||
|
mrAuto: !isXS,
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={[_s.d, _s.z4, _s.h53PX, _s.w100PC].join(' ')}>
|
<div className={[_s.d, _s.z4, _s.h53PX, _s.w100PC].join(' ')}>
|
||||||
<div className={[_s.d, _s.h53PX, _s.bgNavigation, _s.aiCenter, _s.z3, _s.top0, _s.right0, _s.left0, _s.posFixed].join(' ')} >
|
<div className={[_s.d, _s.h53PX, _s.bgNavigation, _s.aiCenter, _s.z3, _s.top0, _s.right0, _s.left0, _s.posFixed].join(' ')} >
|
||||||
|
|
||||||
<div className={[_s.d, _s.flexRow, _s.saveAreaInsetPT, _s.saveAreaInsetPL, _s.saveAreaInsetPR, _s.w100PC].join(' ')}>
|
<div className={innerClasses}>
|
||||||
|
|
||||||
<BackButton
|
<BackButton
|
||||||
toHome
|
toHome
|
||||||
|
@ -81,6 +96,7 @@ ComposeNavigationBar.propTypes = {
|
||||||
isSubmitting: PropTypes.bool,
|
isSubmitting: PropTypes.bool,
|
||||||
anyMedia: PropTypes.bool,
|
anyMedia: PropTypes.bool,
|
||||||
text: PropTypes.string,
|
text: PropTypes.string,
|
||||||
|
isXS: PropTypes.bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(ComposeNavigationBar)
|
export default connect(mapStateToProps, mapDispatchToProps)(ComposeNavigationBar)
|
|
@ -1,11 +1,26 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
import { POPOVER_PROFILE_OPTIONS } from '../../constants'
|
||||||
|
import { openPopover } from '../../actions/popover'
|
||||||
import BackButton from '../back_button'
|
import BackButton from '../back_button'
|
||||||
import Button from '../button'
|
import Button from '../button'
|
||||||
import Heading from '../heading'
|
import Heading from '../heading'
|
||||||
|
|
||||||
class ProfileNavigationBar extends React.PureComponent {
|
class ProfileNavigationBar extends React.PureComponent {
|
||||||
|
|
||||||
|
handleOpenMore = () => {
|
||||||
|
this.props.openProfileOptionsPopover({
|
||||||
|
account: this.props.account,
|
||||||
|
targetRef: this.openMoreNode,
|
||||||
|
position: 'bottom',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setOpenMoreNodeRef = (n) => {
|
||||||
|
this.openMoreNode = n
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { titleHTML } = this.props
|
const { titleHTML } = this.props
|
||||||
|
|
||||||
|
@ -51,9 +66,15 @@ class ProfileNavigationBar extends React.PureComponent {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
openProfileOptionsPopover(props) {
|
||||||
|
dispatch(openPopover(POPOVER_PROFILE_OPTIONS, props))
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
ProfileNavigationBar.propTypes = {
|
ProfileNavigationBar.propTypes = {
|
||||||
titleHTML: PropTypes.string,
|
titleHTML: PropTypes.string,
|
||||||
showBackBtn: PropTypes.bool,
|
showBackBtn: PropTypes.bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ProfileNavigationBar
|
export default connect(null, mapDispatchToProps)(ProfileNavigationBar)
|
|
@ -226,6 +226,7 @@ class ProfileHeader extends ImmutablePureComponent {
|
||||||
iconClassName={_s.inheritFill}
|
iconClassName={_s.inheritFill}
|
||||||
color='brand'
|
color='brand'
|
||||||
backgroundColor='none'
|
backgroundColor='none'
|
||||||
|
// : TODO :
|
||||||
className={[_s.jcCenter, _s.aiCenter, _s.mr10, _s.px10].join(' ')}
|
className={[_s.jcCenter, _s.aiCenter, _s.mr10, _s.px10].join(' ')}
|
||||||
onClick={this.handleOpenMore}
|
onClick={this.handleOpenMore}
|
||||||
buttonRef={this.setOpenMoreNodeRef}
|
buttonRef={this.setOpenMoreNodeRef}
|
||||||
|
@ -365,6 +366,18 @@ class ProfileHeader extends ImmutablePureComponent {
|
||||||
onClick={this.handleOpenMore}
|
onClick={this.handleOpenMore}
|
||||||
buttonRef={this.setOpenMoreNodeRef}
|
buttonRef={this.setOpenMoreNodeRef}
|
||||||
/>
|
/>
|
||||||
|
<Button
|
||||||
|
isOutline
|
||||||
|
icon='chat'
|
||||||
|
iconSize='18px'
|
||||||
|
iconClassName={_s.inheritFill}
|
||||||
|
color='brand'
|
||||||
|
backgroundColor='none'
|
||||||
|
// : TODO :
|
||||||
|
className={[_s.jcCenter, _s.aiCenter, _s.mr10, _s.px10].join(' ')}
|
||||||
|
onClick={this.handleOpenMore}
|
||||||
|
buttonRef={this.setOpenMoreNodeRef}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,21 +5,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||||
import { defineMessages, injectIntl } from 'react-intl'
|
import { defineMessages, injectIntl } from 'react-intl'
|
||||||
import { is } from 'immutable'
|
import { is } from 'immutable'
|
||||||
import throttle from 'lodash.throttle'
|
|
||||||
import { decode } from 'blurhash'
|
import { decode } from 'blurhash'
|
||||||
import videojs from 'video.js'
|
import videojs from 'video.js'
|
||||||
import { isFullscreen, requestFullscreen, exitFullscreen } from '../utils/fullscreen'
|
|
||||||
import { isPanoramic, isPortrait, minimumAspectRatio, maximumAspectRatio } from '../utils/media_aspect_ratio'
|
import { isPanoramic, isPortrait, minimumAspectRatio, maximumAspectRatio } from '../utils/media_aspect_ratio'
|
||||||
import {
|
|
||||||
openPopover,
|
|
||||||
} from '../actions/popover'
|
|
||||||
import { displayMedia } from '../initial_state'
|
import { displayMedia } from '../initial_state'
|
||||||
import {
|
|
||||||
CX,
|
|
||||||
POPOVER_VIDEO_STATS,
|
|
||||||
BREAKPOINT_EXTRA_SMALL,
|
|
||||||
} from '../constants'
|
|
||||||
import Responsive from '../features/ui/util/responsive_component'
|
|
||||||
import Button from './button'
|
import Button from './button'
|
||||||
import Icon from './icon'
|
import Icon from './icon'
|
||||||
import SensitiveMediaItem from './sensitive_media_item'
|
import SensitiveMediaItem from './sensitive_media_item'
|
||||||
|
@ -27,10 +16,6 @@ import Text from './text'
|
||||||
|
|
||||||
import '!style-loader!css-loader!video.js/dist/video-js.min.css'
|
import '!style-loader!css-loader!video.js/dist/video-js.min.css'
|
||||||
|
|
||||||
// check every 100 ms for buffer
|
|
||||||
const checkInterval = 100
|
|
||||||
const FIXED_VAR = 6
|
|
||||||
|
|
||||||
const videoJsOptions = {
|
const videoJsOptions = {
|
||||||
autoplay: false,
|
autoplay: false,
|
||||||
playbackRates: [0.5, 1, 1.5, 2],
|
playbackRates: [0.5, 1, 1.5, 2],
|
||||||
|
@ -38,130 +23,19 @@ const videoJsOptions = {
|
||||||
sources: [{}],
|
sources: [{}],
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatTime = (secondsNum) => {
|
|
||||||
if (isNaN(secondsNum)) secondsNum = 0
|
|
||||||
|
|
||||||
let hours = Math.floor(secondsNum / 3600)
|
|
||||||
let minutes = Math.floor((secondsNum - (hours * 3600)) / 60)
|
|
||||||
let seconds = Math.floor(secondsNum) - (hours * 3600) - (minutes * 60)
|
|
||||||
|
|
||||||
if (hours < 10) hours = '0' + hours
|
|
||||||
if (minutes < 10) minutes = '0' + minutes
|
|
||||||
if (seconds < 10) seconds = '0' + seconds
|
|
||||||
|
|
||||||
return (hours === '00' ? '' : `${hours}:`) + `${minutes}:${seconds}`
|
|
||||||
}
|
|
||||||
|
|
||||||
export const findElementPosition = (el) => {
|
|
||||||
let box
|
|
||||||
|
|
||||||
if (el.getBoundingClientRect && el.parentNode) {
|
|
||||||
box = el.getBoundingClientRect()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!box) {
|
|
||||||
return {
|
|
||||||
left: 0,
|
|
||||||
top: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const docEl = document.documentElement
|
|
||||||
const body = document.body
|
|
||||||
|
|
||||||
const clientLeft = docEl.clientLeft || body.clientLeft || 0
|
|
||||||
const scrollLeft = window.pageXOffset || body.scrollLeft
|
|
||||||
const left = (box.left + scrollLeft) - clientLeft
|
|
||||||
|
|
||||||
const clientTop = docEl.clientTop || body.clientTop || 0
|
|
||||||
const scrollTop = window.pageYOffset || body.scrollTop
|
|
||||||
const top = (box.top + scrollTop) - clientTop
|
|
||||||
|
|
||||||
return {
|
|
||||||
left: Math.round(left),
|
|
||||||
top: Math.round(top),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getPointerPosition = (el, event) => {
|
|
||||||
const position = {}
|
|
||||||
const box = findElementPosition(el)
|
|
||||||
const boxW = el.offsetWidth
|
|
||||||
const boxH = el.offsetHeight
|
|
||||||
const boxY = box.top
|
|
||||||
const boxX = box.left
|
|
||||||
|
|
||||||
let pageY = event.pageY
|
|
||||||
let pageX = event.pageX
|
|
||||||
|
|
||||||
if (event.changedTouches) {
|
|
||||||
pageX = event.changedTouches[0].pageX
|
|
||||||
pageY = event.changedTouches[0].pageY
|
|
||||||
}
|
|
||||||
|
|
||||||
position.y = Math.max(0, Math.min(1, (pageY - boxY) / boxH))
|
|
||||||
position.x = Math.max(0, Math.min(1, (pageX - boxX) / boxW))
|
|
||||||
|
|
||||||
return position
|
|
||||||
}
|
|
||||||
|
|
||||||
class Video extends ImmutablePureComponent {
|
class Video extends ImmutablePureComponent {
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
currentTime: 0,
|
|
||||||
duration: 0,
|
|
||||||
volume: 0.5,
|
|
||||||
paused: true,
|
|
||||||
dragging: false,
|
|
||||||
draggingVolume: false,
|
|
||||||
containerWidth: this.props.width,
|
containerWidth: this.props.width,
|
||||||
fullscreen: false,
|
|
||||||
hovered: false,
|
|
||||||
muted: false,
|
|
||||||
hoveringVolumeButton: false,
|
|
||||||
hoveringVolumeControl: false,
|
|
||||||
revealed: this.props.visible !== undefined ? this.props.visible : (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all'),
|
revealed: this.props.visible !== undefined ? this.props.visible : (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all'),
|
||||||
pipAvailable: true,
|
|
||||||
isBuffering: false,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bufferCheckInterval = null
|
|
||||||
lastPlayPos = 0
|
|
||||||
volHeight = 100
|
|
||||||
volOffset = 13
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { meta, blurhash } = this.props
|
|
||||||
|
|
||||||
document.addEventListener('fullscreenchange', this.handleFullscreenChange, true)
|
|
||||||
document.addEventListener('webkitfullscreenchange', this.handleFullscreenChange, true)
|
|
||||||
document.addEventListener('mozfullscreenchange', this.handleFullscreenChange, true)
|
|
||||||
document.addEventListener('MSFullscreenChange', this.handleFullscreenChange, true)
|
|
||||||
|
|
||||||
if (meta) {
|
|
||||||
this.setState({ duration: parseInt(meta.get('duration')) })
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('pictureInPictureEnabled' in document) {
|
|
||||||
this.setState({ pipAvailable: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
videoJsOptions.sources = [{ src: this.props.src }]
|
videoJsOptions.sources = [{ src: this.props.src }]
|
||||||
console.log("videoJsOptions:", videoJsOptions)
|
this.videoPlayer = videojs(this.video, videoJsOptions)
|
||||||
// instantiate video.js
|
|
||||||
this.videoPlayer = videojs(this.videoNode, videoJsOptions, function onPlayerReady() {
|
|
||||||
console.log('onPlayerReady', this)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
document.removeEventListener('fullscreenchange', this.handleFullscreenChange, true)
|
|
||||||
document.removeEventListener('webkitfullscreenchange', this.handleFullscreenChange, true)
|
|
||||||
document.removeEventListener('mozfullscreenchange', this.handleFullscreenChange, true)
|
|
||||||
document.removeEventListener('MSFullscreenChange', this.handleFullscreenChange, true)
|
|
||||||
|
|
||||||
clearInterval(this.bufferCheckInterval)
|
|
||||||
|
|
||||||
if (this.videoPlayer) {
|
if (this.videoPlayer) {
|
||||||
this.videoPlayer.dispose()
|
this.videoPlayer.dispose()
|
||||||
}
|
}
|
||||||
|
@ -179,34 +53,6 @@ class Video extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
checkBuffering = () => {
|
|
||||||
const { isBuffering, paused } = this.state
|
|
||||||
if (!this.video) {
|
|
||||||
this.handlePause()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const { currentTime } = this.video
|
|
||||||
|
|
||||||
// Checking offset should be at most the check interval but allow for some margin
|
|
||||||
let offset = (checkInterval - 30) / 1000
|
|
||||||
|
|
||||||
if (!isBuffering && currentTime < (this.lastPlayPos + offset) && !paused) {
|
|
||||||
// If no buffering is currently detected, and the position does not seem to increase
|
|
||||||
// and the player isn't manually paused...
|
|
||||||
this.setState({ isBuffering: true })
|
|
||||||
} else if (isBuffering && currentTime > (this.lastPlayPos + offset) && !paused) {
|
|
||||||
// If we were buffering but the player has advanced, then there is no buffering
|
|
||||||
this.setState({ isBuffering: false })
|
|
||||||
}
|
|
||||||
|
|
||||||
this.lastPlayPos = currentTime
|
|
||||||
}
|
|
||||||
|
|
||||||
volHandleOffset = (v) => {
|
|
||||||
const offset = v * this.volHeight + this.volOffset
|
|
||||||
return (offset > 110) ? 110 : offset
|
|
||||||
}
|
|
||||||
|
|
||||||
setPlayerRef = (n) => {
|
setPlayerRef = (n) => {
|
||||||
this.player = n
|
this.player = n
|
||||||
|
|
||||||
|
@ -220,197 +66,10 @@ class Video extends ImmutablePureComponent {
|
||||||
|
|
||||||
setVideoRef = (n) => {
|
setVideoRef = (n) => {
|
||||||
this.video = n
|
this.video = n
|
||||||
this.videoNode = n
|
|
||||||
|
|
||||||
if (this.video) {
|
|
||||||
const { volume, muted } = this.video
|
|
||||||
this.setState({
|
|
||||||
volume,
|
|
||||||
muted,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setSeekRef = (n) => {
|
|
||||||
this.seek = n
|
|
||||||
}
|
|
||||||
|
|
||||||
setVolumeRef = (n) => {
|
|
||||||
this.volume = n
|
|
||||||
}
|
|
||||||
|
|
||||||
setSettingsBtnRef = (n) => {
|
|
||||||
this.settingsBtn = n
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClickRoot = (e) => e.stopPropagation()
|
handleClickRoot = (e) => e.stopPropagation()
|
||||||
|
|
||||||
handlePlay = () => {
|
|
||||||
this.setState({ paused: false })
|
|
||||||
|
|
||||||
this.bufferCheckInterval = setInterval(this.checkBuffering, checkInterval)
|
|
||||||
}
|
|
||||||
|
|
||||||
handlePause = () => {
|
|
||||||
this.setState({
|
|
||||||
paused: true,
|
|
||||||
isBuffering: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
clearInterval(this.bufferCheckInterval)
|
|
||||||
}
|
|
||||||
|
|
||||||
handleTimeUpdate = () => {
|
|
||||||
const { currentTime, duration } = this.video
|
|
||||||
this.setState({
|
|
||||||
currentTime: currentTime.toFixed(FIXED_VAR),
|
|
||||||
duration: duration.toFixed(FIXED_VAR),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleVolumeMouseDown = (e) => {
|
|
||||||
document.addEventListener('mousemove', this.handleMouseVolSlide, true)
|
|
||||||
document.addEventListener('mouseup', this.handleVolumeMouseUp, true)
|
|
||||||
document.addEventListener('touchmove', this.handleMouseVolSlide, true)
|
|
||||||
document.addEventListener('touchend', this.handleVolumeMouseUp, true)
|
|
||||||
|
|
||||||
this.handleMouseVolSlide(e)
|
|
||||||
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
|
|
||||||
this.setState({ draggingVolume: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
handleVolumeMouseUp = () => {
|
|
||||||
this.handleMouseLeaveVolumeControl()
|
|
||||||
|
|
||||||
document.removeEventListener('mousemove', this.handleMouseVolSlide, true)
|
|
||||||
document.removeEventListener('mouseup', this.handleVolumeMouseUp, true)
|
|
||||||
document.removeEventListener('touchmove', this.handleMouseVolSlide, true)
|
|
||||||
document.removeEventListener('touchend', this.handleVolumeMouseUp, true)
|
|
||||||
|
|
||||||
this.setState({ draggingVolume: false })
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMouseVolSlide = throttle((e) => {
|
|
||||||
const rect = this.volume.getBoundingClientRect()
|
|
||||||
const y = 1 - ((e.clientY - rect.top) / this.volHeight)
|
|
||||||
|
|
||||||
if (!isNaN(y)) {
|
|
||||||
const slideamt = y
|
|
||||||
if (y > 1) {
|
|
||||||
slideamt = 1
|
|
||||||
} else if (y < 0) {
|
|
||||||
slideamt = 0
|
|
||||||
}
|
|
||||||
this.video.volume = slideamt
|
|
||||||
this.setState({ volume: slideamt })
|
|
||||||
}
|
|
||||||
}, 60)
|
|
||||||
|
|
||||||
handleMouseDown = (e) => {
|
|
||||||
document.addEventListener('mousemove', this.handleMouseMove, true)
|
|
||||||
document.addEventListener('mouseup', this.handleMouseUp, true)
|
|
||||||
document.addEventListener('touchmove', this.handleMouseMove, true)
|
|
||||||
document.addEventListener('touchend', this.handleMouseUp, true)
|
|
||||||
|
|
||||||
this.setState({ dragging: true })
|
|
||||||
this.video.pause()
|
|
||||||
this.handleMouseMove(e)
|
|
||||||
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMouseUp = () => {
|
|
||||||
document.removeEventListener('mousemove', this.handleMouseMove, true)
|
|
||||||
document.removeEventListener('mouseup', this.handleMouseUp, true)
|
|
||||||
document.removeEventListener('touchmove', this.handleMouseMove, true)
|
|
||||||
document.removeEventListener('touchend', this.handleMouseUp, true)
|
|
||||||
|
|
||||||
this.setState({ dragging: false })
|
|
||||||
this.video.play()
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMouseMove = throttle(e => {
|
|
||||||
const { x } = getPointerPosition(this.seek, e)
|
|
||||||
const currentTime = parseFloat(this.video.duration * x).toFixed(FIXED_VAR)
|
|
||||||
|
|
||||||
if (!isNaN(currentTime)) {
|
|
||||||
this.video.currentTime = currentTime
|
|
||||||
this.setState({ currentTime })
|
|
||||||
}
|
|
||||||
}, 60)
|
|
||||||
|
|
||||||
togglePlay = () => {
|
|
||||||
if (this.state.paused) {
|
|
||||||
this.video.play()
|
|
||||||
} else {
|
|
||||||
this.video.pause()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleFullscreen = () => {
|
|
||||||
if (isFullscreen()) {
|
|
||||||
exitFullscreen()
|
|
||||||
} else {
|
|
||||||
requestFullscreen(this.player)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
togglePip = () => {
|
|
||||||
try {
|
|
||||||
if (this.video !== document.pictureInPictureElement) {
|
|
||||||
if (this.state.paused) {
|
|
||||||
this.video.play()
|
|
||||||
}
|
|
||||||
setTimeout(() => { // : hack :
|
|
||||||
this.video.requestPictureInPicture()
|
|
||||||
}, 500)
|
|
||||||
} else {
|
|
||||||
document.exitPictureInPicture()
|
|
||||||
}
|
|
||||||
} catch(e) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleFullscreenChange = () => {
|
|
||||||
this.setState({ fullscreen: isFullscreen() })
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMouseEnter = () => {
|
|
||||||
this.setState({ hovered: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMouseLeave = () => {
|
|
||||||
this.setState({ hovered: false })
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMouseEnterAudio = () => {
|
|
||||||
this.setState({ hoveringVolumeButton: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMouseLeaveAudio = throttle(() => {
|
|
||||||
this.setState({ hoveringVolumeButton: false })
|
|
||||||
}, 2500)
|
|
||||||
|
|
||||||
handleMouseEnterVolumeControl = () => {
|
|
||||||
this.setState({ hoveringVolumeControl: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMouseLeaveVolumeControl = throttle(() => {
|
|
||||||
if (!this.state.draggingVolume) {
|
|
||||||
this.setState({ hoveringVolumeControl: false })
|
|
||||||
}
|
|
||||||
}, 2500)
|
|
||||||
|
|
||||||
toggleMute = () => {
|
|
||||||
this.video.muted = !this.video.muted
|
|
||||||
this.setState({ muted: this.video.muted })
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleReveal = () => {
|
toggleReveal = () => {
|
||||||
if (this.props.onToggleVisibility) {
|
if (this.props.onToggleVisibility) {
|
||||||
this.props.onToggleVisibility()
|
this.props.onToggleVisibility()
|
||||||
|
@ -419,36 +78,6 @@ class Video extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLoadedData = () => {
|
|
||||||
if (this.props.startTime) {
|
|
||||||
this.video.currentTime = this.props.startTime
|
|
||||||
this.video.play()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleProgress = () => {
|
|
||||||
const { buffered, duration } = this.video
|
|
||||||
|
|
||||||
if (!buffered) return
|
|
||||||
if (buffered.length > 0) {
|
|
||||||
this.setState({
|
|
||||||
buffer: buffered.end(0) / duration * 100,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleVolumeChange = () => {
|
|
||||||
const { volume, muted } = this.video
|
|
||||||
this.setState({
|
|
||||||
volume,
|
|
||||||
muted,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleOnClickSettings = () => {
|
|
||||||
this.props.onOpenVideoStatsPopover(this.settingsBtn, this.props.meta)
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
preview,
|
preview,
|
||||||
|
@ -464,26 +93,9 @@ class Video extends ImmutablePureComponent {
|
||||||
|
|
||||||
const {
|
const {
|
||||||
containerWidth,
|
containerWidth,
|
||||||
currentTime,
|
|
||||||
duration,
|
|
||||||
volume,
|
|
||||||
buffer,
|
|
||||||
dragging,
|
|
||||||
paused,
|
|
||||||
fullscreen,
|
|
||||||
hovered,
|
|
||||||
muted,
|
|
||||||
revealed,
|
revealed,
|
||||||
hoveringVolumeButton,
|
|
||||||
hoveringVolumeControl,
|
|
||||||
pipAvailable,
|
|
||||||
isBuffering,
|
|
||||||
} = this.state
|
} = this.state
|
||||||
|
|
||||||
const progress = (currentTime / duration) * 100
|
|
||||||
|
|
||||||
const volumeHeight = (muted) ? 0 : volume * this.volHeight
|
|
||||||
const volumeHandleLoc = (muted) ? this.volHandleOffset(0) : this.volHandleOffset(volume)
|
|
||||||
const playerStyle = {}
|
const playerStyle = {}
|
||||||
|
|
||||||
let { width, height } = this.props
|
let { width, height } = this.props
|
||||||
|
@ -505,7 +117,7 @@ class Video extends ImmutablePureComponent {
|
||||||
|
|
||||||
let preload
|
let preload
|
||||||
|
|
||||||
if (startTime || fullscreen || dragging) {
|
if (startTime) {
|
||||||
preload = 'auto'
|
preload = 'auto'
|
||||||
} else if (detailed) {
|
} else if (detailed) {
|
||||||
preload = 'metadata'
|
preload = 'metadata'
|
||||||
|
@ -513,87 +125,13 @@ class Video extends ImmutablePureComponent {
|
||||||
preload = 'none'
|
preload = 'none'
|
||||||
}
|
}
|
||||||
|
|
||||||
const mainContainerClasses = CX({
|
|
||||||
d: 1,
|
|
||||||
mt10: 1,
|
|
||||||
outlineNone: 1,
|
|
||||||
})
|
|
||||||
|
|
||||||
const seekHandleClasses = CX({
|
|
||||||
d: 1,
|
|
||||||
posAbs: 1,
|
|
||||||
circle: 1,
|
|
||||||
h20PX: 1,
|
|
||||||
w20PX: 1,
|
|
||||||
bgTransparent: 1,
|
|
||||||
mlNeg5PX: 1,
|
|
||||||
mr5: 1,
|
|
||||||
z3: 1,
|
|
||||||
aiCenter: 1,
|
|
||||||
jcCenter: 1,
|
|
||||||
videoEase: 1,
|
|
||||||
opacity0: !dragging,
|
|
||||||
opacity1: dragging || hovered,
|
|
||||||
})
|
|
||||||
|
|
||||||
const seekInnerHandleClasses = CX({
|
|
||||||
d: 1,
|
|
||||||
circle: 1,
|
|
||||||
h14PX: 1,
|
|
||||||
w14PX: 1,
|
|
||||||
bgBrand: 1,
|
|
||||||
boxShadow1: 1,
|
|
||||||
})
|
|
||||||
|
|
||||||
const progressClasses = CX({
|
|
||||||
d: 1,
|
|
||||||
radiusSmall: 1,
|
|
||||||
mt10: 1,
|
|
||||||
posAbs: 1,
|
|
||||||
h4PX: 1,
|
|
||||||
videoEase: 1,
|
|
||||||
})
|
|
||||||
|
|
||||||
const volumeControlClasses = CX({
|
|
||||||
d: 1,
|
|
||||||
posAbs: 1,
|
|
||||||
bgBlackOpaque: 1,
|
|
||||||
videoPlayerVolume: 1,
|
|
||||||
h122PX: 1,
|
|
||||||
circle: 1,
|
|
||||||
displayNone: !hoveringVolumeButton && !hoveringVolumeControl || !hovered,
|
|
||||||
})
|
|
||||||
|
|
||||||
const videoControlsBackgroundClasses = CX({
|
|
||||||
d: 1,
|
|
||||||
z2: 1,
|
|
||||||
px15: 1,
|
|
||||||
videoPlayerControlsBackground: 1,
|
|
||||||
posAbs: 1,
|
|
||||||
bottom0: 1,
|
|
||||||
right0: 1,
|
|
||||||
left0: 1,
|
|
||||||
displayNone: !hovered && !paused,
|
|
||||||
})
|
|
||||||
|
|
||||||
const overlayClasses = CX({
|
|
||||||
d: 1,
|
|
||||||
top50PC: 1,
|
|
||||||
left50PC: 1,
|
|
||||||
posAbs: 1,
|
|
||||||
z2: 1,
|
|
||||||
aiCenter: 1,
|
|
||||||
jcCenter: 1,
|
|
||||||
displayNone: !paused && !isBuffering,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!revealed && sensitive) {
|
if (!revealed && sensitive) {
|
||||||
return <SensitiveMediaItem onClick={this.toggleReveal} />
|
return <SensitiveMediaItem onClick={this.toggleReveal} />
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={mainContainerClasses}
|
className={[_s.d, _s.mt10, _s.outlineNone].join(' ')}
|
||||||
style={playerStyle}
|
style={playerStyle}
|
||||||
ref={this.setPlayerRef}
|
ref={this.setPlayerRef}
|
||||||
onMouseEnter={this.handleMouseEnter}
|
onMouseEnter={this.handleMouseEnter}
|
||||||
|
@ -601,35 +139,19 @@ class Video extends ImmutablePureComponent {
|
||||||
onClick={this.handleClickRoot}
|
onClick={this.handleClickRoot}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
|
|
||||||
<div className={overlayClasses} id='overlay'>
|
|
||||||
{
|
|
||||||
!paused && true &&
|
|
||||||
<Icon id='loading' size='60px' className={[_s.d, _s.posAbs].join(' ')} />
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div data-vjs-player>
|
<div data-vjs-player>
|
||||||
<video
|
<video
|
||||||
className={[_s.d, _s.h100PC, _s.w100PC, _s.outlineNone, 'video-js'].join(' ')}
|
className={[_s.d, _s.h100PC, _s.w100PC, _s.outlineNone, 'video-js'].join(' ')}
|
||||||
ref={this.setVideoRef}
|
ref={this.setVideoRef}
|
||||||
playsInline
|
playsInline
|
||||||
// poster={preview}
|
poster={preview}
|
||||||
// preload={preload}
|
preload={preload}
|
||||||
// role='button'
|
role='button'
|
||||||
// tabIndex='0'
|
tabIndex='0'
|
||||||
// aria-label={alt}
|
aria-label={alt}
|
||||||
// title={alt}
|
title={alt}
|
||||||
// width={width}
|
width={width}
|
||||||
// height={height}
|
height={height}
|
||||||
// volume={volume}
|
|
||||||
// onClick={this.togglePlay}
|
|
||||||
// onPlay={this.handlePlay}
|
|
||||||
// onPause={this.handlePause}
|
|
||||||
// onTimeUpdate={this.handleTimeUpdate}
|
|
||||||
// onLoadedData={this.handleLoadedData}
|
|
||||||
// onProgress={this.handleProgress}
|
|
||||||
// onVolumeChange={this.handleVolumeChange}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -651,30 +173,9 @@ class Video extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
play: { id: 'video.play', defaultMessage: 'Play' },
|
|
||||||
pause: { id: 'video.pause', defaultMessage: 'Pause' },
|
|
||||||
mute: { id: 'video.mute', defaultMessage: 'Mute sound' },
|
|
||||||
unmute: { id: 'video.unmute', defaultMessage: 'Unmute sound' },
|
|
||||||
hide: { id: 'video.hide', defaultMessage: 'Hide video' },
|
|
||||||
fullscreen: { id: 'video.fullscreen', defaultMessage: 'Full screen' },
|
|
||||||
exit_fullscreen: { id: 'video.exit_fullscreen', defaultMessage: 'Exit full screen' },
|
|
||||||
sensitive: { id: 'status.sensitive_warning', defaultMessage: 'Sensitive content' },
|
|
||||||
hidden: { id: 'status.media_hidden', defaultMessage: 'Media hidden' },
|
|
||||||
video_stats: { id: 'video.stats_label', defaultMessage: 'Video meta stats' },
|
|
||||||
toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Hide media' },
|
toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Hide media' },
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
onOpenVideoStatsPopover(targetRef, meta) {
|
|
||||||
dispatch(openPopover(POPOVER_VIDEO_STATS, {
|
|
||||||
targetRef,
|
|
||||||
meta,
|
|
||||||
position: 'top',
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
Video.propTypes = {
|
Video.propTypes = {
|
||||||
preview: PropTypes.string,
|
preview: PropTypes.string,
|
||||||
src: PropTypes.string.isRequired,
|
src: PropTypes.string.isRequired,
|
||||||
|
@ -692,7 +193,6 @@ Video.propTypes = {
|
||||||
blurhash: PropTypes.string,
|
blurhash: PropTypes.string,
|
||||||
aspectRatio: PropTypes.number,
|
aspectRatio: PropTypes.number,
|
||||||
meta: ImmutablePropTypes.map,
|
meta: ImmutablePropTypes.map,
|
||||||
onOpenVideoStatsPopover: PropTypes.func.isRequired,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default injectIntl(connect(null, mapDispatchToProps)(Video))
|
export default injectIntl(Video)
|
|
@ -7,109 +7,149 @@ import { injectIntl, defineMessages } from 'react-intl'
|
||||||
import { expandAccountMediaTimeline } from '../actions/timelines'
|
import { expandAccountMediaTimeline } from '../actions/timelines'
|
||||||
import { getAccountGallery } from '../selectors'
|
import { getAccountGallery } from '../selectors'
|
||||||
import ColumnIndicator from '../components/column_indicator'
|
import ColumnIndicator from '../components/column_indicator'
|
||||||
|
import Heading from '../components/heading'
|
||||||
|
import TabBar from '../components/tab_bar'
|
||||||
import MediaItem from '../components/media_item'
|
import MediaItem from '../components/media_item'
|
||||||
import LoadMore from '../components/load_more'
|
import LoadMore from '../components/load_more'
|
||||||
import Block from '../components/block'
|
import Block from '../components/block'
|
||||||
|
import Image from '../components/image'
|
||||||
|
import Album from '../components/album'
|
||||||
import MediaGalleryPlaceholder from '../components/placeholder/media_gallery_placeholder'
|
import MediaGalleryPlaceholder from '../components/placeholder/media_gallery_placeholder'
|
||||||
|
|
||||||
class AccountAlbums extends ImmutablePureComponent {
|
class AccountAlbums extends ImmutablePureComponent {
|
||||||
|
|
||||||
componentDidMount() {
|
// componentDidMount() {
|
||||||
const { accountId, mediaType } = this.props
|
// const { accountId, mediaType } = this.props
|
||||||
|
|
||||||
if (accountId && accountId !== -1) {
|
// if (accountId && accountId !== -1) {
|
||||||
this.props.dispatch(expandAccountMediaTimeline(accountId, { mediaType }))
|
// this.props.dispatch(expandAccountMediaTimeline(accountId, { mediaType }))
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
// componentWillReceiveProps(nextProps) {
|
||||||
if (
|
// if (
|
||||||
(nextProps.accountId && nextProps.accountId !== this.props.accountId) ||
|
// (nextProps.accountId && nextProps.accountId !== this.props.accountId) ||
|
||||||
(nextProps.accountId && nextProps.mediaType !== this.props.mediaType)
|
// (nextProps.accountId && nextProps.mediaType !== this.props.mediaType)
|
||||||
) {
|
// ) {
|
||||||
this.props.dispatch(expandAccountMediaTimeline(nextProps.accountId, {
|
// this.props.dispatch(expandAccountMediaTimeline(nextProps.accountId, {
|
||||||
mediaType: nextProps.mediaType,
|
// mediaType: nextProps.mediaType,
|
||||||
}))
|
// }))
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
handleScrollToBottom = () => {
|
// handleScrollToBottom = () => {
|
||||||
if (this.props.hasMore) {
|
// if (this.props.hasMore) {
|
||||||
this.handleLoadMore(this.props.attachments.size > 0 ? this.props.attachments.last().getIn(['status', 'id']) : undefined)
|
// this.handleLoadMore(this.props.attachments.size > 0 ? this.props.attachments.last().getIn(['status', 'id']) : undefined)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
handleScroll = (e) => {
|
// handleScroll = (e) => {
|
||||||
const { scrollTop, scrollHeight, clientHeight } = e.target
|
// const { scrollTop, scrollHeight, clientHeight } = e.target
|
||||||
const offset = scrollHeight - scrollTop - clientHeight
|
// const offset = scrollHeight - scrollTop - clientHeight
|
||||||
|
|
||||||
if (150 > offset && !this.props.isLoading) {
|
// if (150 > offset && !this.props.isLoading) {
|
||||||
this.handleScrollToBottom()
|
// this.handleScrollToBottom()
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
handleLoadMore = (maxId) => {
|
// handleLoadMore = (maxId) => {
|
||||||
if (this.props.accountId && this.props.accountId !== -1) {
|
// if (this.props.accountId && this.props.accountId !== -1) {
|
||||||
this.props.dispatch(expandAccountMediaTimeline(this.props.accountId, {
|
// this.props.dispatch(expandAccountMediaTimeline(this.props.accountId, {
|
||||||
maxId,
|
// maxId,
|
||||||
mediaType: this.props.mediaType,
|
// mediaType: this.props.mediaType,
|
||||||
}))
|
// }))
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
handleLoadOlder = (e) => {
|
// handleLoadOlder = (e) => {
|
||||||
e.preventDefault()
|
// e.preventDefault()
|
||||||
this.handleScrollToBottom()
|
// this.handleScrollToBottom()
|
||||||
}
|
// }
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
|
||||||
attachments,
|
|
||||||
isLoading,
|
|
||||||
hasMore,
|
|
||||||
intl,
|
|
||||||
account,
|
|
||||||
} = this.props
|
|
||||||
|
|
||||||
if (!account) return null
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Block>
|
<Block>
|
||||||
<div
|
<div className={[_s.d, _s.px10, _s.py10].join(' ')}>
|
||||||
role='feed'
|
<div className={[_s.d, _s.px5, _s.py5, _s.mb10].join(' ')}>
|
||||||
onScroll={this.handleScroll}
|
<Heading size='h2'>Photos</Heading>
|
||||||
className={[_s.d, _s.flexRow, _s.flexWrap, _s.py5, _s.px5].join(' ')}
|
|
||||||
>
|
|
||||||
|
|
||||||
{
|
|
||||||
attachments.map((attachment, i) => (
|
|
||||||
<MediaItem
|
|
||||||
key={attachment.get('id')}
|
|
||||||
attachment={attachment}
|
|
||||||
account={account}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
isLoading && attachments.size === 0 &&
|
|
||||||
<div className={[_s.d, _s.w100PC].join(' ')}>
|
|
||||||
<MediaGalleryPlaceholder />
|
|
||||||
</div>
|
</div>
|
||||||
}
|
<TabBar tabs={[
|
||||||
|
|
||||||
{
|
{
|
||||||
!isLoading && attachments.size === 0 &&
|
title: 'All Photos',
|
||||||
<ColumnIndicator type='error' message={intl.formatMessage(messages.none)} />
|
to: '/'
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
title: 'Albums',
|
||||||
|
isActive: true,
|
||||||
|
to: '/'
|
||||||
|
},
|
||||||
|
]}/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
<div className={[_s.d, _s.w100PC, _s.flexRow, _s.flexWrap, _s.px10, _s.mb15, _s.pb10].join(' ')}>
|
||||||
hasMore && !(isLoading && attachments.size === 0) &&
|
<Album />
|
||||||
<LoadMore visible={!isLoading} onClick={this.handleLoadOlder} />
|
<Album />
|
||||||
}
|
<Album />
|
||||||
|
<Album />
|
||||||
|
<Album />
|
||||||
|
<Album />
|
||||||
|
|
||||||
|
<Album isDummy />
|
||||||
|
<Album isDummy />
|
||||||
|
<Album isDummy />
|
||||||
|
<Album isDummy />
|
||||||
|
<Album isDummy />
|
||||||
|
<Album isDummy />
|
||||||
|
</div>
|
||||||
</Block>
|
</Block>
|
||||||
)
|
)
|
||||||
|
// const {
|
||||||
|
// attachments,
|
||||||
|
// isLoading,
|
||||||
|
// hasMore,
|
||||||
|
// intl,
|
||||||
|
// account,
|
||||||
|
// } = this.props
|
||||||
|
|
||||||
|
// if (!account) return null
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <Block>
|
||||||
|
// <div
|
||||||
|
// role='feed'
|
||||||
|
// onScroll={this.handleScroll}
|
||||||
|
// className={[_s.d, _s.flexRow, _s.flexWrap, _s.py5, _s.px5].join(' ')}
|
||||||
|
// >
|
||||||
|
|
||||||
|
// {
|
||||||
|
// attachments.map((attachment, i) => (
|
||||||
|
// <MediaItem
|
||||||
|
// key={attachment.get('id')}
|
||||||
|
// attachment={attachment}
|
||||||
|
// account={account}
|
||||||
|
// />
|
||||||
|
// ))
|
||||||
|
// }
|
||||||
|
|
||||||
|
// {
|
||||||
|
// isLoading && attachments.size === 0 &&
|
||||||
|
// <div className={[_s.d, _s.w100PC].join(' ')}>
|
||||||
|
// <MediaGalleryPlaceholder />
|
||||||
|
// </div>
|
||||||
|
// }
|
||||||
|
|
||||||
|
// {
|
||||||
|
// !isLoading && attachments.size === 0 &&
|
||||||
|
// <ColumnIndicator type='error' message={intl.formatMessage(messages.none)} />
|
||||||
|
// }
|
||||||
|
// </div>
|
||||||
|
|
||||||
|
// {
|
||||||
|
// hasMore && !(isLoading && attachments.size === 0) &&
|
||||||
|
// <LoadMore visible={!isLoading} onClick={this.handleLoadOlder} />
|
||||||
|
// }
|
||||||
|
// </Block>
|
||||||
|
// )
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,31 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import { clearCompose } from '../../actions/compose'
|
import { withRouter } from 'react-router-dom'
|
||||||
|
import queryString from 'query-string'
|
||||||
|
import { clearCompose, changeCompose } from '../../actions/compose'
|
||||||
import ComposeFormContainer from './containers/compose_form_container'
|
import ComposeFormContainer from './containers/compose_form_container'
|
||||||
|
|
||||||
class Compose extends React.PureComponent {
|
class Compose extends React.PureComponent {
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const search = this.context.router.route.location.search
|
||||||
|
try {
|
||||||
|
const qp = queryString.parse(search)
|
||||||
|
const url = `${qp.url || ''}`
|
||||||
|
const text = `${qp.text || ''}`
|
||||||
|
|
||||||
|
if (url.length > 0 || text.length > 0) {
|
||||||
|
let value = ""
|
||||||
|
if (text.length > 0) value += `${text} `
|
||||||
|
if (url.length > 0) value += url
|
||||||
|
this.props.dispatch(changeCompose(value))
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.props.dispatch(clearCompose())
|
this.props.dispatch(clearCompose())
|
||||||
}
|
}
|
||||||
|
@ -16,4 +36,8 @@ class Compose extends React.PureComponent {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect()(Compose)
|
Compose.contextTypes = {
|
||||||
|
router: PropTypes.object.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withRouter(connect()(Compose))
|
|
@ -2,6 +2,7 @@ export function About() { return import(/* webpackChunkName: "features/about/abo
|
||||||
export function AboutSidebar() { return import(/* webpackChunkName: "components/about_sidebar" */'../../../components/sidebar/about_sidebar') }
|
export function AboutSidebar() { return import(/* webpackChunkName: "components/about_sidebar" */'../../../components/sidebar/about_sidebar') }
|
||||||
export function AccountTimeline() { return import(/* webpackChunkName: "features/account_timeline" */'../../account_timeline') }
|
export function AccountTimeline() { return import(/* webpackChunkName: "features/account_timeline" */'../../account_timeline') }
|
||||||
export function AccountCommentsTimeline() { return import(/* webpackChunkName: "features/account_comments_timeline" */'../../account_comments_timeline') }
|
export function AccountCommentsTimeline() { return import(/* webpackChunkName: "features/account_comments_timeline" */'../../account_comments_timeline') }
|
||||||
|
export function AccountAlbums() { return import(/* webpackChunkName: "features/account_albums" */'../../account_albums') }
|
||||||
export function AccountGallery() { return import(/* webpackChunkName: "features/account_gallery" */'../../account_gallery') }
|
export function AccountGallery() { return import(/* webpackChunkName: "features/account_gallery" */'../../account_gallery') }
|
||||||
export function AlbumCreate() { return import(/* webpackChunkName: "features/album_create" */'../../album_create') }
|
export function AlbumCreate() { return import(/* webpackChunkName: "features/album_create" */'../../album_create') }
|
||||||
export function AlbumCreateModal() { return import(/* webpackChunkName: "components/album_create_modal" */'../../../components/modal/album_create_modal') }
|
export function AlbumCreateModal() { return import(/* webpackChunkName: "components/album_create_modal" */'../../../components/modal/album_create_modal') }
|
||||||
|
|
|
@ -1,28 +1,27 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { me } from '../initial_state'
|
import { me } from '../initial_state'
|
||||||
import DefaultSidebar from '../components/sidebar/default_sidebar'
|
import { CX } from '../constants'
|
||||||
import ComposeNavigationBar from '../components/navigation_bar/compose_navigation_bar_xs'
|
import ComposeNavigationBar from '../components/navigation_bar/compose_navigation_bar_xs'
|
||||||
import Responsive from '../features/ui/util/responsive_component'
|
|
||||||
import WrappedBundle from '../features/ui/util/wrapped_bundle'
|
|
||||||
import {
|
|
||||||
SidebarXS,
|
|
||||||
} from '../features/ui/util/async_components'
|
|
||||||
|
|
||||||
class ComposeLayout extends React.PureComponent {
|
class ComposeLayout extends React.PureComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { children, isXS } = this.props
|
const { children, isXS } = this.props
|
||||||
|
|
||||||
if (!isXS) return null
|
const mainClasses = CX({
|
||||||
|
d: 1,
|
||||||
|
w100PC: 1,
|
||||||
|
flexGrow1: 1,
|
||||||
|
borderRight1PX: !isXS,
|
||||||
|
borderLeft1PX: !isXS,
|
||||||
|
borderColorSecondary: !isXS,
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={[_s.d, _s.w100PC, _s.minH100VH, _s.bgTertiary].join(' ')}>
|
<div className={[_s.d, _s.w100PC, _s.maxW640PX, _s.mlAuto, _s.mrAuto, _s.minH100VH, _s.bgTertiary].join(' ')}>
|
||||||
<WrappedBundle component={SidebarXS} />
|
<ComposeNavigationBar isXS={isXS} />
|
||||||
|
<main role='main' className={mainClasses}>
|
||||||
<ComposeNavigationBar />
|
|
||||||
|
|
||||||
<main role='main' className={[_s.d, _s.w100PC, _s.flexGrow1].join(' ')}>
|
|
||||||
{ children }
|
{ children }
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -88,7 +88,7 @@ class ProfileLayout extends ImmutablePureComponent {
|
||||||
<Responsive max={BREAKPOINT_EXTRA_SMALL}>
|
<Responsive max={BREAKPOINT_EXTRA_SMALL}>
|
||||||
{
|
{
|
||||||
!!me &&
|
!!me &&
|
||||||
<ProfileNavigationBar titleHTML={titleHTML} />
|
<ProfileNavigationBar titleHTML={titleHTML} account={account} />
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
!me &&
|
!me &&
|
||||||
|
|
|
@ -35,7 +35,6 @@ class ComposePage extends React.PureComponent {
|
||||||
const { width } = this.state
|
const { width } = this.state
|
||||||
|
|
||||||
const isXS = width <= BREAKPOINT_EXTRA_SMALL
|
const isXS = width <= BREAKPOINT_EXTRA_SMALL
|
||||||
if (!isXS) throw 'This page does not exist'
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ComposeLayout title='Compose' isXS={isXS}>
|
<ComposeLayout title='Compose' isXS={isXS}>
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {
|
||||||
ALBUMS_FETCH_FAIL,
|
ALBUMS_FETCH_FAIL,
|
||||||
ALBUMS_CREATE_SUCCESS,
|
ALBUMS_CREATE_SUCCESS,
|
||||||
ALBUMS_REMOVE_REQUEST,
|
ALBUMS_REMOVE_REQUEST,
|
||||||
} from '../actions/bookmarks'
|
} from '../actions/albums'
|
||||||
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'
|
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'
|
||||||
|
|
||||||
const initialState = ImmutableMap({
|
const initialState = ImmutableMap({
|
||||||
|
@ -24,7 +24,7 @@ export default function albums(state = initialState, action) {
|
||||||
})
|
})
|
||||||
case ALBUMS_FETCH_SUCCESS:
|
case ALBUMS_FETCH_SUCCESS:
|
||||||
return state.withMutations((map) => {
|
return state.withMutations((map) => {
|
||||||
map.set('items', fromJS(action.bookmarkCollections))
|
map.set('items', fromJS(action.albums))
|
||||||
map.set('isLoading', false)
|
map.set('isLoading', false)
|
||||||
map.set('isFetched', true)
|
map.set('isFetched', true)
|
||||||
map.set('isError', false)
|
map.set('isError', false)
|
||||||
|
@ -36,10 +36,10 @@ export default function albums(state = initialState, action) {
|
||||||
map.set('isError', true)
|
map.set('isError', true)
|
||||||
})
|
})
|
||||||
case ALBUMS_CREATE_SUCCESS:
|
case ALBUMS_CREATE_SUCCESS:
|
||||||
return state.update('items', list => list.push(fromJS(action.bookmarkCollection)))
|
return state.update('items', list => list.push(fromJS(action.albums)))
|
||||||
case ALBUMS_REMOVE_REQUEST:
|
case ALBUMS_REMOVE_REQUEST:
|
||||||
return state.update('items', list => list.filterNot((item) => {
|
return state.update('items', list => list.filterNot((item) => {
|
||||||
return item.get('id') === action.bookmarkCollectionId
|
return item.get('id') === action.albumId
|
||||||
}))
|
}))
|
||||||
default:
|
default:
|
||||||
return state
|
return state
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
// APIs for normalizing fullscreen operations. Note that Edge uses
|
|
||||||
// the WebKit-prefixed APIs currently (as of Edge 16).
|
|
||||||
|
|
||||||
export const isFullscreen = () => document.fullscreenElement ||
|
|
||||||
document.webkitFullscreenElement ||
|
|
||||||
document.mozFullScreenElement;
|
|
||||||
|
|
||||||
export const exitFullscreen = () => {
|
|
||||||
if (document.exitFullscreen) {
|
|
||||||
document.exitFullscreen();
|
|
||||||
} else if (document.webkitExitFullscreen) {
|
|
||||||
document.webkitExitFullscreen();
|
|
||||||
} else if (document.mozCancelFullScreen) {
|
|
||||||
document.mozCancelFullScreen();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const requestFullscreen = el => {
|
|
||||||
if (el.requestFullscreen) {
|
|
||||||
el.requestFullscreen();
|
|
||||||
} else if (el.webkitRequestFullscreen) {
|
|
||||||
el.webkitRequestFullscreen();
|
|
||||||
} else if (el.mozRequestFullScreen) {
|
|
||||||
el.mozRequestFullScreen();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const attachFullscreenListener = (listener) => {
|
|
||||||
if ('onfullscreenchange' in document) {
|
|
||||||
document.addEventListener('fullscreenchange', listener);
|
|
||||||
} else if ('onwebkitfullscreenchange' in document) {
|
|
||||||
document.addEventListener('webkitfullscreenchange', listener);
|
|
||||||
} else if ('onmozfullscreenchange' in document) {
|
|
||||||
document.addEventListener('mozfullscreenchange', listener);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const detachFullscreenListener = (listener) => {
|
|
||||||
if ('onfullscreenchange' in document) {
|
|
||||||
document.removeEventListener('fullscreenchange', listener);
|
|
||||||
} else if ('onwebkitfullscreenchange' in document) {
|
|
||||||
document.removeEventListener('webkitfullscreenchange', listener);
|
|
||||||
} else if ('onmozfullscreenchange' in document) {
|
|
||||||
document.removeEventListener('mozfullscreenchange', listener);
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -600,6 +600,7 @@ pre {
|
||||||
.maxW212PX { max-width: 212px; }
|
.maxW212PX { max-width: 212px; }
|
||||||
|
|
||||||
.minW330PX { min-width: 330px; }
|
.minW330PX { min-width: 330px; }
|
||||||
|
.minW162PX { min-width: 162px; }
|
||||||
.minW120PX { min-width: 120px; }
|
.minW120PX { min-width: 120px; }
|
||||||
.minW84PX { min-width: 84px; }
|
.minW84PX { min-width: 84px; }
|
||||||
.minW76PX { min-width: 76px; }
|
.minW76PX { min-width: 76px; }
|
||||||
|
@ -849,6 +850,7 @@ pre {
|
||||||
.mtNeg50PX { margin-top: -50px; }
|
.mtNeg50PX { margin-top: -50px; }
|
||||||
.mtNeg75PX { margin-top: -75px; }
|
.mtNeg75PX { margin-top: -75px; }
|
||||||
|
|
||||||
|
.pt100PC { padding-top: 100%; }
|
||||||
.pt5625PC { padding-top: 56.25%; }
|
.pt5625PC { padding-top: 56.25%; }
|
||||||
.pt25PC { padding-top: 25%; }
|
.pt25PC { padding-top: 25%; }
|
||||||
.pt53PX { padding-top: 53px; }
|
.pt53PX { padding-top: 53px; }
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
# cover_id :bigint(8)
|
# cover_id :bigint(8)
|
||||||
|
# count :integer default(0), not null
|
||||||
#
|
#
|
||||||
|
|
||||||
class MediaAttachmentAlbum < ApplicationRecord
|
class MediaAttachmentAlbum < ApplicationRecord
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
# account_id :integer not null
|
# account_id :integer not null
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
# visibility :string
|
|
||||||
#
|
#
|
||||||
|
|
||||||
class StatusBookmarkCollection < ApplicationRecord
|
class StatusBookmarkCollection < ApplicationRecord
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
class AddCountToMediaAttachmentAlbums < ActiveRecord::Migration[5.2]
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
def change
|
||||||
|
safety_assured { add_column :media_attachment_albums, :count, :integer, null: false, default: 0 }
|
||||||
|
end
|
||||||
|
end
|
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 2020_12_17_003945) do
|
ActiveRecord::Schema.define(version: 2020_12_18_012018) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "pg_stat_statements"
|
enable_extension "pg_stat_statements"
|
||||||
|
@ -420,6 +420,7 @@ ActiveRecord::Schema.define(version: 2020_12_17_003945) do
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.bigint "cover_id"
|
t.bigint "cover_id"
|
||||||
|
t.integer "count", default: 0, null: false
|
||||||
t.index ["cover_id"], name: "index_media_attachment_albums_on_cover_id"
|
t.index ["cover_id"], name: "index_media_attachment_albums_on_cover_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue