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 && (
+
+
+
+
+
+ )}
+
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 !== '' && (
+
+
+
+
+ )}
+
+ -
+ }}
+ />
+
+ -
+ }}
+ />
+
+
+
+
+
+ );
+ }
+
+}
diff --git a/app/javascript/flavours/glitch/containers/mastodon.js b/app/javascript/flavours/glitch/containers/mastodon.js
index 4bd9cb75e..4fb6be476 100644
--- a/app/javascript/flavours/glitch/containers/mastodon.js
+++ b/app/javascript/flavours/glitch/containers/mastodon.js
@@ -12,6 +12,7 @@ import { connectUserStream } from 'flavours/glitch/actions/streaming';
import { IntlProvider, addLocaleData } from 'react-intl';
import { getLocale } from 'locales';
import initialState from 'flavours/glitch/util/initial_state';
+import ErrorBoundary from 'flavours/glitch/components/error_boundary';
const { localeData, messages } = getLocale();
addLocaleData(localeData);
@@ -61,11 +62,13 @@ export default class Mastodon extends React.PureComponent {
return (
-
-
-
-
-
+
+
+
+
+
+
+
);
diff --git a/app/javascript/flavours/glitch/styles/components/error_boundary.scss b/app/javascript/flavours/glitch/styles/components/error_boundary.scss
new file mode 100644
index 000000000..f9bf425f8
--- /dev/null
+++ b/app/javascript/flavours/glitch/styles/components/error_boundary.scss
@@ -0,0 +1,32 @@
+.error-boundary {
+ h1 {
+ font-size: 26px;
+ line-height: 36px;
+ font-weight: 400;
+ margin-bottom: 8px;
+ }
+
+ p {
+ color: $primary-text-color;
+ font-size: 15px;
+ line-height: 20px;
+
+ a {
+ color: $primary-text-color;
+ text-decoration: underline;
+ }
+
+ ul {
+ list-style: disc;
+ margin-left: 0;
+ padding-left: 1em;
+ }
+
+ textarea.web_app_crash-stacktrace {
+ width: 100%;
+ resize: none;
+ white-space: pre;
+ font-family: $font-monospace, monospace;
+ }
+ }
+}
diff --git a/app/javascript/flavours/glitch/styles/components/index.scss b/app/javascript/flavours/glitch/styles/components/index.scss
index 3e3ef6b52..873dfa98d 100644
--- a/app/javascript/flavours/glitch/styles/components/index.scss
+++ b/app/javascript/flavours/glitch/styles/components/index.scss
@@ -1263,3 +1263,4 @@ noscript {
@import 'lists';
@import 'emoji_picker';
@import 'local_settings';
+@import 'error_boundary';
--
cgit
From 7e63fb26e001da6573cdc8d059a2bd47efd62c88 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Wed, 28 Nov 2018 15:35:44 +0100
Subject: Do not hardcode preferences link, pleroma doesn't have it
---
.../flavours/glitch/components/error_boundary.js | 17 ++++++++++-------
1 file changed, 10 insertions(+), 7 deletions(-)
(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
index fd37383f2..142a0c21a 100644
--- a/app/javascript/flavours/glitch/components/error_boundary.js
+++ b/app/javascript/flavours/glitch/components/error_boundary.js
@@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
+import { preferencesLink } from 'flavours/glitch/util/backend_links';
export default class ErrorBoundary extends React.PureComponent {
@@ -75,13 +76,15 @@ export default class ErrorBoundary extends React.PureComponent {
values={{ reload: }}
/>
-
- }}
- />
-
+ { preferencesLink !== undefined && (
+
+ }}
+ />
+
+ )}
--
cgit
From 6a264c9379a50cb94afc8dd369bfc5b626a9c4d1 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Wed, 28 Nov 2018 20:20:03 +0100
Subject: Improve detailed status component lifecycle
- Move componentWillMount and componentWillReceiveProps logic to
getDerivedStateFromProps.
- Compute CW auto-unfold status earlier
---
.../flavours/glitch/features/status/index.js | 47 ++++++++++------------
1 file changed, 22 insertions(+), 25 deletions(-)
(limited to 'app/javascript/flavours')
diff --git a/app/javascript/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.js
index cfa1450f6..61de148e1 100644
--- a/app/javascript/flavours/glitch/features/status/index.js
+++ b/app/javascript/flavours/glitch/features/status/index.js
@@ -91,26 +91,26 @@ export default class Status extends ImmutablePureComponent {
fullscreen: false,
isExpanded: undefined,
threadExpanded: undefined,
+ statusId: undefined,
};
- componentWillMount () {
- this.props.dispatch(fetchStatus(this.props.params.statusId));
- }
-
componentDidMount () {
attachFullscreenListener(this.onFullScreenChange);
+ this.props.dispatch(fetchStatus(this.props.params.statusId));
}
- componentWillReceiveProps (nextProps) {
- if (this.state.isExpanded === undefined) {
- const isExpanded = autoUnfoldCW(nextProps.settings, nextProps.status);
- if (isExpanded !== undefined) this.setState({ isExpanded: isExpanded });
- }
- if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
- this._scrolledIntoView = false;
- this.props.dispatch(fetchStatus(nextProps.params.statusId));
- this.setState({ isExpanded: autoUnfoldCW(nextProps.settings, nextProps.status), threadExpanded: undefined });
+ static getDerivedStateFromProps(props, state) {
+ if (state.statusId === props.params.statusId || !props.params.statusId) {
+ return null;
}
+
+ props.dispatch(fetchStatus(props.params.statusId));
+
+ return {
+ threadExpanded: undefined,
+ isExpanded: autoUnfoldCW(props.settings, props.status),
+ statusId: props.params.statusId,
+ };
}
handleExpandedToggle = () => {
@@ -338,20 +338,17 @@ export default class Status extends ImmutablePureComponent {
this.node = c;
}
- componentDidUpdate () {
- if (this._scrolledIntoView) {
- return;
- }
+ componentDidUpdate (prevProps) {
+ if (this.props.params.statusId !== prevProps.params.statusId && this.props.params.statusId) {
+ const { status, ancestorsIds } = this.props;
- const { status, ancestorsIds } = this.props;
+ if (status && ancestorsIds && ancestorsIds.size > 0) {
+ const element = this.node.querySelectorAll('.focusable')[ancestorsIds.size - 1];
- if (status && ancestorsIds && ancestorsIds.size > 0) {
- const element = this.node.querySelectorAll('.focusable')[ancestorsIds.size - 1];
-
- window.requestAnimationFrame(() => {
- element.scrollIntoView(true);
- });
- this._scrolledIntoView = true;
+ window.requestAnimationFrame(() => {
+ element.scrollIntoView(true);
+ });
+ }
}
}
--
cgit
From 1624a95b2b612ff32408f9c991938a0949cd12b3 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Fri, 30 Nov 2018 12:17:56 +0100
Subject: [Glitch] Introduce flat layout to contexts reducer
Port 023fe5181b66ba2cbd20cca4e3bd34f132deba52 to glitch-soc
---
.../flavours/glitch/actions/timelines.js | 21 -----
.../flavours/glitch/features/status/index.js | 51 ++++++++++--
.../flavours/glitch/reducers/contexts.js | 94 ++++++++++++++--------
3 files changed, 103 insertions(+), 63 deletions(-)
(limited to 'app/javascript/flavours')
diff --git a/app/javascript/flavours/glitch/actions/timelines.js b/app/javascript/flavours/glitch/actions/timelines.js
index 27ca66e51..ffd259d5f 100644
--- a/app/javascript/flavours/glitch/actions/timelines.js
+++ b/app/javascript/flavours/glitch/actions/timelines.js
@@ -12,34 +12,13 @@ export const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP';
export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT';
-export const TIMELINE_CONTEXT_UPDATE = 'CONTEXT_UPDATE';
-
export function updateTimeline(timeline, status) {
return (dispatch, getState) => {
- const parents = [];
-
- if (status.in_reply_to_id) {
- let parent = getState().getIn(['statuses', status.in_reply_to_id]);
-
- while (parent && parent.get('in_reply_to_id')) {
- parents.push(parent.get('id'));
- parent = getState().getIn(['statuses', parent.get('in_reply_to_id')]);
- }
- }
-
dispatch({
type: TIMELINE_UPDATE,
timeline,
status,
});
-
- if (parents.length > 0) {
- dispatch({
- type: TIMELINE_CONTEXT_UPDATE,
- status,
- references: parents,
- });
- }
};
};
diff --git a/app/javascript/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.js
index 61de148e1..2ce6a9281 100644
--- a/app/javascript/flavours/glitch/features/status/index.js
+++ b/app/javascript/flavours/glitch/features/status/index.js
@@ -1,3 +1,4 @@
+import Immutable from 'immutable';
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
@@ -57,13 +58,49 @@ const messages = defineMessages({
const makeMapStateToProps = () => {
const getStatus = makeGetStatus();
- const mapStateToProps = (state, props) => ({
- status: getStatus(state, { id: props.params.statusId }),
- settings: state.get('local_settings'),
- ancestorsIds: state.getIn(['contexts', 'ancestors', props.params.statusId]),
- descendantsIds: state.getIn(['contexts', 'descendants', props.params.statusId]),
- askReplyConfirmation: state.getIn(['compose', 'text']).trim().length !== 0,
- });
+ const mapStateToProps = (state, props) => {
+ const status = getStatus(state, { id: props.params.statusId });
+ let ancestorsIds = Immutable.List();
+ let descendantsIds = Immutable.List();
+
+ if (status) {
+ ancestorsIds = ancestorsIds.withMutations(mutable => {
+ function addAncestor(id) {
+ if (id) {
+ const inReplyTo = state.getIn(['contexts', 'inReplyTos', id]);
+
+ mutable.unshift(id);
+ addAncestor(inReplyTo);
+ }
+ }
+
+ addAncestor(status.get('in_reply_to_id'));
+ });
+
+ descendantsIds = descendantsIds.withMutations(mutable => {
+ function addDescendantOf(id) {
+ const replies = state.getIn(['contexts', 'replies', id]);
+
+ if (replies) {
+ replies.forEach(reply => {
+ mutable.push(reply);
+ addDescendantOf(reply);
+ });
+ }
+ }
+
+ addDescendantOf(status.get('id'));
+ });
+ }
+
+ return {
+ status,
+ ancestorsIds,
+ descendantsIds,
+ settings: state.get('local_settings'),
+ askReplyConfirmation: state.getIn(['compose', 'text']).trim().length !== 0,
+ };
+ };
return mapStateToProps;
};
diff --git a/app/javascript/flavours/glitch/reducers/contexts.js b/app/javascript/flavours/glitch/reducers/contexts.js
index effd70756..71e78d94c 100644
--- a/app/javascript/flavours/glitch/reducers/contexts.js
+++ b/app/javascript/flavours/glitch/reducers/contexts.js
@@ -3,38 +3,62 @@ import {
ACCOUNT_MUTE_SUCCESS,
} from 'flavours/glitch/actions/accounts';
import { CONTEXT_FETCH_SUCCESS } from 'flavours/glitch/actions/statuses';
-import { TIMELINE_DELETE, TIMELINE_CONTEXT_UPDATE } from 'flavours/glitch/actions/timelines';
+import { TIMELINE_DELETE, TIMELINE_UPDATE } from 'flavours/glitch/actions/timelines';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
const initialState = ImmutableMap({
- ancestors: ImmutableMap(),
- descendants: ImmutableMap(),
+ inReplyTos: ImmutableMap(),
+ replies: ImmutableMap(),
});
-const normalizeContext = (state, id, ancestors, descendants) => {
- const ancestorsIds = ImmutableList(ancestors.map(ancestor => ancestor.id));
- const descendantsIds = ImmutableList(descendants.map(descendant => descendant.id));
+const normalizeContext = (immutableState, id, ancestors, descendants) => immutableState.withMutations(state => {
+ state.update('inReplyTos', immutableAncestors => immutableAncestors.withMutations(inReplyTos => {
+ state.update('replies', immutableDescendants => immutableDescendants.withMutations(replies => {
+ function addReply({ id, in_reply_to_id }) {
+ if (in_reply_to_id) {
+ const siblings = replies.get(in_reply_to_id, ImmutableList());
- return state.withMutations(map => {
- map.setIn(['ancestors', id], ancestorsIds);
- map.setIn(['descendants', id], descendantsIds);
- });
-};
+ if (!siblings.includes(id)) {
+ const index = siblings.findLastIndex(sibling => sibling.id < id);
+ replies.set(in_reply_to_id, siblings.insert(index + 1, id));
+ }
+
+ inReplyTos.set(id, in_reply_to_id);
+ }
+ }
+
+ if (ancestors[0]) {
+ addReply({ id, in_reply_to_id: ancestors[0].id });
+ }
+
+ if (descendants[0]) {
+ addReply({ id: descendants[0].id, in_reply_to_id: id });
+ }
+
+ [ancestors, descendants].forEach(statuses => statuses.forEach(addReply));
+ }));
+ }));
+});
const deleteFromContexts = (immutableState, ids) => immutableState.withMutations(state => {
- state.update('ancestors', immutableAncestors => immutableAncestors.withMutations(ancestors => {
- state.update('descendants', immutableDescendants => immutableDescendants.withMutations(descendants => {
+ state.update('inReplyTos', immutableAncestors => immutableAncestors.withMutations(inReplyTos => {
+ state.update('replies', immutableDescendants => immutableDescendants.withMutations(replies => {
ids.forEach(id => {
- descendants.get(id, ImmutableList()).forEach(descendantId => {
- ancestors.update(descendantId, ImmutableList(), list => list.filterNot(itemId => itemId === id));
- });
+ const inReplyToIdOfId = inReplyTos.get(id);
+ const repliesOfId = replies.get(id);
+ const siblings = replies.get(inReplyToIdOfId);
- ancestors.get(id, ImmutableList()).forEach(ancestorId => {
- descendants.update(ancestorId, ImmutableList(), list => list.filterNot(itemId => itemId === id));
- });
+ if (siblings) {
+ replies.set(inReplyToIdOfId, siblings.filterNot(sibling => sibling === id));
+ }
+
+
+ if (repliesOfId) {
+ repliesOfId.forEach(reply => inReplyTos.delete(reply));
+ }
- descendants.delete(id);
- ancestors.delete(id);
+ inReplyTos.delete(id);
+ replies.delete(id);
});
}));
}));
@@ -47,23 +71,23 @@ const filterContexts = (state, relationship, statuses) => {
return deleteFromContexts(state, ownedStatusIds);
};
-const updateContext = (state, status, references) => {
- return state.update('descendants', map => {
- references.forEach(parentId => {
- map = map.update(parentId, ImmutableList(), list => {
- if (list.includes(status.id)) {
- return list;
- }
+const updateContext = (state, status) => {
+ if (status.in_reply_to_id) {
+ return state.withMutations(mutable => {
+ const replies = mutable.getIn(['replies', status.in_reply_to_id], ImmutableList());
- return list.push(status.id);
- });
+ mutable.setIn(['inReplyTos', status.id], status.in_reply_to_id);
+
+ if (!replies.includes(status.id)) {
+ mutable.setIn(['replies', status.id], replies.push(status.id));
+ }
});
+ }
- return map;
- });
+ return state;
};
-export default function contexts(state = initialState, action) {
+export default function replies(state = initialState, action) {
switch(action.type) {
case ACCOUNT_BLOCK_SUCCESS:
case ACCOUNT_MUTE_SUCCESS:
@@ -72,8 +96,8 @@ export default function contexts(state = initialState, action) {
return normalizeContext(state, action.id, action.ancestors, action.descendants);
case TIMELINE_DELETE:
return deleteFromContexts(state, [action.id]);
- case TIMELINE_CONTEXT_UPDATE:
- return updateContext(state, action.status, action.references);
+ case TIMELINE_UPDATE:
+ return updateContext(state, action.status);
default:
return state;
}
--
cgit
From a28f5695f3d639fcbae1e73c85acd79ac088ca54 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Fri, 30 Nov 2018 12:23:07 +0100
Subject: [Glitch] Fix context building in the reducer
Port 7706ed038f1a16d232635d564a28e6f90e53e0fd to glitch-soc
---
.../flavours/glitch/reducers/contexts.js | 27 +++++++++++-----------
1 file changed, 14 insertions(+), 13 deletions(-)
(limited to 'app/javascript/flavours')
diff --git a/app/javascript/flavours/glitch/reducers/contexts.js b/app/javascript/flavours/glitch/reducers/contexts.js
index 71e78d94c..73b25fe3f 100644
--- a/app/javascript/flavours/glitch/reducers/contexts.js
+++ b/app/javascript/flavours/glitch/reducers/contexts.js
@@ -5,6 +5,7 @@ import {
import { CONTEXT_FETCH_SUCCESS } from 'flavours/glitch/actions/statuses';
import { TIMELINE_DELETE, TIMELINE_UPDATE } from 'flavours/glitch/actions/timelines';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
+import compareId from 'flavours/glitch/util/compare_id';
const initialState = ImmutableMap({
inReplyTos: ImmutableMap(),
@@ -15,27 +16,27 @@ const normalizeContext = (immutableState, id, ancestors, descendants) => immutab
state.update('inReplyTos', immutableAncestors => immutableAncestors.withMutations(inReplyTos => {
state.update('replies', immutableDescendants => immutableDescendants.withMutations(replies => {
function addReply({ id, in_reply_to_id }) {
- if (in_reply_to_id) {
- const siblings = replies.get(in_reply_to_id, ImmutableList());
+ if (in_reply_to_id && !inReplyTos.has(id)) {
- if (!siblings.includes(id)) {
- const index = siblings.findLastIndex(sibling => sibling.id < id);
- replies.set(in_reply_to_id, siblings.insert(index + 1, id));
- }
+ replies.update(in_reply_to_id, ImmutableList(), siblings => {
+ const index = siblings.findLastIndex(sibling => compareId(sibling, id) < 0);
+ return siblings.insert(index + 1, id);
+ });
inReplyTos.set(id, in_reply_to_id);
}
}
- if (ancestors[0]) {
- addReply({ id, in_reply_to_id: ancestors[0].id });
- }
+ // We know in_reply_to_id of statuses but `id` itself.
+ // So we assume that the status of the id replies to last ancestors.
- if (descendants[0]) {
- addReply({ id: descendants[0].id, in_reply_to_id: id });
+ ancestors.forEach(addReply);
+
+ if (ancestors[0]) {
+ addReply({ id, in_reply_to_id: ancestors[ancestors.length - 1].id });
}
- [ancestors, descendants].forEach(statuses => statuses.forEach(addReply));
+ descendants.forEach(addReply);
}));
}));
});
@@ -79,7 +80,7 @@ const updateContext = (state, status) => {
mutable.setIn(['inReplyTos', status.id], status.in_reply_to_id);
if (!replies.includes(status.id)) {
- mutable.setIn(['replies', status.id], replies.push(status.id));
+ mutable.setIn(['replies', status.in_reply_to_id], replies.push(status.id));
}
});
}
--
cgit
From 837ea32c88f75cac4a83f4449161e0d2d81a6cd2 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Fri, 30 Nov 2018 12:24:12 +0100
Subject: [Glitch] Replace recursion in status mapStateToProps
Port dfbadd68374ab5ddcaa75907261bd91da688fc6b to glitch-soc
---
.../flavours/glitch/features/status/index.js | 21 +++++++++------------
1 file changed, 9 insertions(+), 12 deletions(-)
(limited to 'app/javascript/flavours')
diff --git a/app/javascript/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.js
index 2ce6a9281..c1ff5513a 100644
--- a/app/javascript/flavours/glitch/features/status/index.js
+++ b/app/javascript/flavours/glitch/features/status/index.js
@@ -65,31 +65,28 @@ const makeMapStateToProps = () => {
if (status) {
ancestorsIds = ancestorsIds.withMutations(mutable => {
- function addAncestor(id) {
- if (id) {
- const inReplyTo = state.getIn(['contexts', 'inReplyTos', id]);
+ let id = status.get('in_reply_to_id');
- mutable.unshift(id);
- addAncestor(inReplyTo);
- }
+ while (id) {
+ mutable.unshift(id);
+ id = state.getIn(['contexts', 'inReplyTos', id]);
}
-
- addAncestor(status.get('in_reply_to_id'));
});
descendantsIds = descendantsIds.withMutations(mutable => {
- function addDescendantOf(id) {
+ const ids = [status.get('id')];
+
+ while (ids.length > 0) {
+ let id = ids.shift();
const replies = state.getIn(['contexts', 'replies', id]);
if (replies) {
replies.forEach(reply => {
mutable.push(reply);
- addDescendantOf(reply);
+ ids.unshift(reply);
});
}
}
-
- addDescendantOf(status.get('id'));
});
}
--
cgit
From 31599ad91cd918a8d0cdb8afd1389c206fbc2a1a Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Fri, 30 Nov 2018 12:24:58 +0100
Subject: [Glitch] Fix bug in order of conversations in web UI
Port e18390cfe6f5f0eb55abafe072b14b7fa123e808 to glitch-soc
---
app/javascript/flavours/glitch/features/status/index.js | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
(limited to 'app/javascript/flavours')
diff --git a/app/javascript/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.js
index c1ff5513a..a96c8a1b9 100644
--- a/app/javascript/flavours/glitch/features/status/index.js
+++ b/app/javascript/flavours/glitch/features/status/index.js
@@ -80,9 +80,12 @@ const makeMapStateToProps = () => {
let id = ids.shift();
const replies = state.getIn(['contexts', 'replies', id]);
+ if (status.get('id') !== id) {
+ mutable.push(id);
+ }
+
if (replies) {
- replies.forEach(reply => {
- mutable.push(reply);
+ replies.reverse().forEach(reply => {
ids.unshift(reply);
});
}
--
cgit
From a10fee3c09c268f0fd61a2f6ed03c6217934e383 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Fri, 30 Nov 2018 12:27:19 +0100
Subject: Scroll to detailed status when new ancestors get inserted
---
app/javascript/flavours/glitch/features/status/index.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'app/javascript/flavours')
diff --git a/app/javascript/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.js
index a96c8a1b9..018b52f1b 100644
--- a/app/javascript/flavours/glitch/features/status/index.js
+++ b/app/javascript/flavours/glitch/features/status/index.js
@@ -376,7 +376,7 @@ export default class Status extends ImmutablePureComponent {
}
componentDidUpdate (prevProps) {
- if (this.props.params.statusId !== prevProps.params.statusId && this.props.params.statusId) {
+ if (this.props.params.statusId && (this.props.params.statusId !== prevProps.params.statusId || prevProps.ancestorsIds.size < this.props.ancestorsIds.size)) {
const { status, ancestorsIds } = this.props;
if (status && ancestorsIds && ancestorsIds.size > 0) {
--
cgit
From 76320bf6d8ff09cfbea911a5d0f466aa160d313f Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Fri, 30 Nov 2018 17:14:01 +0100
Subject: Scroll to selected toot even if the ancestors happen to be loaded
---
app/javascript/flavours/glitch/features/status/index.js | 10 ++++++++++
1 file changed, 10 insertions(+)
(limited to 'app/javascript/flavours')
diff --git a/app/javascript/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.js
index 018b52f1b..d2d5a05c8 100644
--- a/app/javascript/flavours/glitch/features/status/index.js
+++ b/app/javascript/flavours/glitch/features/status/index.js
@@ -134,6 +134,16 @@ export default class Status extends ImmutablePureComponent {
componentDidMount () {
attachFullscreenListener(this.onFullScreenChange);
this.props.dispatch(fetchStatus(this.props.params.statusId));
+
+ const { status, ancestorsIds } = this.props;
+
+ if (status && ancestorsIds && ancestorsIds.size > 0) {
+ const element = this.node.querySelectorAll('.focusable')[ancestorsIds.size - 1];
+
+ window.requestAnimationFrame(() => {
+ element.scrollIntoView(true);
+ });
+ }
}
static getDerivedStateFromProps(props, state) {
--
cgit
From 4b85bf12ab7005a5a6b4472b8aeaf022ce042ad7 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sat, 1 Dec 2018 17:54:12 +0100
Subject: Fix since_id
---
app/javascript/flavours/glitch/actions/notifications.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'app/javascript/flavours')
diff --git a/app/javascript/flavours/glitch/actions/notifications.js b/app/javascript/flavours/glitch/actions/notifications.js
index ddf14f78f..0184d9c80 100644
--- a/app/javascript/flavours/glitch/actions/notifications.js
+++ b/app/javascript/flavours/glitch/actions/notifications.js
@@ -102,7 +102,7 @@ export function expandNotifications({ maxId } = {}, done = noOp) {
};
if (!maxId && notifications.get('items').size > 0) {
- params.since_id = notifications.getIn(['items', 0]);
+ params.since_id = notifications.getIn(['items', 0, 'id']);
}
dispatch(expandNotificationsRequest(isLoadingMore));
--
cgit