From 6ad870a410c7379eb8f3e719f3ad217354691546 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Thu, 27 Jun 2019 22:30:55 +0200 Subject: Change search components classes and styling to match upstream --- app/javascript/flavours/glitch/styles/components/single_column.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/javascript/flavours/glitch/styles/components/single_column.scss') diff --git a/app/javascript/flavours/glitch/styles/components/single_column.scss b/app/javascript/flavours/glitch/styles/components/single_column.scss index ca962abd2..e0f3d62a7 100644 --- a/app/javascript/flavours/glitch/styles/components/single_column.scss +++ b/app/javascript/flavours/glitch/styles/components/single_column.scss @@ -6,7 +6,7 @@ height: calc(100% - 10px); overflow-y: hidden; - .drawer--search input { + .search__input { line-height: 18px; font-size: 16px; padding: 15px; -- cgit From fabfa90e6cf7e20b3bb81213a2a388e1386c9c04 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Thu, 18 Jul 2019 14:32:45 +0200 Subject: Make width breakpoint for removing margins consistent and match upstream Fixes #1136 --- app/javascript/flavours/glitch/styles/components/columns.scss | 6 +++--- app/javascript/flavours/glitch/styles/components/drawer.scss | 2 +- app/javascript/flavours/glitch/styles/components/single_column.scss | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) (limited to 'app/javascript/flavours/glitch/styles/components/single_column.scss') diff --git a/app/javascript/flavours/glitch/styles/components/columns.scss b/app/javascript/flavours/glitch/styles/components/columns.scss index b354e7acf..7f3c21163 100644 --- a/app/javascript/flavours/glitch/styles/components/columns.scss +++ b/app/javascript/flavours/glitch/styles/components/columns.scss @@ -43,7 +43,7 @@ display: flex; flex-direction: column; - @media screen and (min-width: 360px) { + @media screen and (min-width: $no-gap-breakpoint) { padding: 0 10px; } } @@ -466,14 +466,14 @@ } .auto-columns.navbar-under { - @media screen and (max-width: 360px) { + @media screen and (max-width: $no-gap-breakpoint) { @include fix-margins-for-navbar-under; } } .auto-columns.navbar-under .react-swipeable-view-container .columns-area, .single-column.navbar-under .react-swipeable-view-container .columns-area { - @media screen and (max-width: 360px) { + @media screen and (max-width: $no-gap-breakpoint) { height: 100% !important; } } diff --git a/app/javascript/flavours/glitch/styles/components/drawer.scss b/app/javascript/flavours/glitch/styles/components/drawer.scss index 0994a9b43..93a3f62ed 100644 --- a/app/javascript/flavours/glitch/styles/components/drawer.scss +++ b/app/javascript/flavours/glitch/styles/components/drawer.scss @@ -78,7 +78,7 @@ margin-bottom: 10px; flex: none; - @include limited-single-column('screen and (max-width: 360px)') { margin-bottom: 0 } + @include limited-single-column('screen and (max-width: #{$no-gap-breakpoint})') { margin-bottom: 0 } @include single-column('screen and (max-width: 630px)') { font-size: 16px } } diff --git a/app/javascript/flavours/glitch/styles/components/single_column.scss b/app/javascript/flavours/glitch/styles/components/single_column.scss index e0f3d62a7..83c5d351b 100644 --- a/app/javascript/flavours/glitch/styles/components/single_column.scss +++ b/app/javascript/flavours/glitch/styles/components/single_column.scss @@ -98,7 +98,7 @@ top: 15px; } - @media screen and (min-width: 360px) { + @media screen and (min-width: $no-gap-breakpoint) { padding: 10px 0; } @@ -184,7 +184,7 @@ } } -@media screen and (min-width: 360px) { +@media screen and (min-width: $no-gap-breakpoint) { .tabs-bar { margin: 10px auto; margin-bottom: 0; -- cgit From 3aeaf9b897a7fff8ebb08055b06717e78e5cf914 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 23 Aug 2019 22:38:02 +0200 Subject: [Glitch] Add audio player Port 4190e31626907059aebf32b1be66715dacb989a9 to glitch-soc Signed-off-by: Thibaut Girka --- .../flavours/glitch/components/status.js | 30 ++- .../flavours/glitch/containers/media_container.js | 3 +- .../flavours/glitch/features/audio/index.js | 219 +++++++++++++++++++++ .../features/status/components/detailed_status.js | 18 +- .../features/ui/components/focal_point_modal.js | 14 +- .../flavours/glitch/features/video/index.js | 2 +- .../flavours/glitch/styles/components/media.scss | 35 ++++ .../glitch/styles/components/single_column.scss | 6 +- .../flavours/glitch/styles/components/status.scss | 9 +- .../flavours/glitch/util/async-components.js | 4 + 10 files changed, 325 insertions(+), 15 deletions(-) create mode 100644 app/javascript/flavours/glitch/features/audio/index.js (limited to 'app/javascript/flavours/glitch/styles/components/single_column.scss') diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js index 5b69ac4da..47e110128 100644 --- a/app/javascript/flavours/glitch/components/status.js +++ b/app/javascript/flavours/glitch/components/status.js @@ -10,7 +10,7 @@ import AttachmentList from './attachment_list'; import Card from '../features/status/components/card'; import { injectIntl, FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { MediaGallery, Video } from 'flavours/glitch/util/async-components'; +import { MediaGallery, Video, Audio } from 'flavours/glitch/util/async-components'; import { HotKeys } from 'react-hotkeys'; import NotificationOverlayContainer from 'flavours/glitch/features/notifications/containers/overlay_container'; import classNames from 'classnames'; @@ -443,11 +443,15 @@ class Status extends ImmutablePureComponent { } renderLoadingMediaGallery () { - return
; + return
; } renderLoadingVideoPlayer () { - return
; + return
; + } + + renderLoadingAudioPlayer () { + return
; } render () { @@ -561,7 +565,23 @@ class Status extends ImmutablePureComponent { media={status.get('media_attachments')} /> ); - } else if (['video', 'audio'].includes(attachments.getIn([0, 'type']))) { + } else if (attachments.getIn([0, 'type']) === 'audio') { + const attachment = status.getIn(['media_attachments', 0]); + + media = ( + + {Component => ( + + )} + + ); + mediaIcon = 'music'; + } else if (attachments.getIn([0, 'type']) === 'video') { const attachment = status.getIn(['media_attachments', 0]); media = ( @@ -584,7 +604,7 @@ class Status extends ImmutablePureComponent { />)} ); - mediaIcon = attachment.get('type') === 'video' ? 'video-camera' : 'music'; + mediaIcon = 'video-camera'; } else { // Media type is 'image' or 'gifv' media = ( diff --git a/app/javascript/flavours/glitch/containers/media_container.js b/app/javascript/flavours/glitch/containers/media_container.js index 1b480658f..c1738db4d 100644 --- a/app/javascript/flavours/glitch/containers/media_container.js +++ b/app/javascript/flavours/glitch/containers/media_container.js @@ -7,6 +7,7 @@ import MediaGallery from 'flavours/glitch/components/media_gallery'; import Video from 'flavours/glitch/features/video'; import Card from 'flavours/glitch/features/status/components/card'; import Poll from 'flavours/glitch/components/poll'; +import Audio from 'flavours/glitch/features/audio'; import ModalRoot from 'flavours/glitch/components/modal_root'; import MediaModal from 'flavours/glitch/features/ui/components/media_modal'; import { List as ImmutableList, fromJS } from 'immutable'; @@ -14,7 +15,7 @@ import { List as ImmutableList, fromJS } from 'immutable'; const { localeData, messages } = getLocale(); addLocaleData(localeData); -const MEDIA_COMPONENTS = { MediaGallery, Video, Card, Poll }; +const MEDIA_COMPONENTS = { MediaGallery, Video, Card, Poll, Audio }; export default class MediaContainer extends PureComponent { diff --git a/app/javascript/flavours/glitch/features/audio/index.js b/app/javascript/flavours/glitch/features/audio/index.js new file mode 100644 index 000000000..1daf56bc1 --- /dev/null +++ b/app/javascript/flavours/glitch/features/audio/index.js @@ -0,0 +1,219 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import WaveSurfer from 'wavesurfer.js'; +import { defineMessages, injectIntl } from 'react-intl'; +import { formatTime } from 'flavours/glitch/features/video'; +import Icon from 'flavours/glitch/components/icon'; +import classNames from 'classnames'; +import { throttle } from 'lodash'; + +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' }, +}); + +const arrayOf = (length, fill) => (new Array(length)).fill(fill); + +export default @injectIntl +class Audio extends React.PureComponent { + + static propTypes = { + src: PropTypes.string.isRequired, + alt: PropTypes.string, + duration: PropTypes.number, + height: PropTypes.number, + preload: PropTypes.bool, + editable: PropTypes.bool, + intl: PropTypes.object.isRequired, + }; + + state = { + currentTime: 0, + duration: null, + paused: true, + muted: false, + volume: 0.5, + }; + + // hard coded in components.scss + // any way to get ::before values programatically? + + volWidth = 50; + + volOffset = 70; + + volHandleOffset = v => { + const offset = v * this.volWidth + this.volOffset; + return (offset > 110) ? 110 : offset; + } + + setVolumeRef = c => { + this.volume = c; + } + + setWaveformRef = c => { + this.waveform = c; + } + + componentDidMount () { + if (this.waveform) { + this._updateWaveform(); + } + } + + componentDidUpdate (prevProps) { + if (this.waveform && prevProps.src !== this.props.src) { + this._updateWaveform(); + } + } + + componentWillUnmount () { + if (this.wavesurfer) { + this.wavesurfer.destroy(); + this.wavesurfer = null; + } + } + + _updateWaveform () { + const { src, height, duration, preload } = this.props; + + const progressColor = window.getComputedStyle(document.querySelector('.audio-player__progress-placeholder')).getPropertyValue('background-color'); + const waveColor = window.getComputedStyle(document.querySelector('.audio-player__wave-placeholder')).getPropertyValue('background-color'); + + if (this.wavesurfer) { + this.wavesurfer.destroy(); + } + + const wavesurfer = WaveSurfer.create({ + container: this.waveform, + height, + barWidth: 3, + cursorWidth: 0, + progressColor, + waveColor, + forceDecode: true, + }); + + wavesurfer.setVolume(this.state.volume); + + if (preload) { + wavesurfer.load(src); + } else { + wavesurfer.load(src, arrayOf(1, 0.5), null, duration); + } + + wavesurfer.on('ready', () => this.setState({ duration: Math.floor(wavesurfer.getDuration()) })); + wavesurfer.on('audioprocess', () => this.setState({ currentTime: Math.floor(wavesurfer.getCurrentTime()) })); + wavesurfer.on('pause', () => this.setState({ paused: true })); + wavesurfer.on('play', () => this.setState({ paused: false })); + wavesurfer.on('volume', volume => this.setState({ volume })); + wavesurfer.on('mute', muted => this.setState({ muted })); + + this.wavesurfer = wavesurfer; + } + + togglePlay = () => { + if (this.state.paused) { + if (!this.props.preload) { + this.wavesurfer.createBackend(); + this.wavesurfer.createPeakCache(); + this.wavesurfer.load(this.props.src); + } + + this.wavesurfer.play(); + } else { + this.wavesurfer.pause(); + } + } + + toggleMute = () => { + this.wavesurfer.setMute(!this.state.muted); + } + + 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(); + } + + handleVolumeMouseUp = () => { + document.removeEventListener('mousemove', this.handleMouseVolSlide, true); + document.removeEventListener('mouseup', this.handleVolumeMouseUp, true); + document.removeEventListener('touchmove', this.handleMouseVolSlide, true); + document.removeEventListener('touchend', this.handleVolumeMouseUp, true); + } + + handleMouseVolSlide = throttle(e => { + const rect = this.volume.getBoundingClientRect(); + const x = (e.clientX - rect.left) / this.volWidth; // x position within the element. + + if(!isNaN(x)) { + let slideamt = x; + + if (x > 1) { + slideamt = 1; + } else if(x < 0) { + slideamt = 0; + } + + this.wavesurfer.setVolume(slideamt); + } + }, 60); + + render () { + const { height, intl, alt, editable } = this.props; + const { paused, muted, volume, currentTime } = this.state; + + const volumeWidth = muted ? 0 : volume * this.volWidth; + const volumeHandleLoc = muted ? this.volHandleOffset(0) : this.volHandleOffset(volume); + + return ( +
+
+
+ +
+ +
+
+
+ + + +
+
+ + +
+ + + {formatTime(currentTime)} + / + {formatTime(this.state.duration || Math.floor(this.props.duration))} + +
+
+
+
+ ); + } + +} diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.js b/app/javascript/flavours/glitch/features/status/components/detailed_status.js index 873ea35fb..b676410d6 100644 --- a/app/javascript/flavours/glitch/features/status/components/detailed_status.js +++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.js @@ -11,6 +11,7 @@ import { FormattedDate, FormattedNumber } from 'react-intl'; import Card from './card'; import ImmutablePureComponent from 'react-immutable-pure-component'; import Video from 'flavours/glitch/features/video'; +import Audio from 'flavours/glitch/features/audio'; import VisibilityIcon from 'flavours/glitch/components/status_visibility_icon'; import scheduleIdleTask from 'flavours/glitch/util/schedule_idle_task'; import classNames from 'classnames'; @@ -131,7 +132,20 @@ export default class DetailedStatus extends ImmutablePureComponent { } else if (status.get('media_attachments').size > 0) { if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) { media = ; - } else if (['video', 'audio'].includes(status.getIn(['media_attachments', 0, 'type']))) { + } else if (status.getIn(['media_attachments', 0, 'type']) === 'audio') { + const attachment = status.getIn(['media_attachments', 0]); + + media = ( +