+ {this.props.onDismiss &&
}
+
this.setState({ thumbnailLoaded: true });
+
+ render () {
+ const { url, title, publisher, sharedTimes, thumbnail, blurhash } = this.props;
+
+ const { thumbnailLoaded } = this.state;
+
+ return (
+
+
+
{publisher ? publisher : }
+
{title ? title : }
+
{typeof sharedTimes === 'number' ? : }
+
+
+
+ {thumbnail ? (
+
+
+
+
+ ) :
}
+
+
+ );
+ }
+
+}
diff --git a/app/javascript/flavours/glitch/features/explore/index.js b/app/javascript/flavours/glitch/features/explore/index.js
new file mode 100644
index 00000000000..c89463e7e82
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/explore/index.js
@@ -0,0 +1,92 @@
+import React from 'react';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import Column from 'flavours/glitch/components/column';
+import ColumnHeader from 'flavours/glitch/components/column_header';
+import { NavLink, Switch, Route } from 'react-router-dom';
+import Links from './links';
+import Tags from './tags';
+import Statuses from './statuses';
+import Suggestions from './suggestions';
+import Search from 'flavours/glitch/features/compose/containers/search_container';
+import SearchResults from './results';
+import { showTrends } from 'flavours/glitch/util/initial_state';
+
+const messages = defineMessages({
+ title: { id: 'explore.title', defaultMessage: 'Explore' },
+ searchResults: { id: 'explore.search_results', defaultMessage: 'Search results' },
+});
+
+const mapStateToProps = state => ({
+ layout: state.getIn(['meta', 'layout']),
+ isSearching: state.getIn(['search', 'submitted']) || !showTrends,
+});
+
+export default @connect(mapStateToProps)
+@injectIntl
+class Explore extends React.PureComponent {
+
+ static contextTypes = {
+ router: PropTypes.object,
+ };
+
+ static propTypes = {
+ intl: PropTypes.object.isRequired,
+ multiColumn: PropTypes.bool,
+ isSearching: PropTypes.bool,
+ layout: PropTypes.string,
+ };
+
+ handleHeaderClick = () => {
+ this.column.scrollTop();
+ }
+
+ setRef = c => {
+ this.column = c;
+ }
+
+ render () {
+ const { intl, multiColumn, isSearching, layout } = this.props;
+
+ return (
+
+ {layout === 'mobile' ? (
+
+
+
+ ) : (
+
+ )}
+
+
+ {isSearching ? (
+
+ ) : (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+ );
+ }
+
+}
diff --git a/app/javascript/flavours/glitch/features/explore/links.js b/app/javascript/flavours/glitch/features/explore/links.js
new file mode 100644
index 00000000000..1d8cd8efc1c
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/explore/links.js
@@ -0,0 +1,48 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import Story from './components/story';
+import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
+import { connect } from 'react-redux';
+import { fetchTrendingLinks } from 'flavours/glitch/actions/trends';
+
+const mapStateToProps = state => ({
+ links: state.getIn(['trends', 'links', 'items']),
+ isLoading: state.getIn(['trends', 'links', 'isLoading']),
+});
+
+export default @connect(mapStateToProps)
+class Links extends React.PureComponent {
+
+ static propTypes = {
+ links: ImmutablePropTypes.list,
+ isLoading: PropTypes.bool,
+ dispatch: PropTypes.func.isRequired,
+ };
+
+ componentDidMount () {
+ const { dispatch } = this.props;
+ dispatch(fetchTrendingLinks());
+ }
+
+ render () {
+ const { isLoading, links } = this.props;
+
+ return (
+
+ {isLoading ? () : links.map(link => (
+
+ ))}
+
+ );
+ }
+
+}
diff --git a/app/javascript/flavours/glitch/features/explore/results.js b/app/javascript/flavours/glitch/features/explore/results.js
new file mode 100644
index 00000000000..8eac63c2c50
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/explore/results.js
@@ -0,0 +1,113 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { FormattedMessage } from 'react-intl';
+import { connect } from 'react-redux';
+import { expandSearch } from 'flavours/glitch/actions/search';
+import Account from 'flavours/glitch/containers/account_container';
+import Status from 'flavours/glitch/containers/status_container';
+import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag';
+import { List as ImmutableList } from 'immutable';
+import LoadMore from 'flavours/glitch/components/load_more';
+import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
+
+const mapStateToProps = state => ({
+ isLoading: state.getIn(['search', 'isLoading']),
+ results: state.getIn(['search', 'results']),
+});
+
+const appendLoadMore = (id, list, onLoadMore) => {
+ if (list.size >= 5) {
+ return list.push(
);
+ } else {
+ return list;
+ }
+};
+
+const renderAccounts = (results, onLoadMore) => appendLoadMore('accounts', results.get('accounts', ImmutableList()).map(item => (
+
+)), onLoadMore);
+
+const renderHashtags = (results, onLoadMore) => appendLoadMore('hashtags', results.get('hashtags', ImmutableList()).map(item => (
+
+)), onLoadMore);
+
+const renderStatuses = (results, onLoadMore) => appendLoadMore('statuses', results.get('statuses', ImmutableList()).map(item => (
+
+)), onLoadMore);
+
+export default @connect(mapStateToProps)
+class Results extends React.PureComponent {
+
+ static propTypes = {
+ results: ImmutablePropTypes.map,
+ isLoading: PropTypes.bool,
+ multiColumn: PropTypes.bool,
+ dispatch: PropTypes.func.isRequired,
+ };
+
+ state = {
+ type: 'all',
+ };
+
+ handleSelectAll = () => this.setState({ type: 'all' });
+ handleSelectAccounts = () => this.setState({ type: 'accounts' });
+ handleSelectHashtags = () => this.setState({ type: 'hashtags' });
+ handleSelectStatuses = () => this.setState({ type: 'statuses' });
+ handleLoadMoreAccounts = () => this.loadMore('accounts');
+ handleLoadMoreStatuses = () => this.loadMore('statuses');
+ handleLoadMoreHashtags = () => this.loadMore('hashtags');
+
+ loadMore (type) {
+ const { dispatch } = this.props;
+ dispatch(expandSearch(type));
+ }
+
+ render () {
+ const { isLoading, results } = this.props;
+ const { type } = this.state;
+
+ let filteredResults = ImmutableList();
+
+ if (!isLoading) {
+ switch(type) {
+ case 'all':
+ filteredResults = filteredResults.concat(renderAccounts(results, this.handleLoadMoreAccounts), renderHashtags(results, this.handleLoadMoreHashtags), renderStatuses(results, this.handleLoadMoreStatuses));
+ break;
+ case 'accounts':
+ filteredResults = filteredResults.concat(renderAccounts(results, this.handleLoadMoreAccounts));
+ break;
+ case 'hashtags':
+ filteredResults = filteredResults.concat(renderHashtags(results, this.handleLoadMoreHashtags));
+ break;
+ case 'statuses':
+ filteredResults = filteredResults.concat(renderStatuses(results, this.handleLoadMoreStatuses));
+ break;
+ }
+
+ if (filteredResults.size === 0) {
+ filteredResults = (
+
+
+
+ );
+ }
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+ {isLoading ? : filteredResults}
+
+
+ );
+ }
+
+}
diff --git a/app/javascript/flavours/glitch/features/explore/statuses.js b/app/javascript/flavours/glitch/features/explore/statuses.js
new file mode 100644
index 00000000000..fe08ce4667f
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/explore/statuses.js
@@ -0,0 +1,57 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import StatusList from 'flavours/glitch/components/status_list';
+import { FormattedMessage } from 'react-intl';
+import { connect } from 'react-redux';
+import { fetchTrendingStatuses, expandTrendingStatuses } from 'flavours/glitch/actions/trends';
+import { debounce } from 'lodash';
+
+const mapStateToProps = state => ({
+ statusIds: state.getIn(['status_lists', 'trending', 'items']),
+ isLoading: state.getIn(['status_lists', 'trending', 'isLoading'], true),
+ hasMore: !!state.getIn(['status_lists', 'trending', 'next']),
+});
+
+export default @connect(mapStateToProps)
+class Statuses extends React.PureComponent {
+
+ static propTypes = {
+ statusIds: ImmutablePropTypes.list,
+ isLoading: PropTypes.bool,
+ hasMore: PropTypes.bool,
+ multiColumn: PropTypes.bool,
+ dispatch: PropTypes.func.isRequired,
+ };
+
+ componentDidMount () {
+ const { dispatch } = this.props;
+ dispatch(fetchTrendingStatuses());
+ }
+
+ handleLoadMore = debounce(() => {
+ const { dispatch } = this.props;
+ dispatch(expandTrendingStatuses());
+ }, 300, { leading: true })
+
+ render () {
+ const { isLoading, hasMore, statusIds, multiColumn } = this.props;
+
+ const emptyMessage =
;
+
+ return (
+
+ );
+ }
+
+}
diff --git a/app/javascript/flavours/glitch/features/explore/suggestions.js b/app/javascript/flavours/glitch/features/explore/suggestions.js
new file mode 100644
index 00000000000..ccd2c950a5b
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/explore/suggestions.js
@@ -0,0 +1,45 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import AccountCard from 'flavours/glitch/features/directory/components/account_card';
+import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
+import { connect } from 'react-redux';
+import { fetchSuggestions, dismissSuggestion } from 'flavours/glitch/actions/suggestions';
+
+const mapStateToProps = state => ({
+ suggestions: state.getIn(['suggestions', 'items']),
+ isLoading: state.getIn(['suggestions', 'isLoading']),
+});
+
+export default @connect(mapStateToProps)
+class Suggestions extends React.PureComponent {
+
+ static propTypes = {
+ isLoading: PropTypes.bool,
+ suggestions: ImmutablePropTypes.list,
+ dispatch: PropTypes.func.isRequired,
+ };
+
+ componentDidMount () {
+ const { dispatch } = this.props;
+ dispatch(fetchSuggestions(true));
+ }
+
+ handleDismiss = (accountId) => {
+ const { dispatch } = this.props;
+ dispatch(dismissSuggestion(accountId));
+ }
+
+ render () {
+ const { isLoading, suggestions } = this.props;
+
+ return (
+
+ {isLoading ?
: suggestions.map(suggestion => (
+
+ ))}
+
+ );
+ }
+
+}
diff --git a/app/javascript/flavours/glitch/features/explore/tags.js b/app/javascript/flavours/glitch/features/explore/tags.js
new file mode 100644
index 00000000000..0ec1eb88be9
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/explore/tags.js
@@ -0,0 +1,40 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag';
+import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
+import { connect } from 'react-redux';
+import { fetchTrendingHashtags } from 'flavours/glitch/actions/trends';
+
+const mapStateToProps = state => ({
+ hashtags: state.getIn(['trends', 'tags', 'items']),
+ isLoadingHashtags: state.getIn(['trends', 'tags', 'isLoading']),
+});
+
+export default @connect(mapStateToProps)
+class Tags extends React.PureComponent {
+
+ static propTypes = {
+ hashtags: ImmutablePropTypes.list,
+ isLoading: PropTypes.bool,
+ dispatch: PropTypes.func.isRequired,
+ };
+
+ componentDidMount () {
+ const { dispatch } = this.props;
+ dispatch(fetchTrendingHashtags());
+ }
+
+ render () {
+ const { isLoading, hashtags } = this.props;
+
+ return (
+
+ {isLoading ? () : hashtags.map(hashtag => (
+
+ ))}
+
+ );
+ }
+
+}
diff --git a/app/javascript/flavours/glitch/features/getting_started/containers/trends_container.js b/app/javascript/flavours/glitch/features/getting_started/containers/trends_container.js
index 68568d169ee..d88dbbaf40a 100644
--- a/app/javascript/flavours/glitch/features/getting_started/containers/trends_container.js
+++ b/app/javascript/flavours/glitch/features/getting_started/containers/trends_container.js
@@ -1,13 +1,13 @@
import { connect } from 'react-redux';
-import { fetchTrends } from 'flavours/glitch/actions/trends';
+import { fetchTrendingHashtags } from 'flavours/glitch/actions/trends';
import Trends from '../components/trends';
const mapStateToProps = state => ({
- trends: state.getIn(['trends', 'items']),
+ trends: state.getIn(['trends', 'tags', 'items']),
});
const mapDispatchToProps = dispatch => ({
- fetchTrends: () => dispatch(fetchTrends()),
+ fetchTrends: () => dispatch(fetchTrendingHashtags()),
});
export default connect(mapStateToProps, mapDispatchToProps)(Trends);
diff --git a/app/javascript/flavours/glitch/features/getting_started/index.js b/app/javascript/flavours/glitch/features/getting_started/index.js
index 56750fd88a4..8b6a617ff4c 100644
--- a/app/javascript/flavours/glitch/features/getting_started/index.js
+++ b/app/javascript/flavours/glitch/features/getting_started/index.js
@@ -8,7 +8,7 @@ import { openModal } from 'flavours/glitch/actions/modal';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
-import { me, profile_directory, showTrends } from 'flavours/glitch/util/initial_state';
+import { me, showTrends } from 'flavours/glitch/util/initial_state';
import { fetchFollowRequests } from 'flavours/glitch/actions/accounts';
import { List as ImmutableList } from 'immutable';
import { createSelector } from 'reselect';
@@ -26,6 +26,7 @@ const messages = defineMessages({
navigation_subheading: { id: 'column_subheading.navigation', defaultMessage: 'Navigation' },
settings_subheading: { id: 'column_subheading.settings', defaultMessage: 'Settings' },
community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
+ explore: { id: 'navigation_bar.explore', defaultMessage: 'Explore' },
direct: { id: 'navigation_bar.direct', defaultMessage: 'Direct messages' },
bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' },
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
@@ -37,7 +38,6 @@ const messages = defineMessages({
lists_subheading: { id: 'column_subheading.lists', defaultMessage: 'Lists' },
misc: { id: 'navigation_bar.misc', defaultMessage: 'Misc' },
menu: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
- profile_directory: { id: 'getting_started.directory', defaultMessage: 'Profile directory' },
});
const makeMapStateToProps = () => {
@@ -122,45 +122,45 @@ const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2);
if (multiColumn) {
if (!columns.find(item => item.get('id') === 'HOME')) {
- navItems.push(
);
+ navItems.push(
);
}
if (!columns.find(item => item.get('id') === 'NOTIFICATIONS')) {
- navItems.push(
);
+ navItems.push(
);
}
if (!columns.find(item => item.get('id') === 'COMMUNITY')) {
- navItems.push(
);
+ navItems.push(
);
}
if (!columns.find(item => item.get('id') === 'PUBLIC')) {
- navItems.push(
);
+ navItems.push(
);
}
}
+ if (showTrends) {
+ navItems.push(
);
+ }
+
if (!multiColumn || !columns.find(item => item.get('id') === 'DIRECT')) {
- navItems.push(
);
+ navItems.push(
);
}
if (!multiColumn || !columns.find(item => item.get('id') === 'BOOKMARKS')) {
- navItems.push(
);
+ navItems.push(
);
}
if (myAccount.get('locked') || unreadFollowRequests > 0) {
- navItems.push(
);
+ navItems.push(
);
}
- if (profile_directory) {
- navItems.push(
);
- }
-
- navItems.push(
);
+ navItems.push(
);
listItems = listItems.concat([
-
+
{lists.filter(list => !columns.find(item => item.get('id') === 'LIST' && item.getIn(['params', 'id']) === list.get('id'))).map(list =>
-
+
)}
,
]);
diff --git a/app/javascript/flavours/glitch/features/search/index.js b/app/javascript/flavours/glitch/features/search/index.js
deleted file mode 100644
index b35c8ed495c..00000000000
--- a/app/javascript/flavours/glitch/features/search/index.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import React from 'react';
-import SearchContainer from 'flavours/glitch/features/compose/containers/search_container';
-import SearchResultsContainer from 'flavours/glitch/features/compose/containers/search_results_container';
-
-const Search = () => (
-
-);
-
-export default Search;
diff --git a/app/javascript/flavours/glitch/features/ui/components/columns_area.js b/app/javascript/flavours/glitch/features/ui/components/columns_area.js
index bfb1ae4057d..048251fa6e2 100644
--- a/app/javascript/flavours/glitch/features/ui/components/columns_area.js
+++ b/app/javascript/flavours/glitch/features/ui/components/columns_area.js
@@ -49,7 +49,7 @@ const componentMap = {
'DIRECTORY': Directory,
};
-const shouldHideFAB = path => path.match(/^\/statuses\/|^\/@[^/]+\/\d+|^\/publish|^\/search|^\/getting-started|^\/start/);
+const shouldHideFAB = path => path.match(/^\/statuses\/|^\/@[^/]+\/\d+|^\/publish|^\/explore|^\/getting-started|^\/start/);
const messages = defineMessages({
publish: { id: 'compose_form.publish', defaultMessage: 'Toot' },
diff --git a/app/javascript/flavours/glitch/features/ui/components/link_footer.js b/app/javascript/flavours/glitch/features/ui/components/link_footer.js
index d91f9b8c403..67fa067f933 100644
--- a/app/javascript/flavours/glitch/features/ui/components/link_footer.js
+++ b/app/javascript/flavours/glitch/features/ui/components/link_footer.js
@@ -3,7 +3,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
import { Link } from 'react-router-dom';
-import { limitedFederationMode, version, repository, source_url } from 'flavours/glitch/util/initial_state';
+import { limitedFederationMode, version, repository, source_url, profile_directory as profileDirectory } from 'flavours/glitch/util/initial_state';
import { signOutLink, securityLink, privacyPolicyLink } from 'flavours/glitch/util/backend_links';
import { logOut } from 'flavours/glitch/util/log_out';
import { openModal } from 'flavours/glitch/actions/modal';
@@ -54,6 +54,7 @@ class LinkFooter extends React.PureComponent {
{((this.context.identity.permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS) &&
· }
{!!securityLink &&
· }
{!limitedFederationMode &&
· }
+ {profileDirectory &&
· }
·
·
·
diff --git a/app/javascript/flavours/glitch/features/ui/components/navigation_panel.js b/app/javascript/flavours/glitch/features/ui/components/navigation_panel.js
index 2dcd535ca4b..f4d649456e5 100644
--- a/app/javascript/flavours/glitch/features/ui/components/navigation_panel.js
+++ b/app/javascript/flavours/glitch/features/ui/components/navigation_panel.js
@@ -2,7 +2,7 @@ import React from 'react';
import { NavLink, withRouter } from 'react-router-dom';
import { FormattedMessage } from 'react-intl';
import Icon from 'flavours/glitch/components/icon';
-import { profile_directory, showTrends } from 'flavours/glitch/util/initial_state';
+import { showTrends } from 'flavours/glitch/util/initial_state';
import { preferencesLink, relationshipsLink } from 'flavours/glitch/util/backend_links';
import NotificationsCounterIcon from './notifications_counter_icon';
import FollowRequestsNavLink from './follow_requests_nav_link';
@@ -14,11 +14,11 @@ const NavigationPanel = ({ onOpenSettings }) => (
+ { showTrends &&
}
- {profile_directory &&
}
diff --git a/app/javascript/flavours/glitch/features/ui/components/tabs_bar.js b/app/javascript/flavours/glitch/features/ui/components/tabs_bar.js
index 55cc84f5e05..0a7078a9c65 100644
--- a/app/javascript/flavours/glitch/features/ui/components/tabs_bar.js
+++ b/app/javascript/flavours/glitch/features/ui/components/tabs_bar.js
@@ -12,7 +12,7 @@ export const links = [
,
,
,
-
,
+
,
,
];
diff --git a/app/javascript/flavours/glitch/features/ui/index.js b/app/javascript/flavours/glitch/features/ui/index.js
index 2be6d94780c..7b547fd5bb3 100644
--- a/app/javascript/flavours/glitch/features/ui/index.js
+++ b/app/javascript/flavours/glitch/features/ui/index.js
@@ -5,13 +5,14 @@ import LoadingBarContainer from './containers/loading_bar_container';
import ModalContainer from './containers/modal_container';
import { connect } from 'react-redux';
import { Redirect, withRouter } from 'react-router-dom';
-import { isMobile } from 'flavours/glitch/util/is_mobile';
+import { layoutFromWindow } from 'flavours/glitch/util/is_mobile';
import { debounce } from 'lodash';
import { uploadCompose, resetCompose, changeComposeSpoilerness } from 'flavours/glitch/actions/compose';
import { expandHomeTimeline } from 'flavours/glitch/actions/timelines';
import { expandNotifications, notificationsSetVisibility } from 'flavours/glitch/actions/notifications';
import { fetchRules } from 'flavours/glitch/actions/rules';
import { clearHeight } from 'flavours/glitch/actions/height_cache';
+import { changeLayout } from 'flavours/glitch/actions/app';
import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'flavours/glitch/actions/markers';
import { WrappedSwitch, WrappedRoute } from 'flavours/glitch/util/react_router_helpers';
import UploadArea from './components/upload_area';
@@ -47,9 +48,9 @@ import {
Mutes,
PinnedStatuses,
Lists,
- Search,
GettingStartedMisc,
Directory,
+ Explore,
FollowRecommendations,
} from 'flavours/glitch/util/async-components';
import { HotKeys } from 'react-hotkeys';
@@ -66,10 +67,12 @@ const messages = defineMessages({
});
const mapStateToProps = state => ({
+ layout: state.getIn(['meta', 'layout']),
hasComposingText: state.getIn(['compose', 'text']).trim().length !== 0,
hasMediaAttachments: state.getIn(['compose', 'media_attachments']).size > 0,
canUploadMore: !state.getIn(['compose', 'media_attachments']).some(x => ['audio', 'video'].includes(x.get('type'))) && state.getIn(['compose', 'media_attachments']).size < 4,
- layout: state.getIn(['local_settings', 'layout']),
+ layout: state.getIn(['meta', 'layout']),
+ layout_local_setting: state.getIn(['local_settings', 'layout']),
isWide: state.getIn(['local_settings', 'stretch']),
navbarUnder: state.getIn(['local_settings', 'navbar_under']),
dropdownMenuIsOpen: state.getIn(['dropdown_menu', 'openId']) !== null,
@@ -120,26 +123,13 @@ class SwitchingColumnsArea extends React.PureComponent {
static propTypes = {
children: PropTypes.node,
- layout: PropTypes.string,
location: PropTypes.object,
navbarUnder: PropTypes.bool,
- onLayoutChange: PropTypes.func.isRequired,
+ mobile: PropTypes.bool,
};
- state = {
- mobile: isMobile(window.innerWidth, this.props.layout),
- };
-
- componentWillReceiveProps (nextProps) {
- if (nextProps.layout !== this.props.layout) {
- this.setState({ mobile: isMobile(window.innerWidth, nextProps.layout) });
- }
- }
-
componentWillMount () {
- window.addEventListener('resize', this.handleResize, { passive: true });
-
- if (this.state.mobile) {
+ if (this.props.mobile) {
document.body.classList.toggle('layout-single-column', true);
document.body.classList.toggle('layout-multiple-columns', false);
} else {
@@ -148,37 +138,14 @@ class SwitchingColumnsArea extends React.PureComponent {
}
}
- componentDidUpdate (prevProps, prevState) {
+ componentDidUpdate (prevProps) {
if (![this.props.location.pathname, '/'].includes(prevProps.location.pathname)) {
this.node.handleChildrenContentChange();
}
- if (prevState.mobile !== this.state.mobile) {
- document.body.classList.toggle('layout-single-column', this.state.mobile);
- document.body.classList.toggle('layout-multiple-columns', !this.state.mobile);
- }
- }
-
- componentWillUnmount () {
- window.removeEventListener('resize', this.handleResize);
- }
-
- handleLayoutChange = debounce(() => {
- // The cached heights are no longer accurate, invalidate
- this.props.onLayoutChange();
- }, 500, {
- trailing: true,
- })
-
- handleResize = () => {
- const mobile = isMobile(window.innerWidth, this.props.layout);
-
- if (mobile !== this.state.mobile) {
- this.handleLayoutChange.cancel();
- this.props.onLayoutChange();
- this.setState({ mobile });
- } else {
- this.handleLayoutChange();
+ if (prevProps.mobile !== this.props.mobile) {
+ document.body.classList.toggle('layout-single-column', this.props.mobile);
+ document.body.classList.toggle('layout-multiple-columns', !this.props.mobile);
}
}
@@ -189,12 +156,11 @@ class SwitchingColumnsArea extends React.PureComponent {
}
render () {
- const { children, navbarUnder } = this.props;
- const singleColumn = this.state.mobile;
- const redirect = singleColumn ?
:
;
+ const { children, mobile, navbarUnder } = this.props;
+ const redirect = mobile ?
:
;
return (
-
+
{redirect}
@@ -213,8 +179,8 @@ class SwitchingColumnsArea extends React.PureComponent {
-
+
@@ -256,7 +222,7 @@ class UI extends React.Component {
static propTypes = {
dispatch: PropTypes.func.isRequired,
children: PropTypes.node,
- layout: PropTypes.string,
+ layout_local_setting: PropTypes.string,
isWide: PropTypes.bool,
systemFontUi: PropTypes.bool,
navbarUnder: PropTypes.bool,
@@ -272,6 +238,7 @@ class UI extends React.Component {
unreadNotifications: PropTypes.number,
showFaviconBadge: PropTypes.bool,
moved: PropTypes.map,
+ layout: PropTypes.string.isRequired,
firstLaunch: PropTypes.bool,
username: PropTypes.string,
};
@@ -293,11 +260,6 @@ class UI extends React.Component {
}
}
- handleLayoutChange = () => {
- // The cached heights are no longer accurate, invalidate
- this.props.dispatch(clearHeight());
- }
-
handleDragEnter = (e) => {
e.preventDefault();
@@ -378,8 +340,27 @@ class UI extends React.Component {
}
}
- componentWillMount () {
+ handleLayoutChange = debounce(() => {
+ this.props.dispatch(clearHeight()); // The cached heights are no longer accurate, invalidate
+ }, 500, {
+ trailing: true,
+ });
+
+ handleResize = () => {
+ const layout = layoutFromWindow(this.props.layout_local_setting);
+
+ if (layout !== this.props.layout) {
+ this.handleLayoutChange.cancel();
+ this.props.dispatch(changeLayout(layout));
+ } else {
+ this.handleLayoutChange();
+ }
+ }
+
+ componentDidMount () {
window.addEventListener('beforeunload', this.handleBeforeUnload, false);
+ window.addEventListener('resize', this.handleResize, { passive: true });
+
document.addEventListener('dragenter', this.handleDragEnter, false);
document.addEventListener('dragover', this.handleDragOver, false);
document.addEventListener('drop', this.handleDrop, false);
@@ -403,9 +384,7 @@ class UI extends React.Component {
this.props.dispatch(expandNotifications());
setTimeout(() => this.props.dispatch(fetchRules()), 3000);
- }
- componentDidMount () {
this.hotkeys.__mousetrap__.stopCallback = (e, element) => {
return ['TEXTAREA', 'SELECT', 'INPUT'].includes(element.tagName);
};
@@ -427,6 +406,19 @@ class UI extends React.Component {
}
}
+ componentWillReceiveProps (nextProps) {
+ if (nextProps.layout_local_setting !== this.props.layout_local_setting) {
+ const layout = layoutFromWindow(nextProps.layout_local_setting);
+
+ if (layout !== this.props.layout) {
+ this.handleLayoutChange.cancel();
+ this.props.dispatch(changeLayout(layout));
+ } else {
+ this.handleLayoutChange();
+ }
+ }
+ }
+
componentDidUpdate (prevProps) {
if (this.props.unreadNotifications != prevProps.unreadNotifications ||
this.props.showFaviconBadge != prevProps.showFaviconBadge) {
@@ -446,6 +438,8 @@ class UI extends React.Component {
}
window.removeEventListener('beforeunload', this.handleBeforeUnload);
+ window.removeEventListener('resize', this.handleResize);
+
document.removeEventListener('dragenter', this.handleDragEnter);
document.removeEventListener('dragover', this.handleDragOver);
document.removeEventListener('drop', this.handleDrop);
@@ -576,7 +570,7 @@ class UI extends React.Component {
render () {
const { draggingOver } = this.state;
- const { children, layout, isWide, navbarUnder, location, dropdownMenuIsOpen, moved } = this.props;
+ const { children, isWide, navbarUnder, location, dropdownMenuIsOpen, layout, moved } = this.props;
const columnsClass = layout => {
switch (layout) {
@@ -632,11 +626,11 @@ class UI extends React.Component {
)}}
/>
)}
-