Skip to content
This repository was archived by the owner on Dec 21, 2023. It is now read-only.

Commit cf845fe

Browse files
akihikodakiGargron
authored andcommitted
Hide some components rather than unmounting (mastodon#2271)
Hide some components rather than unmounting them to allow to show again quickly and keep the view state such as the scrolled offset.
1 parent 72c984e commit cf845fe

File tree

13 files changed

+167
-53
lines changed

13 files changed

+167
-53
lines changed

app/assets/javascripts/components/components/status_list.jsx

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class StatusList extends React.PureComponent {
6060
}
6161

6262
render () {
63-
const { statusIds, onScrollToBottom, trackScroll, isLoading, isUnread, hasMore, prepend, emptyMessage } = this.props;
63+
const { statusIds, onScrollToBottom, scrollKey, shouldUpdateScroll, isLoading, isUnread, hasMore, prepend, emptyMessage } = this.props;
6464

6565
let loadMore = '';
6666
let scrollableArea = '';
@@ -98,25 +98,22 @@ class StatusList extends React.PureComponent {
9898
);
9999
}
100100

101-
if (trackScroll) {
102-
return (
103-
<ScrollContainer scrollKey='status-list'>
104-
{scrollableArea}
105-
</ScrollContainer>
106-
);
107-
} else {
108-
return scrollableArea;
109-
}
101+
return (
102+
<ScrollContainer scrollKey={scrollKey} shouldUpdateScroll={shouldUpdateScroll}>
103+
{scrollableArea}
104+
</ScrollContainer>
105+
);
110106
}
111107

112108
}
113109

114110
StatusList.propTypes = {
111+
scrollKey: PropTypes.string.isRequired,
115112
statusIds: ImmutablePropTypes.list.isRequired,
116113
onScrollToBottom: PropTypes.func,
117114
onScrollToTop: PropTypes.func,
118115
onScroll: PropTypes.func,
119-
trackScroll: PropTypes.bool,
116+
shouldUpdateScroll: PropTypes.func,
120117
isLoading: PropTypes.bool,
121118
isUnread: PropTypes.bool,
122119
hasMore: PropTypes.bool,

app/assets/javascripts/components/containers/mastodon.jsx

Lines changed: 120 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,125 @@ addLocaleData([
9999
...id,
100100
]);
101101

102+
const getTopWhenReplacing = (previous, { location }) => location && location.action === 'REPLACE' && [0, 0];
103+
104+
const hiddenColumnContainerStyle = {
105+
position: 'absolute',
106+
left: '0',
107+
top: '0',
108+
visibility: 'hidden'
109+
};
110+
111+
class Container extends React.PureComponent {
112+
113+
constructor(props) {
114+
super(props);
115+
116+
this.state = {
117+
renderedPersistents: [],
118+
unrenderedPersistents: [],
119+
};
120+
}
121+
122+
componentWillMount () {
123+
this.unlistenHistory = null;
124+
125+
this.setState(() => {
126+
return {
127+
mountImpersistent: false,
128+
renderedPersistents: [],
129+
unrenderedPersistents: [
130+
{pathname: '/timelines/home', component: HomeTimeline},
131+
{pathname: '/timelines/public', component: PublicTimeline},
132+
{pathname: '/timelines/public/local', component: CommunityTimeline},
133+
134+
{pathname: '/notifications', component: Notifications},
135+
{pathname: '/favourites', component: FavouritedStatuses}
136+
],
137+
};
138+
}, () => {
139+
if (this.unlistenHistory) {
140+
return;
141+
}
142+
143+
this.unlistenHistory = browserHistory.listen(location => {
144+
const pathname = location.pathname.replace(/\/$/, '').toLowerCase();
145+
146+
this.setState(oldState => {
147+
let persistentMatched = false;
148+
149+
const newState = {
150+
renderedPersistents: oldState.renderedPersistents.map(persistent => {
151+
const givenMatched = persistent.pathname === pathname;
152+
153+
if (givenMatched) {
154+
persistentMatched = true;
155+
}
156+
157+
return {
158+
hidden: !givenMatched,
159+
pathname: persistent.pathname,
160+
component: persistent.component
161+
};
162+
}),
163+
};
164+
165+
if (!persistentMatched) {
166+
newState.unrenderedPersistents = [];
167+
168+
oldState.unrenderedPersistents.forEach(persistent => {
169+
if (persistent.pathname === pathname) {
170+
persistentMatched = true;
171+
172+
newState.renderedPersistents.push({
173+
hidden: false,
174+
pathname: persistent.pathname,
175+
component: persistent.component
176+
});
177+
} else {
178+
newState.unrenderedPersistents.push(persistent);
179+
}
180+
});
181+
}
182+
183+
newState.mountImpersistent = !persistentMatched;
184+
185+
return newState;
186+
});
187+
});
188+
});
189+
}
190+
191+
componentWillUnmount () {
192+
if (this.unlistenHistory) {
193+
this.unlistenHistory();
194+
}
195+
196+
this.unlistenHistory = "done";
197+
}
198+
199+
render () {
200+
// Hide some components rather than unmounting them to allow to show again
201+
// quickly and keep the view state such as the scrolled offset.
202+
const persistentsView = this.state.renderedPersistents.map((persistent) =>
203+
<div aria-hidden={persistent.hidden} key={persistent.pathname} className='mastodon-column-container' style={persistent.hidden ? hiddenColumnContainerStyle : null}>
204+
<persistent.component shouldUpdateScroll={persistent.hidden ? Function.prototype : getTopWhenReplacing} />
205+
</div>
206+
);
207+
208+
return (
209+
<UI>
210+
{this.state.mountImpersistent && this.props.children}
211+
{persistentsView}
212+
</UI>
213+
);
214+
}
215+
}
216+
217+
Container.propTypes = {
218+
children: PropTypes.node,
219+
};
220+
102221
class Mastodon extends React.Component {
103222

104223
componentDidMount() {
@@ -160,18 +279,12 @@ class Mastodon extends React.Component {
160279
<IntlProvider locale={locale} messages={getMessagesForLocale(locale)}>
161280
<Provider store={store}>
162281
<Router history={browserHistory} render={applyRouterMiddleware(useScroll())}>
163-
<Route path='/' component={UI}>
282+
<Route path='/' component={Container}>
164283
<IndexRedirect to="/getting-started" />
165284

166285
<Route path='getting-started' component={GettingStarted} />
167-
<Route path='timelines/home' component={HomeTimeline} />
168-
<Route path='timelines/public' component={PublicTimeline} />
169-
<Route path='timelines/public/local' component={CommunityTimeline} />
170286
<Route path='timelines/tag/:id' component={HashtagTimeline} />
171287

172-
<Route path='notifications' component={Notifications} />
173-
<Route path='favourites' component={FavouritedStatuses} />
174-
175288
<Route path='statuses/new' component={Compose} />
176289
<Route path='statuses/:statusId' component={Status} />
177290
<Route path='statuses/:statusId/reblogs' component={Reblogs} />

app/assets/javascripts/components/features/account_timeline/index.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ class AccountTimeline extends React.PureComponent {
6262

6363
<StatusList
6464
prepend={<HeaderContainer accountId={this.props.params.accountId} />}
65+
scrollKey='account_timeline'
6566
statusIds={statusIds}
6667
isLoading={isLoading}
6768
hasMore={hasMore}

app/assets/javascripts/components/features/community_timeline/index.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class CommunityTimeline extends React.PureComponent {
7777
return (
7878
<Column icon='users' active={hasUnread} heading={intl.formatMessage(messages.title)}>
7979
<ColumnBackButtonSlim />
80-
<StatusListContainer type='community' emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />} />
80+
<StatusListContainer {...this.props} scrollKey='community_timeline' type='community' emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />} />
8181
</Column>
8282
);
8383
}

app/assets/javascripts/components/features/favourited_statuses/index.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class Favourites extends React.PureComponent {
4747
return (
4848
<Column icon='star' heading={intl.formatMessage(messages.heading)}>
4949
<ColumnBackButtonSlim />
50-
<StatusList statusIds={statusIds} me={me} onScrollToBottom={this.handleScrollToBottom} />
50+
<StatusList {...this.props} onScrollToBottom={this.handleScrollToBottom} />
5151
</Column>
5252
);
5353
}

app/assets/javascripts/components/features/hashtag_timeline/index.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ class HashtagTimeline extends React.PureComponent {
7171
return (
7272
<Column icon='hashtag' active={hasUnread} heading={id}>
7373
<ColumnBackButtonSlim />
74-
<StatusListContainer type='tag' id={id} emptyMessage={<FormattedMessage id='empty_column.hashtag' defaultMessage='There is nothing in this hashtag yet.' />} />
74+
<StatusListContainer scrollKey='hashtag_timeline' type='tag' id={id} emptyMessage={<FormattedMessage id='empty_column.hashtag' defaultMessage='There is nothing in this hashtag yet.' />} />
7575
</Column>
7676
);
7777
}

app/assets/javascripts/components/features/home_timeline/index.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class HomeTimeline extends React.PureComponent {
2222
return (
2323
<Column icon='home' active={hasUnread} heading={intl.formatMessage(messages.title)}>
2424
<ColumnSettingsContainer />
25-
<StatusListContainer {...this.props} type='home' emptyMessage={<FormattedMessage id='empty_column.home' defaultMessage="You aren't following anyone yet. Visit {public} or use search to get started and meet other users." values={{ public: <Link to='/timelines/public'><FormattedMessage id='empty_column.home.public_timeline' defaultMessage='the public timeline' /></Link> }} />} />
25+
<StatusListContainer {...this.props} scrollKey='home_timeline' type='home' emptyMessage={<FormattedMessage id='empty_column.home' defaultMessage="You aren't following anyone yet. Visit {public} or use search to get started and meet other users." values={{ public: <Link to='/timelines/public'><FormattedMessage id='empty_column.home.public_timeline' defaultMessage='the public timeline' /></Link> }} />} />
2626
</Column>
2727
);
2828
}

app/assets/javascripts/components/features/notifications/index.jsx

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ class Notifications extends React.PureComponent {
8080
}
8181

8282
render () {
83-
const { intl, notifications, trackScroll, isLoading, isUnread } = this.props;
83+
const { intl, notifications, shouldUpdateScroll, isLoading, isUnread } = this.props;
8484

8585
let loadMore = '';
8686
let scrollableArea = '';
@@ -113,33 +113,23 @@ class Notifications extends React.PureComponent {
113113
);
114114
}
115115

116-
if (trackScroll) {
117-
return (
118-
<Column icon='bell' active={isUnread} heading={intl.formatMessage(messages.title)}>
119-
<ColumnSettingsContainer />
120-
<ClearColumnButton onClick={this.handleClear} />
121-
<ScrollContainer scrollKey='notifications'>
122-
{scrollableArea}
123-
</ScrollContainer>
124-
</Column>
125-
);
126-
} else {
127-
return (
128-
<Column icon='bell' active={isUnread} heading={intl.formatMessage(messages.title)}>
129-
<ColumnSettingsContainer />
130-
<ClearColumnButton onClick={this.handleClear} />
116+
return (
117+
<Column icon='bell' active={isUnread} heading={intl.formatMessage(messages.title)}>
118+
<ColumnSettingsContainer />
119+
<ClearColumnButton onClick={this.handleClear} />
120+
<ScrollContainer scrollKey='notifications' shouldUpdateScroll={shouldUpdateScroll}>
131121
{scrollableArea}
132-
</Column>
133-
);
134-
}
122+
</ScrollContainer>
123+
</Column>
124+
);
135125
}
136126

137127
}
138128

139129
Notifications.propTypes = {
140130
notifications: ImmutablePropTypes.list.isRequired,
141131
dispatch: PropTypes.func.isRequired,
142-
trackScroll: PropTypes.bool,
132+
shouldUpdateScroll: PropTypes.func,
143133
intl: PropTypes.object.isRequired,
144134
isLoading: PropTypes.bool,
145135
isUnread: PropTypes.bool

app/assets/javascripts/components/features/public_timeline/index.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class PublicTimeline extends React.PureComponent {
7777
return (
7878
<Column icon='globe' active={hasUnread} heading={intl.formatMessage(messages.title)}>
7979
<ColumnBackButtonSlim />
80-
<StatusListContainer type='public' emptyMessage={<FormattedMessage id='empty_column.public' defaultMessage='There is nothing here! Write something publicly, or manually follow users from other instances to fill it up' />} />
80+
<StatusListContainer {...this.props} type='public' scrollKey='public_timeline' emptyMessage={<FormattedMessage id='empty_column.public' defaultMessage='There is nothing here! Write something publicly, or manually follow users from other instances to fill it up' />} />
8181
</Column>
8282
);
8383
}

app/assets/javascripts/components/features/ui/containers/status_list_container.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ const makeMapStateToProps = () => {
4040
const getStatusIds = makeGetStatusIds();
4141

4242
const mapStateToProps = (state, props) => ({
43+
scrollKey: props.scrollKey,
44+
shouldUpdateScroll: props.shouldUpdateScroll,
4345
statusIds: getStatusIds(state, props),
4446
isLoading: state.getIn(['timelines', props.type, 'isLoading'], true),
4547
isUnread: state.getIn(['timelines', props.type, 'unread']) > 0,

app/assets/javascripts/components/features/ui/index.jsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,9 @@ class UI extends React.PureComponent {
127127
mountedColumns = (
128128
<ColumnsArea>
129129
<Compose withHeader={true} />
130-
<HomeTimeline trackScroll={false} />
131-
<Notifications trackScroll={false} />
132-
{children}
130+
<HomeTimeline shouldUpdateScroll={() => false} />
131+
<Notifications shouldUpdateScroll={() => false} />
132+
<div style={{display: 'flex', flex: '1 1 auto', position: 'relative'}}>{children}</div>
133133
</ColumnsArea>
134134
);
135135
}

app/assets/stylesheets/components.scss

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,11 @@
8989
border: none;
9090
background: transparent;
9191
cursor: pointer;
92-
transition: all 100ms ease-in;
92+
transition: color 100ms ease-in;
9393

9494
&:hover, &:active, &:focus {
9595
color: lighten($color1, 33%);
96-
transition: all 200ms ease-out;
96+
transition: color 200ms ease-out;
9797
}
9898

9999
&.disabled {
@@ -152,11 +152,11 @@
152152
padding: 0 3px;
153153
line-height: 27px;
154154
outline: 0;
155-
transition: all 100ms ease-in;
155+
transition: color 100ms ease-in;
156156

157157
&:hover, &:active, &:focus {
158158
color: lighten($color1, 26%);
159-
transition: all 200ms ease-out;
159+
transition: color 200ms ease-out;
160160
}
161161

162162
&.disabled {
@@ -1100,6 +1100,7 @@ a.status__content__spoiler-link {
11001100
flex-direction: row;
11011101
justify-content: flex-start;
11021102
overflow-x: auto;
1103+
position: relative;
11031104
}
11041105

11051106
@media screen and (min-width: 360px) {
@@ -1257,11 +1258,11 @@ a.status__content__spoiler-link {
12571258
flex-direction: row;
12581259

12591260
a {
1260-
transition: all 100ms ease-in;
1261+
transition: background 100ms ease-in;
12611262

12621263
&:hover {
12631264
background: lighten($color1, 3%);
1264-
transition: all 200ms ease-out;
1265+
transition: background 200ms ease-out;
12651266
}
12661267
}
12671268
}

app/assets/stylesheets/containers.scss

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@
99
}
1010
}
1111

12+
.mastodon-column-container {
13+
display: flex;
14+
height: 100%;
15+
width: 100%;
16+
17+
// 707568 - height 100% doesn't work on child of a flex item - chromium - Monorail
18+
// https://bugs.chromium.org/p/chromium/issues/detail?id=707568
19+
flex: 1 1 auto;
20+
}
21+
1222
.logo-container {
1323
max-width: 400px;
1424
margin: 100px auto;
@@ -40,7 +50,7 @@
4050

4151
img {
4252
opacity: 0.8;
43-
transition: all 0.8s ease;
53+
transition: opacity 0.8s ease;
4454
}
4555

4656
&:hover {

0 commit comments

Comments
 (0)