From f969eca07e2ac7b601694c463ef7097b787bbf65 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Tue, 27 Nov 2018 13:29:12 +0100 Subject: [Glitch] check_boxes label should use display: inline-block Port 180ae0472a3ff2c0c4a59733f78c87cb6e2004d7 to glitch-soc --- app/javascript/flavours/glitch/styles/forms.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/javascript/flavours') diff --git a/app/javascript/flavours/glitch/styles/forms.scss b/app/javascript/flavours/glitch/styles/forms.scss index 46ef85774..4f96204f2 100644 --- a/app/javascript/flavours/glitch/styles/forms.scss +++ b/app/javascript/flavours/glitch/styles/forms.scss @@ -266,7 +266,7 @@ code { font-family: inherit; font-size: 14px; color: $primary-text-color; - display: block; + display: inline-block; width: auto; position: relative; padding-top: 5px; -- cgit From 3b707bdc12cd6d727e712cbd8a0055fad0ea50e8 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Tue, 27 Nov 2018 15:21:57 +0100 Subject: [Glitch] Volume sliders for videos Port f978afa4871c22caecb625a0b3eba533edfa309b to glitch-soc --- .../flavours/glitch/features/video/index.js | 70 ++++++++++++++++++++-- .../flavours/glitch/styles/components/media.scss | 57 +++++++++++++++++- 2 files changed, 122 insertions(+), 5 deletions(-) (limited to 'app/javascript/flavours') diff --git a/app/javascript/flavours/glitch/features/video/index.js b/app/javascript/flavours/glitch/features/video/index.js index 4c2e5e62b..30592707c 100644 --- a/app/javascript/flavours/glitch/features/video/index.js +++ b/app/javascript/flavours/glitch/features/video/index.js @@ -108,6 +108,7 @@ export default class Video extends React.PureComponent { state = { currentTime: 0, duration: 0, + volume: 0.5, paused: true, dragging: false, containerWidth: false, @@ -117,6 +118,15 @@ export default class Video extends React.PureComponent { revealed: this.props.revealed === undefined ? (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all') : this.props.revealed, }; + // 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; + } + setPlayerRef = c => { this.player = c; @@ -135,6 +145,10 @@ export default class Video extends React.PureComponent { this.seek = c; } + setVolumeRef = c => { + this.volume = c; + } + handleMouseDownRoot = e => { e.preventDefault(); e.stopPropagation(); @@ -155,6 +169,43 @@ export default class Video extends React.PureComponent { }); } + 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)) { + var slideamt = x; + if(x > 1) { + slideamt = 1; + } else if(x < 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); @@ -290,10 +341,13 @@ export default class Video extends React.PureComponent { render () { const { preview, src, inline, startTime, onOpenVideo, onCloseVideo, intl, alt, letterbox, fullwidth, detailed, sensitive } = this.props; - const { containerWidth, currentTime, duration, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state; + const { containerWidth, currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state; const progress = (currentTime / duration) * 100; const playerStyle = {}; + const volumeWidth = (muted) ? 0 : volume * this.volWidth; + const volumeHandleLoc = (muted) ? this.volHandleOffset(0) : this.volHandleOffset(volume); + const computedClass = classNames('video-player', { inactive: !revealed, detailed, inline: inline && !fullscreen, fullscreen, letterbox, 'full-width': fullwidth }); let { width, height } = this.props; @@ -346,6 +400,7 @@ export default class Video extends React.PureComponent { title={alt} width={width} height={height} + volume={volume} onClick={this.togglePlay} onPlay={this.handlePlay} onPause={this.handlePause} @@ -374,9 +429,15 @@ export default class Video extends React.PureComponent {
- - - {!onCloseVideo && } + +
+
+ +
{(detailed || fullscreen) && @@ -388,6 +449,7 @@ export default class Video extends React.PureComponent {
+ {!onCloseVideo && } {(!fullscreen && onOpenVideo) && } {onCloseVideo && } diff --git a/app/javascript/flavours/glitch/styles/components/media.scss b/app/javascript/flavours/glitch/styles/components/media.scss index 40a144de4..e8011bde9 100644 --- a/app/javascript/flavours/glitch/styles/components/media.scss +++ b/app/javascript/flavours/glitch/styles/components/media.scss @@ -277,6 +277,19 @@ z-index: 100; } +.detailed, +.fullscreen { + .video-player__volume__current, + .video-player__volume::before { + bottom: 27px; + } + + .video-player__volume__handle { + bottom: 23px; + } + +} + .video-player { overflow: hidden; position: relative; @@ -432,7 +445,7 @@ &__time-current { color: $white; - margin-left: 10px; + margin-left: 60px; } &__time-sep { @@ -445,6 +458,48 @@ color: $white; } + &__volume { + cursor: pointer; + height: 24px; + display: inline; + + &::before { + content: ""; + width: 50px; + background: rgba($white, 0.35); + border-radius: 4px; + display: block; + position: absolute; + height: 4px; + left: 70px; + bottom: 20px; + } + + &__current { + display: block; + position: absolute; + height: 4px; + border-radius: 4px; + left: 70px; + bottom: 20px; + background: lighten($ui-highlight-color, 8%); + } + + &__handle { + position: absolute; + z-index: 3; + border-radius: 50%; + width: 12px; + height: 12px; + bottom: 16px; + left: 70px; + transition: opacity .1s ease; + background: lighten($ui-highlight-color, 8%); + box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2); + pointer-events: none; + } + } + &__seek { cursor: pointer; height: 24px; -- cgit From f17a61a91686c79ebacf3fad8bd934ac3a563dd0 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Sun, 4 Nov 2018 13:00:59 +0100 Subject: Add basic UI to set list replies setting in glitch-soc --- app/javascript/flavours/glitch/actions/lists.js | 4 ++-- .../glitch/features/list_timeline/index.js | 22 ++++++++++++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) (limited to 'app/javascript/flavours') diff --git a/app/javascript/flavours/glitch/actions/lists.js b/app/javascript/flavours/glitch/actions/lists.js index 3021fa5b5..7d94ee950 100644 --- a/app/javascript/flavours/glitch/actions/lists.js +++ b/app/javascript/flavours/glitch/actions/lists.js @@ -148,10 +148,10 @@ export const createListFail = error => ({ error, }); -export const updateList = (id, title, shouldReset) => (dispatch, getState) => { +export const updateList = (id, title, shouldReset, replies_policy) => (dispatch, getState) => { dispatch(updateListRequest(id)); - api(getState).put(`/api/v1/lists/${id}`, { title }).then(({ data }) => { + api(getState).put(`/api/v1/lists/${id}`, { title, replies_policy }).then(({ data }) => { dispatch(updateListSuccess(data)); if (shouldReset) { diff --git a/app/javascript/flavours/glitch/features/list_timeline/index.js b/app/javascript/flavours/glitch/features/list_timeline/index.js index 2e77ba235..edefc2006 100644 --- a/app/javascript/flavours/glitch/features/list_timeline/index.js +++ b/app/javascript/flavours/glitch/features/list_timeline/index.js @@ -9,7 +9,7 @@ import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/col import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; import { connectListStream } from 'flavours/glitch/actions/streaming'; import { expandListTimeline } from 'flavours/glitch/actions/timelines'; -import { fetchList, deleteList } from 'flavours/glitch/actions/lists'; +import { fetchList, deleteList, updateList } from 'flavours/glitch/actions/lists'; import { openModal } from 'flavours/glitch/actions/modal'; import MissingIndicator from 'flavours/glitch/components/missing_indicator'; import LoadingIndicator from 'flavours/glitch/components/loading_indicator'; @@ -17,6 +17,9 @@ import LoadingIndicator from 'flavours/glitch/components/loading_indicator'; const messages = defineMessages({ deleteMessage: { id: 'confirmations.delete_list.message', defaultMessage: 'Are you sure you want to permanently delete this list?' }, deleteConfirm: { id: 'confirmations.delete_list.confirm', defaultMessage: 'Delete' }, + all_replies: { id: 'lists.replies_policy.all_replies', defaultMessage: 'to any followed user' }, + no_replies: { id: 'lists.replies_policy.no_replies', defaultMessage: 'none' }, + list_replies: { id: 'lists.replies_policy.list_replies', defaultMessage: 'only to list' }, }); const mapStateToProps = (state, props) => ({ @@ -111,11 +114,19 @@ export default class ListTimeline extends React.PureComponent { })); } + handleRepliesPolicyClick = () => { + const { dispatch, list } = this.props; + const { id } = this.props.params; + const replies_policy = {'all_replies': 'no_replies', 'no_replies': 'list_replies', 'list_replies': 'all_replies'}[list.get('replies_policy')]; + this.props.dispatch(updateList(id, undefined, false, replies_policy)); + } + render () { - const { hasUnread, columnId, multiColumn, list } = this.props; + const { hasUnread, columnId, multiColumn, list, intl } = this.props; const { id } = this.props.params; const pinned = !!columnId; const title = list ? list.get('title') : id; + const replies_policy = list ? list.get('replies_policy') : undefined; if (typeof list === 'undefined') { return ( @@ -155,6 +166,13 @@ export default class ListTimeline extends React.PureComponent { + + { replies_policy !== undefined && ( + + ) + }

-- cgit From c18bb5d2453a74a2b3dcfc6cfb1d8e9a38a2f810 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Wed, 28 Nov 2018 16:57:26 +0100 Subject: Switch “cycling” reply policy link to set of radio inputs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #832 --- .../glitch/features/list_timeline/index.js | 36 ++++++++++++++-------- .../flavours/glitch/styles/components/index.scss | 22 +++++++++++++ 2 files changed, 45 insertions(+), 13 deletions(-) (limited to 'app/javascript/flavours') diff --git a/app/javascript/flavours/glitch/features/list_timeline/index.js b/app/javascript/flavours/glitch/features/list_timeline/index.js index edefc2006..ef829b937 100644 --- a/app/javascript/flavours/glitch/features/list_timeline/index.js +++ b/app/javascript/flavours/glitch/features/list_timeline/index.js @@ -17,9 +17,9 @@ import LoadingIndicator from 'flavours/glitch/components/loading_indicator'; const messages = defineMessages({ deleteMessage: { id: 'confirmations.delete_list.message', defaultMessage: 'Are you sure you want to permanently delete this list?' }, deleteConfirm: { id: 'confirmations.delete_list.confirm', defaultMessage: 'Delete' }, - all_replies: { id: 'lists.replies_policy.all_replies', defaultMessage: 'to any followed user' }, - no_replies: { id: 'lists.replies_policy.no_replies', defaultMessage: 'none' }, - list_replies: { id: 'lists.replies_policy.list_replies', defaultMessage: 'only to list' }, + all_replies: { id: 'lists.replies_policy.all_replies', defaultMessage: 'any followed user' }, + no_replies: { id: 'lists.replies_policy.no_replies', defaultMessage: 'no one' }, + list_replies: { id: 'lists.replies_policy.list_replies', defaultMessage: 'members of the list' }, }); const mapStateToProps = (state, props) => ({ @@ -114,11 +114,10 @@ export default class ListTimeline extends React.PureComponent { })); } - handleRepliesPolicyClick = () => { + handleRepliesPolicyChange = ({ target }) => { const { dispatch, list } = this.props; const { id } = this.props.params; - const replies_policy = {'all_replies': 'no_replies', 'no_replies': 'list_replies', 'list_replies': 'all_replies'}[list.get('replies_policy')]; - this.props.dispatch(updateList(id, undefined, false, replies_policy)); + this.props.dispatch(updateList(id, undefined, false, target.value)); } render () { @@ -166,15 +165,26 @@ export default class ListTimeline extends React.PureComponent { - - { replies_policy !== undefined && ( - - ) - }
+ { replies_policy !== undefined && ( +
+
+
+ + { ['no_replies', 'list_replies', 'all_replies'].map(policy => ( +
+ + +
+ ))} +
+
+
+ )} +
diff --git a/app/javascript/flavours/glitch/styles/components/index.scss b/app/javascript/flavours/glitch/styles/components/index.scss index b16b13d87..3e3ef6b52 100644 --- a/app/javascript/flavours/glitch/styles/components/index.scss +++ b/app/javascript/flavours/glitch/styles/components/index.scss @@ -1062,6 +1062,7 @@ } .setting-toggle__label, +.setting-radio__label, .setting-meta__label { color: $darker-text-color; display: inline-block; @@ -1070,6 +1071,27 @@ vertical-align: middle; } +.setting-radio { + display: block; + line-height: 18px; +} + +.setting-radio__label { + margin-bottom: 0; +} + +.column-settings__row legend { + color: $darker-text-color; + cursor: default; + display: block; + font-weight: 500; + margin-top: 10px; +} + +.setting-radio__input { + vertical-align: middle; +} + .setting-meta__label { float: right; } -- cgit From 39c8a71df80d303ee08d24b3ce3a66400a1dcc0b Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Wed, 28 Nov 2018 14:56:22 +0100 Subject: Do not crash the whole UI when loading an invalid column --- app/javascript/flavours/glitch/features/ui/components/bundle.js | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'app/javascript/flavours') diff --git a/app/javascript/flavours/glitch/features/ui/components/bundle.js b/app/javascript/flavours/glitch/features/ui/components/bundle.js index fc88e0c70..8f0d7b8b1 100644 --- a/app/javascript/flavours/glitch/features/ui/components/bundle.js +++ b/app/javascript/flavours/glitch/features/ui/components/bundle.js @@ -52,6 +52,11 @@ class Bundle extends React.Component { load = (props) => { const { fetchComponent, onFetch, onFetchSuccess, onFetchFail, renderDelay } = props || this.props; + if (fetchComponent === undefined) { + this.setState({ mod: null }); + return Promise.resolve(); + } + onFetch(); if (Bundle.cache[fetchComponent.name]) { -- cgit From 922d05864f4ba37a4026cd5f7e6e6ed5417a7404 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Wed, 28 Nov 2018 15:01:40 +0100 Subject: Add error boundary component to catch Web UI crashes --- .../flavours/glitch/components/error_boundary.js | 92 ++++++++++++++++++++++ .../flavours/glitch/containers/mastodon.js | 13 +-- .../glitch/styles/components/error_boundary.scss | 32 ++++++++ .../flavours/glitch/styles/components/index.scss | 1 + 4 files changed, 133 insertions(+), 5 deletions(-) create mode 100644 app/javascript/flavours/glitch/components/error_boundary.js create mode 100644 app/javascript/flavours/glitch/styles/components/error_boundary.scss (limited to 'app/javascript/flavours') diff --git a/app/javascript/flavours/glitch/components/error_boundary.js b/app/javascript/flavours/glitch/components/error_boundary.js new file mode 100644 index 000000000..fd37383f2 --- /dev/null +++ b/app/javascript/flavours/glitch/components/error_boundary.js @@ -0,0 +1,92 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { FormattedMessage } from 'react-intl'; + +export default class ErrorBoundary extends React.PureComponent { + + static propTypes = { + children: PropTypes.node, + }; + + state = { + hasError: false, + stackTrace: undefined, + componentStack: undefined, + } + + componentDidCatch(error, info) { + this.setState({ + hasError: true, + stackTrace: error.stack, + componentStack: info && info.componentStack, + }); + } + + handleReload(e) { + e.preventDefault(); + window.location.reload(); + } + + render() { + const { hasError, stackTrace, componentStack } = this.state; + + if (!hasError) return this.props.children; + + let debugInfo = ''; + if (stackTrace) { + debugInfo += 'Stack trace\n-----------\n\n```\n' + stackTrace.toString() + '\n```'; + } + if (componentStack) { + if (debugInfo) { + debugInfo += '\n\n\n'; + } + debugInfo += 'React component stack\n---------------------\n\n```\n' + componentStack.toString() + '\n```'; + } + + return ( +
+
+

+

+ +

    +
  • + }} + /> + { debugInfo !== '' && ( +
    + +