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:
mgabdev 2020-12-17 23:46:37 -05:00
parent 47d57960a4
commit 47cd60f851
19 changed files with 269 additions and 676 deletions

View File

@ -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

View File

@ -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='/'
>
</Button> <div className={[_s.d, _s.w100PC, _s.mt5, _s.mb10].join(' ')}>
<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

View File

@ -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)

View File

@ -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)

View File

@ -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>
} }

View File

@ -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)

View File

@ -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(' ')} </div>
> <TabBar tabs={[
{
{ title: 'All Photos',
attachments.map((attachment, i) => ( to: '/'
<MediaItem },
key={attachment.get('id')} {
attachment={attachment} title: 'Albums',
account={account} isActive: true,
/> to: '/'
)) },
} ]}/>
{
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> </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>
// )
} }
} }

View File

@ -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))

View File

@ -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') }

View File

@ -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>

View File

@ -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 &&

View File

@ -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}>

View File

@ -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

View File

@ -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);
}
};

View File

@ -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; }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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