From c297bf54719d0d9bee5918fb3a0c3b80af335899 Mon Sep 17 00:00:00 2001 From: noellabo Date: Fri, 23 Sep 2022 21:06:47 +0900 Subject: [PATCH] Add column width customize features --- .../settings/preferences_controller.rb | 1 + app/javascript/mastodon/components/column.js | 6 ++- .../mastodon/components/column_header.js | 43 ++++++++++++++-- .../features/account_conversations/index.js | 15 ++++-- .../features/account_gallery/index.js | 15 ++++-- .../features/account_timeline/index.js | 15 ++++-- .../mastodon/features/blocks/index.js | 7 ++- .../features/bookmarked_statuses/index.js | 38 +++++++++++--- .../mastodon/features/circles/index.js | 7 ++- .../features/direct_timeline/index.js | 32 ++++++++++-- .../mastodon/features/directory/index.js | 14 ++++- .../mastodon/features/domain_blocks/index.js | 7 ++- .../features/domain_timeline/index.js | 22 +++++++- .../emoji_reactioned_statuses/index.js | 38 +++++++++++--- .../features/emoji_reactions/index.js | 40 ++++++++++++--- .../features/favourited_statuses/index.js | 38 +++++++++++--- .../mastodon/features/favourites/index.js | 38 +++++++++++--- .../features/follow_requests/index.js | 8 +-- .../mastodon/features/followers/index.js | 15 ++++-- .../mastodon/features/following/index.js | 15 ++++-- .../features/getting_started/index.js | 8 +-- .../features/group_directory/index.js | 14 ++++- .../mastodon/features/group_timeline/index.js | 22 +++++++- .../features/hashtag_timeline/index.js | 36 ++++++++++--- .../mastodon/features/home_timeline/index.js | 44 ++++++++++++---- .../features/keyboard_shortcuts/index.js | 13 +++-- .../features/limited_timeline/index.js | 36 ++++++++++--- .../mastodon/features/list_timeline/index.js | 36 ++++++++++--- .../mastodon/features/lists/index.js | 7 ++- .../mastodon/features/mentions/index.js | 38 +++++++++++--- .../mastodon/features/mutes/index.js | 7 ++- .../mastodon/features/notifications/index.js | 50 +++++++++++++----- .../features/pinned_statuses/index.js | 7 ++- .../features/public_timeline/index.js | 22 +++++++- .../mastodon/features/reblogs/index.js | 38 +++++++++++--- .../features/referred_by_statuses/index.js | 38 +++++++++++--- .../mastodon/features/status/index.js | 31 +++++++++-- .../features/status_references/index.js | 29 +++++++++-- .../mastodon/features/subscribing/index.js | 15 ++++-- .../mastodon/features/suggestions/index.js | 36 ++++++++++--- .../mastodon/features/trends/index.js | 36 ++++++++++--- .../mastodon/features/ui/components/column.js | 6 ++- app/javascript/mastodon/initial_state.js | 1 + app/javascript/mastodon/locales/en.json | 5 ++ app/javascript/mastodon/locales/ja.json | 5 ++ .../styles/mastodon/components.scss | 51 +++++++++++++++++++ app/lib/user_settings_decorator.rb | 5 ++ app/models/user.rb | 2 +- app/serializers/initial_state_serializer.rb | 1 + .../preferences/appearance/show.html.haml | 3 ++ config/locales/simple_form.en.yml | 6 +++ config/locales/simple_form.ja.yml | 6 +++ config/settings.yml | 1 + 53 files changed, 890 insertions(+), 179 deletions(-) diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb index 3b69e31ce..af2d51650 100644 --- a/app/controllers/settings/preferences_controller.rb +++ b/app/controllers/settings/preferences_controller.rb @@ -95,6 +95,7 @@ class Settings::PreferencesController < Settings::BaseController :setting_confirm_follow_from_bot, :setting_default_search_searchability, :setting_show_reload_button, + :setting_default_column_width, notification_emails: %i(follow follow_request reblog favourite emoji_reaction status_reference mention digest report pending_account trending_tag), interactions: %i(must_be_follower must_be_following must_be_following_dm must_be_dm_to_send_email must_be_following_reference) ) diff --git a/app/javascript/mastodon/components/column.js b/app/javascript/mastodon/components/column.js index 239824a4f..48c952c7a 100644 --- a/app/javascript/mastodon/components/column.js +++ b/app/javascript/mastodon/components/column.js @@ -2,6 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { supportsPassiveEvents } from 'detect-passive-events'; import { scrollTop } from '../scroll'; +import classNames from 'classnames'; export default class Column extends React.PureComponent { @@ -9,6 +10,7 @@ export default class Column extends React.PureComponent { children: PropTypes.node, label: PropTypes.string, bindToDocument: PropTypes.bool, + columnWidth: PropTypes.string, }; scrollTop () { @@ -50,10 +52,10 @@ export default class Column extends React.PureComponent { } render () { - const { label, children } = this.props; + const { label, columnWidth, children } = this.props; return ( -
+
{children}
); diff --git a/app/javascript/mastodon/components/column_header.js b/app/javascript/mastodon/components/column_header.js index 6fd4f042c..8cae25aa2 100644 --- a/app/javascript/mastodon/components/column_header.js +++ b/app/javascript/mastodon/components/column_header.js @@ -1,10 +1,11 @@ import React from 'react'; +import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import { createPortal } from 'react-dom'; import classNames from 'classnames'; import { FormattedMessage, injectIntl, defineMessages } from 'react-intl'; import Icon from 'mastodon/components/icon'; -import { enableEmptyColumn } from 'mastodon/initial_state'; +import { enableEmptyColumn, defaultColumnWidth } from 'mastodon/initial_state'; const messages = defineMessages({ show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' }, @@ -13,7 +14,20 @@ const messages = defineMessages({ moveRight: { id: 'column_header.moveRight_settings', defaultMessage: 'Move column to the right' }, }); -export default @injectIntl +const column_width_message = [ + { id: 'x080', defaultMessage: }, + { id: 'x100', defaultMessage: }, + { id: 'x125', defaultMessage: }, + { id: 'x150', defaultMessage: }, + { id: 'free', defaultMessage: }, +]; + +const mapStateToProps = (state, { columnWidth }) => ({ + columnWidth: columnWidth ?? defaultColumnWidth, +}); + +export default @connect(mapStateToProps) +@injectIntl class ColumnHeader extends React.PureComponent { static contextTypes = { @@ -36,6 +50,8 @@ class ColumnHeader extends React.PureComponent { onClick: PropTypes.func, appendContent: PropTypes.node, collapseIssues: PropTypes.bool, + columnWidth: PropTypes.string, + onWidthChange: PropTypes.func, }; state = { @@ -88,8 +104,15 @@ class ColumnHeader extends React.PureComponent { this.props.onPin(); } + handleChangeWidth = e => { + e.stopPropagation(); + if (this.props.onWidthChange) { + this.props.onWidthChange(e.target.value); + } + } + render () { - const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues } = this.props; + const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues, columnWidth } = this.props; const { collapsed, animating } = this.state; const wrapperClassName = classNames('column-header__wrapper', { @@ -146,11 +169,25 @@ class ColumnHeader extends React.PureComponent { ); } + const widthButton = ( +
+
+ {column_width_message.map(({ id, defaultMessage }) => ( + + ))} +
+
+ ); + const collapsedContent = [ extraContent, ]; if (multiColumn) { + collapsedContent.unshift(widthButton); collapsedContent.push(moveButtons); collapsedContent.push(pinButton); } diff --git a/app/javascript/mastodon/features/account_conversations/index.js b/app/javascript/mastodon/features/account_conversations/index.js index cdb3d26c2..0db121992 100644 --- a/app/javascript/mastodon/features/account_conversations/index.js +++ b/app/javascript/mastodon/features/account_conversations/index.js @@ -17,7 +17,8 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { fetchAccountIdentityProofs } from '../../actions/identity_proofs'; import MissingIndicator from 'mastodon/components/missing_indicator'; import TimelineHint from 'mastodon/components/timeline_hint'; -import { new_features_policy } from 'mastodon/initial_state'; +import { new_features_policy, defaultColumnWidth } from 'mastodon/initial_state'; +import { changeSetting } from '../../actions/settings'; const messages = defineMessages({ title: { id: 'column.account', defaultMessage: 'Account' }, @@ -36,6 +37,7 @@ const mapStateToProps = (state, { params: { accountId } }) => ({ blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false), advancedMode: state.getIn(['settings', 'account', 'other', 'advancedMode'], new_features_policy === 'conservative' ? false : true), hideRelation: state.getIn(['settings', 'account', 'other', 'hideRelation'], false), + columnWidth: state.getIn(['settings', 'account', 'columnWidth'], defaultColumnWidth), }); const RemoteHint = ({ url }) => ( @@ -63,6 +65,7 @@ class AccountConversations extends ImmutablePureComponent { isAccount: PropTypes.bool, suspended: PropTypes.bool, multiColumn: PropTypes.bool, + columnWidth: PropTypes.string, }; componentWillMount () { @@ -93,12 +96,16 @@ class AccountConversations extends ImmutablePureComponent { this.column.scrollTop(); } + handleWidthChange = (value) => { + this.props.dispatch(changeSetting(['account', 'columnWidth'], value)); + } + setRef = c => { this.column = c; } render () { - const { statusIds, isLoading, hasMore, blockedBy, suspended, isAccount, multiColumn, hideRelation, intl } = this.props; + const { statusIds, isLoading, hasMore, blockedBy, suspended, isAccount, multiColumn, hideRelation, columnWidth, intl } = this.props; if (!isAccount) { return ( @@ -128,7 +135,7 @@ class AccountConversations extends ImmutablePureComponent { } return ( - + diff --git a/app/javascript/mastodon/features/account_gallery/index.js b/app/javascript/mastodon/features/account_gallery/index.js index bcd30ed52..eb67fd3f7 100644 --- a/app/javascript/mastodon/features/account_gallery/index.js +++ b/app/javascript/mastodon/features/account_gallery/index.js @@ -16,8 +16,9 @@ import ScrollContainer from 'mastodon/containers/scroll_container'; import LoadMore from 'mastodon/components/load_more'; import MissingIndicator from 'mastodon/components/missing_indicator'; import { openModal } from 'mastodon/actions/modal'; -import { new_features_policy } from 'mastodon/initial_state'; +import { new_features_policy, defaultColumnWidth } from 'mastodon/initial_state'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { changeSetting } from '../../actions/settings'; const messages = defineMessages({ title: { id: 'column.account', defaultMessage: 'Account' }, @@ -32,6 +33,7 @@ const mapStateToProps = (state, props) => ({ blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false), advancedMode: state.getIn(['settings', 'account', 'other', 'advancedMode'], new_features_policy === 'conservative' ? false : true), hideRelation: state.getIn(['settings', 'account', 'other', 'hideRelation'], false), + columnWidth: state.getIn(['settings', 'account', 'columnWidth'], defaultColumnWidth), }); class LoadMoreMedia extends ImmutablePureComponent { @@ -72,6 +74,7 @@ class AccountGallery extends ImmutablePureComponent { blockedBy: PropTypes.bool, suspended: PropTypes.bool, multiColumn: PropTypes.bool, + columnWidth: PropTypes.string, }; state = { @@ -140,12 +143,16 @@ class AccountGallery extends ImmutablePureComponent { this.column.scrollTop(); } + handleWidthChange = (value) => { + this.props.dispatch(changeSetting(['account', 'columnWidth'], value)); + } + setRef = c => { this.column = c; } render () { - const { attachments, isLoading, hasMore, isAccount, multiColumn, blockedBy, suspended, hideRelation, intl } = this.props; + const { attachments, isLoading, hasMore, isAccount, multiColumn, blockedBy, suspended, hideRelation, columnWidth, intl } = this.props; const { width } = this.state; if (!isAccount) { @@ -179,7 +186,7 @@ class AccountGallery extends ImmutablePureComponent { } return ( - + diff --git a/app/javascript/mastodon/features/account_timeline/index.js b/app/javascript/mastodon/features/account_timeline/index.js index fc06893be..59a8023e7 100644 --- a/app/javascript/mastodon/features/account_timeline/index.js +++ b/app/javascript/mastodon/features/account_timeline/index.js @@ -17,9 +17,10 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { fetchAccountIdentityProofs } from '../../actions/identity_proofs'; import MissingIndicator from 'mastodon/components/missing_indicator'; import TimelineHint from 'mastodon/components/timeline_hint'; -import { me, new_features_policy } from 'mastodon/initial_state'; +import { me, new_features_policy, defaultColumnWidth } from 'mastodon/initial_state'; import { connectTimeline, disconnectTimeline } from 'mastodon/actions/timelines'; import { fetchFeaturedTags } from '../../actions/featured_tags'; +import { changeSetting } from '../../actions/settings'; const emptyList = ImmutableList(); @@ -54,6 +55,7 @@ const mapStateToProps = (state, { params: { accountId, tagged }, about, withRepl withoutReblogs, showPostsInAbout, hideRelation, + columnWidth: state.getIn(['settings', 'account', 'columnWidth'], defaultColumnWidth), }; }; @@ -90,6 +92,7 @@ class AccountTimeline extends ImmutablePureComponent { remote: PropTypes.bool, remoteUrl: PropTypes.string, multiColumn: PropTypes.bool, + columnWidth: PropTypes.string, }; static defaultProps = { @@ -169,12 +172,16 @@ class AccountTimeline extends ImmutablePureComponent { this.column.scrollTop(); } + handleWidthChange = (value) => { + this.props.dispatch(changeSetting(['account', 'columnWidth'], value)); + } + setRef = c => { this.column = c; } render () { - const { intl, statusIds, featuredStatusIds, isLoading, hasMore, blockedBy, suspended, isAccount, multiColumn, remote, remoteUrl, about, withReplies, posts, advancedMode, hideFeaturedTags, showPostsInAbout, hideRelation } = this.props; + const { intl, statusIds, featuredStatusIds, isLoading, hasMore, blockedBy, suspended, isAccount, multiColumn, remote, remoteUrl, about, withReplies, posts, advancedMode, hideFeaturedTags, showPostsInAbout, hideRelation, columnWidth } = this.props; if (!isAccount) { return ( @@ -210,7 +217,7 @@ class AccountTimeline extends ImmutablePureComponent { const remoteMessage = (!about && remote) ? : null; return ( - + diff --git a/app/javascript/mastodon/features/blocks/index.js b/app/javascript/mastodon/features/blocks/index.js index 7ec177434..b66b0c00f 100644 --- a/app/javascript/mastodon/features/blocks/index.js +++ b/app/javascript/mastodon/features/blocks/index.js @@ -11,6 +11,7 @@ import ColumnBackButtonSlim from '../../components/column_back_button_slim'; import AccountContainer from '../../containers/account_container'; import { fetchBlocks, expandBlocks } from '../../actions/blocks'; import ScrollableList from '../../components/scrollable_list'; +import { defaultColumnWidth } from 'mastodon/initial_state'; const messages = defineMessages({ heading: { id: 'column.blocks', defaultMessage: 'Blocked users' }, @@ -20,6 +21,7 @@ const mapStateToProps = state => ({ accountIds: state.getIn(['user_lists', 'blocks', 'items']), hasMore: !!state.getIn(['user_lists', 'blocks', 'next']), isLoading: state.getIn(['user_lists', 'blocks', 'isLoading'], true), + columnWidth: defaultColumnWidth, }); export default @connect(mapStateToProps) @@ -34,6 +36,7 @@ class Blocks extends ImmutablePureComponent { isLoading: PropTypes.bool, intl: PropTypes.object.isRequired, multiColumn: PropTypes.bool, + columnWidth: PropTypes.string, }; componentWillMount () { @@ -45,7 +48,7 @@ class Blocks extends ImmutablePureComponent { }, 300, { leading: true }); render () { - const { intl, accountIds, hasMore, multiColumn, isLoading } = this.props; + const { intl, accountIds, hasMore, multiColumn, isLoading, columnWidth } = this.props; if (!accountIds) { return ( @@ -58,7 +61,7 @@ class Blocks extends ImmutablePureComponent { const emptyMessage = ; return ( - + ({ - statusIds: state.getIn(['status_lists', 'bookmarks', 'items']), - isLoading: state.getIn(['status_lists', 'bookmarks', 'isLoading'], true), - hasMore: !!state.getIn(['status_lists', 'bookmarks', 'next']), -}); +const mapStateToProps = (state, { columnId }) => { + const uuid = columnId; + const columns = state.getIn(['settings', 'columns']); + const index = columns.findIndex(c => c.get('uuid') === uuid); + const columnWidth = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'columnWidth']) : state.getIn(['settings', 'bookmarked_statuses', 'columnWidth']); + + return { + statusIds: state.getIn(['status_lists', 'bookmarks', 'items']), + isLoading: state.getIn(['status_lists', 'bookmarks', 'isLoading'], true), + hasMore: !!state.getIn(['status_lists', 'bookmarks', 'next']), + columnWidth: columnWidth ?? defaultColumnWidth, + }; +}; export default @connect(mapStateToProps) @injectIntl @@ -31,6 +42,7 @@ class Bookmarks extends ImmutablePureComponent { intl: PropTypes.object.isRequired, columnId: PropTypes.string, multiColumn: PropTypes.bool, + columnWidth: PropTypes.string, hasMore: PropTypes.bool, isLoading: PropTypes.bool, }; @@ -66,14 +78,24 @@ class Bookmarks extends ImmutablePureComponent { this.props.dispatch(expandBookmarkedStatuses()); }, 300, { leading: true }) + handleWidthChange = (value) => { + const { columnId, dispatch } = this.props; + + if (columnId) { + dispatch(changeColumnParams(columnId, 'columnWidth', value)); + } else { + dispatch(changeSetting(['bookmarked_statuses', 'columnWidth'], value)); + } + } + render () { - const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props; + const { intl, statusIds, columnId, multiColumn, hasMore, isLoading, columnWidth } = this.props; const pinned = !!columnId; const emptyMessage = ; return ( - + diff --git a/app/javascript/mastodon/features/circles/index.js b/app/javascript/mastodon/features/circles/index.js index 2c1329f3d..a621e9c5b 100644 --- a/app/javascript/mastodon/features/circles/index.js +++ b/app/javascript/mastodon/features/circles/index.js @@ -13,6 +13,7 @@ import NewCircleForm from './components/new_circle_form'; import Circle from './components/circle'; import { createSelector } from 'reselect'; import ScrollableList from '../../components/scrollable_list'; +import { defaultColumnWidth } from 'mastodon/initial_state'; const messages = defineMessages({ heading: { id: 'column.circles', defaultMessage: 'Circles' }, @@ -29,6 +30,7 @@ const getOrderedCircles = createSelector([state => state.get('circles')], circle const mapStateToProps = state => ({ circles: getOrderedCircles(state), + columnWidth: defaultColumnWidth, }); export default @connect(mapStateToProps) @@ -41,6 +43,7 @@ class Circles extends ImmutablePureComponent { circles: ImmutablePropTypes.list, intl: PropTypes.object.isRequired, multiColumn: PropTypes.bool, + columnWidth: PropTypes.string, }; componentWillMount () { @@ -48,7 +51,7 @@ class Circles extends ImmutablePureComponent { } render () { - const { intl, circles, multiColumn } = this.props; + const { intl, circles, multiColumn, columnWidth } = this.props; if (!circles) { return ( @@ -61,7 +64,7 @@ class Circles extends ImmutablePureComponent { const emptyMessage = ; return ( - + diff --git a/app/javascript/mastodon/features/direct_timeline/index.js b/app/javascript/mastodon/features/direct_timeline/index.js index 68523666c..62191623c 100644 --- a/app/javascript/mastodon/features/direct_timeline/index.js +++ b/app/javascript/mastodon/features/direct_timeline/index.js @@ -8,12 +8,25 @@ import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { connectDirectStream } from '../../actions/streaming'; import ConversationsListContainer from './containers/conversations_list_container'; +import { defaultColumnWidth } from 'mastodon/initial_state'; +import { changeSetting } from '../../actions/settings'; +import { changeColumnParams } from '../../actions/columns'; const messages = defineMessages({ title: { id: 'column.direct', defaultMessage: 'Direct messages' }, }); -export default @connect() +const mapStateToProps = (state, { columnId }) => { + const uuid = columnId; + const columns = state.getIn(['settings', 'columns']); + const index = columns.findIndex(c => c.get('uuid') === uuid); + const columnWidth = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'columnWidth']) : state.getIn(['settings', 'direct', 'columnWidth']); + + return { + columnWidth: columnWidth ?? defaultColumnWidth, + }; +}; +export default @connect(mapStateToProps) @injectIntl class DirectTimeline extends React.PureComponent { @@ -23,6 +36,7 @@ class DirectTimeline extends React.PureComponent { intl: PropTypes.object.isRequired, hasUnread: PropTypes.bool, multiColumn: PropTypes.bool, + columnWidth: PropTypes.string, }; handlePin = () => { @@ -69,12 +83,22 @@ class DirectTimeline extends React.PureComponent { this.props.dispatch(expandConversations({ maxId })); } + handleWidthChange = (value) => { + const { columnId, dispatch } = this.props; + + if (columnId) { + dispatch(changeColumnParams(columnId, 'columnWidth', value)); + } else { + dispatch(changeSetting(['direct', 'columnWidth'], value)); + } + } + render () { - const { intl, hasUnread, columnId, multiColumn } = this.props; + const { intl, hasUnread, columnId, multiColumn, columnWidth } = this.props; const pinned = !!columnId; return ( - + ({ accountIds: state.getIn(['user_lists', 'directory', 'items'], ImmutableList()), isLoading: state.getIn(['user_lists', 'directory', 'isLoading'], true), domain: state.getIn(['meta', 'domain']), + columnWidth: state.getIn(['settings', 'directory', 'columnWidth'], defaultColumnWidth), }); export default @connect(mapStateToProps) @@ -43,6 +46,7 @@ class Directory extends React.PureComponent { columnId: PropTypes.string, intl: PropTypes.object.isRequired, multiColumn: PropTypes.bool, + columnWidth: PropTypes.string, domain: PropTypes.string.isRequired, params: PropTypes.shape({ order: PropTypes.string, @@ -123,8 +127,12 @@ class Directory extends React.PureComponent { dispatch(expandDirectory(this.getParams(this.props, this.state))); } + handleWidthChange = (value) => { + this.props.dispatch(changeSetting(['directory', 'columnWidth'], value)); + } + render () { - const { isLoading, accountIds, intl, columnId, multiColumn, domain } = this.props; + const { isLoading, accountIds, intl, columnId, multiColumn, domain, columnWidth } = this.props; const { order, local } = this.getParams(this.props, this.state); const pinned = !!columnId; @@ -151,7 +159,7 @@ class Directory extends React.PureComponent { ); return ( - + {multiColumn && !pinned ? {scrollableArea} : scrollableArea} diff --git a/app/javascript/mastodon/features/domain_blocks/index.js b/app/javascript/mastodon/features/domain_blocks/index.js index edb80aef4..d1d5a928c 100644 --- a/app/javascript/mastodon/features/domain_blocks/index.js +++ b/app/javascript/mastodon/features/domain_blocks/index.js @@ -11,6 +11,7 @@ import ColumnBackButtonSlim from '../../components/column_back_button_slim'; import DomainContainer from '../../containers/domain_container'; import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_blocks'; import ScrollableList from '../../components/scrollable_list'; +import { defaultColumnWidth } from 'mastodon/initial_state'; const messages = defineMessages({ heading: { id: 'column.domain_blocks', defaultMessage: 'Blocked domains' }, @@ -20,6 +21,7 @@ const messages = defineMessages({ const mapStateToProps = state => ({ domains: state.getIn(['domain_lists', 'blocks', 'items']), hasMore: !!state.getIn(['domain_lists', 'blocks', 'next']), + columnWidth: defaultColumnWidth, }); export default @connect(mapStateToProps) @@ -33,6 +35,7 @@ class Blocks extends ImmutablePureComponent { domains: ImmutablePropTypes.orderedSet, intl: PropTypes.object.isRequired, multiColumn: PropTypes.bool, + columnWidth: PropTypes.string, }; componentWillMount () { @@ -44,7 +47,7 @@ class Blocks extends ImmutablePureComponent { }, 300, { leading: true }); render () { - const { intl, domains, hasMore, multiColumn } = this.props; + const { intl, domains, hasMore, multiColumn, columnWidth } = this.props; if (!domains) { return ( @@ -57,7 +60,7 @@ class Blocks extends ImmutablePureComponent { const emptyMessage = ; return ( - + { const uuid = props.columnId; const columns = state.getIn(['settings', 'columns']); const index = columns.findIndex(c => c.get('uuid') === uuid); + const columnWidth = (props.columnId && index >= 0) ? columns.get(index).getIn(['params', 'columnWidth']) : state.getIn(['settings', 'domain', 'columnWidth']); const onlyMedia = (props.columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'domain', 'other', 'onlyMedia']); const withoutMedia = (props.columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'withoutMedia']) : state.getIn(['settings', 'domain', 'other', 'withoutMedia']); const withoutBot = (props.columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'withoutBot']) : state.getIn(['settings', 'domain', 'other', 'withoutBot']); @@ -26,6 +30,7 @@ const mapStateToProps = (state, props) => { withoutMedia, withoutBot, domain: domain, + columnWidth: columnWidth ?? defaultColumnWidth, }; }; @@ -50,6 +55,7 @@ class DomainTimeline extends React.PureComponent { intl: PropTypes.object.isRequired, hasUnread: PropTypes.bool, multiColumn: PropTypes.bool, + columnWidth: PropTypes.string, onlyMedia: PropTypes.bool, withoutMedia: PropTypes.bool, withoutBot: PropTypes.bool, @@ -109,12 +115,22 @@ class DomainTimeline extends React.PureComponent { dispatch(expandDomainTimeline(domain, { maxId, onlyMedia, withoutMedia, withoutBot })); } + handleWidthChange = (value) => { + const { columnId, dispatch } = this.props; + + if (columnId) { + dispatch(changeColumnParams(columnId, 'columnWidth', value)); + } else { + dispatch(changeSetting(['domain', 'columnWidth'], value)); + } + } + render () { - const { hasUnread, columnId, multiColumn, onlyMedia, withoutMedia, withoutBot, domain } = this.props; + const { hasUnread, columnId, multiColumn, onlyMedia, withoutMedia, withoutBot, domain, columnWidth } = this.props; const pinned = !!columnId; return ( - + diff --git a/app/javascript/mastodon/features/emoji_reactioned_statuses/index.js b/app/javascript/mastodon/features/emoji_reactioned_statuses/index.js index 98baf7e15..d25c78aa8 100644 --- a/app/javascript/mastodon/features/emoji_reactioned_statuses/index.js +++ b/app/javascript/mastodon/features/emoji_reactioned_statuses/index.js @@ -10,16 +10,27 @@ import StatusList from '../../components/status_list'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { debounce } from 'lodash'; +import { defaultColumnWidth } from 'mastodon/initial_state'; +import { changeSetting } from '../../actions/settings'; +import { changeColumnParams } from '../../actions/columns'; const messages = defineMessages({ heading: { id: 'column.emoji_reactions', defaultMessage: 'EmojiReactions' }, }); -const mapStateToProps = state => ({ - statusIds: state.getIn(['status_lists', 'emoji_reactions', 'items']), - isLoading: state.getIn(['status_lists', 'emoji_reactions', 'isLoading'], true), - hasMore: !!state.getIn(['status_lists', 'emoji_reactions', 'next']), -}); +const mapStateToProps = (state, { columnId }) => { + const uuid = columnId; + const columns = state.getIn(['settings', 'columns']); + const index = columns.findIndex(c => c.get('uuid') === uuid); + const columnWidth = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'columnWidth']) : state.getIn(['settings', 'emoji_reactioned_statuses', 'columnWidth']); + + return { + statusIds: state.getIn(['status_lists', 'emoji_reactions', 'items']), + isLoading: state.getIn(['status_lists', 'emoji_reactions', 'isLoading'], true), + hasMore: !!state.getIn(['status_lists', 'emoji_reactions', 'next']), + columnWidth: columnWidth ?? defaultColumnWidth, + }; +}; export default @connect(mapStateToProps) @injectIntl @@ -31,6 +42,7 @@ class EmojiReactions extends ImmutablePureComponent { intl: PropTypes.object.isRequired, columnId: PropTypes.string, multiColumn: PropTypes.bool, + columnWidth: PropTypes.string, hasMore: PropTypes.bool, isLoading: PropTypes.bool, }; @@ -66,14 +78,24 @@ class EmojiReactions extends ImmutablePureComponent { this.props.dispatch(expandEmojiReactionedStatuses()); }, 300, { leading: true }) + handleWidthChange = (value) => { + const { columnId, dispatch } = this.props; + + if (columnId) { + dispatch(changeColumnParams(columnId, 'columnWidth', value)); + } else { + dispatch(changeSetting(['emoji_reactioned_statuses', 'columnWidth'], value)); + } + } + render () { - const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props; + const { intl, statusIds, columnId, multiColumn, hasMore, isLoading, columnWidth } = this.props; const pinned = !!columnId; const emptyMessage = ; return ( - + diff --git a/app/javascript/mastodon/features/emoji_reactions/index.js b/app/javascript/mastodon/features/emoji_reactions/index.js index 9e2fbee07..d57132f7e 100644 --- a/app/javascript/mastodon/features/emoji_reactions/index.js +++ b/app/javascript/mastodon/features/emoji_reactions/index.js @@ -16,6 +16,9 @@ import { createSelector } from 'reselect'; import { Map as ImmutableMap } from 'immutable'; import ReactedHeaderContaier from '../reactioned/containers/header_container'; import { debounce } from 'lodash'; +import { defaultColumnWidth } from 'mastodon/initial_state'; +import { changeSetting } from '../../actions/settings'; +import { changeColumnParams } from '../../actions/columns'; const messages = defineMessages({ refresh: { id: 'refresh', defaultMessage: 'Refresh' }, @@ -23,12 +26,20 @@ const messages = defineMessages({ const customEmojiMap = createSelector([state => state.get('custom_emojis')], items => items.reduce((map, emoji) => map.set(emoji.get('shortcode'), emoji), ImmutableMap())); -const mapStateToProps = (state, props) => ({ - emojiReactions: state.getIn(['user_lists', 'emoji_reactioned_by', props.params.statusId, 'items']), - isLoading: state.getIn(['user_lists', 'emoji_reactioned_by', props.params.statusId, 'isLoading'], true), - hasMore: !!state.getIn(['user_lists', 'emoji_reactioned_by', props.params.statusId, 'next']), - emojiMap: customEmojiMap(state), -}); +const mapStateToProps = (state, { columnId, params }) => { + const uuid = columnId; + const columns = state.getIn(['settings', 'columns']); + const index = columns.findIndex(c => c.get('uuid') === uuid); + const columnWidth = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'columnWidth']) : state.getIn(['settings', 'emoji_reactions', 'columnWidth']); + + return { + emojiReactions: state.getIn(['user_lists', 'emoji_reactioned_by', params.statusId, 'items']), + isLoading: state.getIn(['user_lists', 'emoji_reactioned_by', params.statusId, 'isLoading'], true), + hasMore: !!state.getIn(['user_lists', 'emoji_reactioned_by', params.statusId, 'next']), + emojiMap: customEmojiMap(state), + columnWidth: columnWidth ?? defaultColumnWidth, + }; +}; class Reaction extends ImmutablePureComponent { @@ -65,6 +76,7 @@ class EmojiReactions extends ImmutablePureComponent { dispatch: PropTypes.func.isRequired, emojiReactions: ImmutablePropTypes.list, multiColumn: PropTypes.bool, + columnWidth: PropTypes.string, emojiMap: ImmutablePropTypes.map.isRequired, intl: PropTypes.object.isRequired, hasMore: PropTypes.bool, @@ -91,8 +103,18 @@ class EmojiReactions extends ImmutablePureComponent { this.props.dispatch(expandEmojiReactions(this.props.params.statusId)); }, 300, { leading: true }) + handleWidthChange = (value) => { + const { columnId, dispatch } = this.props; + + if (columnId) { + dispatch(changeColumnParams(columnId, 'columnWidth', value)); + } else { + dispatch(changeSetting(['emoji_reactions', 'columnWidth'], value)); + } + } + render () { - const { intl, emojiReactions, multiColumn, emojiMap, hasMore, isLoading } = this.props; + const { intl, emojiReactions, multiColumn, emojiMap, hasMore, isLoading, columnWidth } = this.props; if (!emojiReactions) { return ( @@ -105,10 +127,12 @@ class EmojiReactions extends ImmutablePureComponent { const emptyMessage = ; return ( - + )} diff --git a/app/javascript/mastodon/features/favourited_statuses/index.js b/app/javascript/mastodon/features/favourited_statuses/index.js index 73631946a..a638074ea 100644 --- a/app/javascript/mastodon/features/favourited_statuses/index.js +++ b/app/javascript/mastodon/features/favourited_statuses/index.js @@ -10,16 +10,27 @@ import StatusList from '../../components/status_list'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { debounce } from 'lodash'; +import { defaultColumnWidth } from 'mastodon/initial_state'; +import { changeSetting } from '../../actions/settings'; +import { changeColumnParams } from '../../actions/columns'; const messages = defineMessages({ heading: { id: 'column.favourites', defaultMessage: 'Favourites' }, }); -const mapStateToProps = state => ({ - statusIds: state.getIn(['status_lists', 'favourites', 'items']), - isLoading: state.getIn(['status_lists', 'favourites', 'isLoading'], true), - hasMore: !!state.getIn(['status_lists', 'favourites', 'next']), -}); +const mapStateToProps = (state, { columnId }) => { + const uuid = columnId; + const columns = state.getIn(['settings', 'columns']); + const index = columns.findIndex(c => c.get('uuid') === uuid); + const columnWidth = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'columnWidth']) : state.getIn(['settings', 'favourited_statuses', 'columnWidth']); + + return { + statusIds: state.getIn(['status_lists', 'favourites', 'items']), + isLoading: state.getIn(['status_lists', 'favourites', 'isLoading'], true), + hasMore: !!state.getIn(['status_lists', 'favourites', 'next']), + columnWidth: columnWidth ?? defaultColumnWidth, + }; +}; export default @connect(mapStateToProps) @injectIntl @@ -31,6 +42,7 @@ class Favourites extends ImmutablePureComponent { intl: PropTypes.object.isRequired, columnId: PropTypes.string, multiColumn: PropTypes.bool, + columnWidth: PropTypes.string, hasMore: PropTypes.bool, isLoading: PropTypes.bool, }; @@ -66,14 +78,24 @@ class Favourites extends ImmutablePureComponent { this.props.dispatch(expandFavouritedStatuses()); }, 300, { leading: true }) + handleWidthChange = (value) => { + const { columnId, dispatch } = this.props; + + if (columnId) { + dispatch(changeColumnParams(columnId, 'columnWidth', value)); + } else { + dispatch(changeSetting(['favourited_statuses', 'columnWidth'], value)); + } + } + render () { - const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props; + const { intl, statusIds, columnId, multiColumn, hasMore, isLoading, columnWidth } = this.props; const pinned = !!columnId; const emptyMessage = ; return ( - + diff --git a/app/javascript/mastodon/features/favourites/index.js b/app/javascript/mastodon/features/favourites/index.js index 45a35f5d8..976790269 100644 --- a/app/javascript/mastodon/features/favourites/index.js +++ b/app/javascript/mastodon/features/favourites/index.js @@ -13,16 +13,27 @@ import Icon from 'mastodon/components/icon'; import ColumnHeader from '../../components/column_header'; import ReactedHeaderContaier from '../reactioned/containers/header_container'; import { debounce } from 'lodash'; +import { defaultColumnWidth } from 'mastodon/initial_state'; +import { changeSetting } from '../../actions/settings'; +import { changeColumnParams } from '../../actions/columns'; const messages = defineMessages({ refresh: { id: 'refresh', defaultMessage: 'Refresh' }, }); -const mapStateToProps = (state, props) => ({ - accountIds: state.getIn(['user_lists', 'favourited_by', props.params.statusId, 'items']), - isLoading: state.getIn(['user_lists', 'favourited_by', props.params.statusId, 'isLoading'], true), - hasMore: !!state.getIn(['user_lists', 'favourited_by', props.params.statusId, 'next']), -}); +const mapStateToProps = (state, { columnId, params }) => { + const uuid = columnId; + const columns = state.getIn(['settings', 'columns']); + const index = columns.findIndex(c => c.get('uuid') === uuid); + const columnWidth = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'columnWidth']) : state.getIn(['settings', 'favourites', 'columnWidth']); + + return { + accountIds: state.getIn(['user_lists', 'favourited_by', params.statusId, 'items']), + isLoading: state.getIn(['user_lists', 'favourited_by', params.statusId, 'isLoading'], true), + hasMore: !!state.getIn(['user_lists', 'favourited_by', params.statusId, 'next']), + columnWidth: columnWidth ?? defaultColumnWidth, + }; +}; export default @connect(mapStateToProps) @injectIntl @@ -33,6 +44,7 @@ class Favourites extends ImmutablePureComponent { dispatch: PropTypes.func.isRequired, accountIds: ImmutablePropTypes.list, multiColumn: PropTypes.bool, + columnWidth: PropTypes.string, intl: PropTypes.object.isRequired, hasMore: PropTypes.bool, isLoading: PropTypes.bool, @@ -58,8 +70,18 @@ class Favourites extends ImmutablePureComponent { this.props.dispatch(expandFavourites(this.props.params.statusId)); }, 300, { leading: true }) + handleWidthChange = (value) => { + const { columnId, dispatch } = this.props; + + if (columnId) { + dispatch(changeColumnParams(columnId, 'columnWidth', value)); + } else { + dispatch(changeSetting(['favourites', 'columnWidth'], value)); + } + } + render () { - const { intl, accountIds, multiColumn, hasMore, isLoading } = this.props; + const { intl, accountIds, multiColumn, hasMore, isLoading, columnWidth } = this.props; if (!accountIds) { return ( @@ -72,10 +94,12 @@ class Favourites extends ImmutablePureComponent { const emptyMessage = ; return ( - + )} diff --git a/app/javascript/mastodon/features/follow_requests/index.js b/app/javascript/mastodon/features/follow_requests/index.js index 1f9b635bb..1e63c5216 100644 --- a/app/javascript/mastodon/features/follow_requests/index.js +++ b/app/javascript/mastodon/features/follow_requests/index.js @@ -11,7 +11,7 @@ import ColumnBackButtonSlim from '../../components/column_back_button_slim'; import AccountAuthorizeContainer from './containers/account_authorize_container'; import { fetchFollowRequests, expandFollowRequests } from '../../actions/accounts'; import ScrollableList from '../../components/scrollable_list'; -import { me } from '../../initial_state'; +import { me, defaultColumnWidth } from '../../initial_state'; const messages = defineMessages({ heading: { id: 'column.follow_requests', defaultMessage: 'Follow requests' }, @@ -23,6 +23,7 @@ const mapStateToProps = state => ({ hasMore: !!state.getIn(['user_lists', 'follow_requests', 'next']), locked: !!state.getIn(['accounts', me, 'locked']), domain: state.getIn(['meta', 'domain']), + columnWidth: defaultColumnWidth, }); export default @connect(mapStateToProps) @@ -39,6 +40,7 @@ class FollowRequests extends ImmutablePureComponent { domain: PropTypes.string, intl: PropTypes.object.isRequired, multiColumn: PropTypes.bool, + columnWidth: PropTypes.string, }; componentWillMount () { @@ -50,7 +52,7 @@ class FollowRequests extends ImmutablePureComponent { }, 300, { leading: true }); render () { - const { intl, accountIds, hasMore, multiColumn, locked, domain, isLoading } = this.props; + const { intl, accountIds, hasMore, multiColumn, locked, domain, isLoading, columnWidth } = this.props; if (!accountIds) { return ( @@ -72,7 +74,7 @@ class FollowRequests extends ImmutablePureComponent { ); return ( - + ({ isLoading: state.getIn(['user_lists', 'followers', props.params.accountId, 'isLoading'], true), blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false), advancedMode: state.getIn(['settings', 'account', 'other', 'advancedMode'], new_features_policy === 'conservative' ? false : true), + columnWidth: state.getIn(['settings', 'account', 'columnWidth'], defaultColumnWidth), }); const RemoteHint = ({ url }) => ( @@ -60,6 +62,7 @@ class Followers extends ImmutablePureComponent { remote: PropTypes.bool, remoteUrl: PropTypes.string, multiColumn: PropTypes.bool, + columnWidth: PropTypes.string, }; componentWillMount () { @@ -84,12 +87,16 @@ class Followers extends ImmutablePureComponent { this.column.scrollTop(); } + handleWidthChange = (value) => { + this.props.dispatch(changeSetting(['account', 'columnWidth'], value)); + } + setRef = c => { this.column = c; } render () { - const { accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, remote, remoteUrl, intl } = this.props; + const { accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, remote, remoteUrl, columnWidth, intl } = this.props; if (!isAccount) { return ( @@ -120,7 +127,7 @@ class Followers extends ImmutablePureComponent { const remoteMessage = remote ? : null; return ( - + diff --git a/app/javascript/mastodon/features/following/index.js b/app/javascript/mastodon/features/following/index.js index 2573365b5..022e56639 100644 --- a/app/javascript/mastodon/features/following/index.js +++ b/app/javascript/mastodon/features/following/index.js @@ -19,7 +19,8 @@ import HeaderContainer from '../account_timeline/containers/header_container'; import ScrollableList from '../../components/scrollable_list'; import MissingIndicator from 'mastodon/components/missing_indicator'; import TimelineHint from 'mastodon/components/timeline_hint'; -import { new_features_policy } from 'mastodon/initial_state'; +import { new_features_policy, defaultColumnWidth } from 'mastodon/initial_state'; +import { changeSetting } from '../../actions/settings'; const messages = defineMessages({ title: { id: 'column.account', defaultMessage: 'Account' }, @@ -34,6 +35,7 @@ const mapStateToProps = (state, props) => ({ isLoading: state.getIn(['user_lists', 'following', props.params.accountId, 'isLoading'], true), blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false), advancedMode: state.getIn(['settings', 'account', 'other', 'advancedMode'], new_features_policy === 'conservative' ? false : true), + columnWidth: state.getIn(['settings', 'account', 'columnWidth'], defaultColumnWidth), }); const RemoteHint = ({ url }) => ( @@ -60,6 +62,7 @@ class Following extends ImmutablePureComponent { remote: PropTypes.bool, remoteUrl: PropTypes.string, multiColumn: PropTypes.bool, + columnWidth: PropTypes.string, }; componentWillMount () { @@ -84,12 +87,16 @@ class Following extends ImmutablePureComponent { this.column.scrollTop(); } + handleWidthChange = (value) => { + this.props.dispatch(changeSetting(['account', 'columnWidth'], value)); + } + setRef = c => { this.column = c; } render () { - const { accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, remote, remoteUrl, intl } = this.props; + const { accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, remote, remoteUrl, columnWidth, intl } = this.props; if (!isAccount) { return ( @@ -120,7 +127,7 @@ class Following extends ImmutablePureComponent { const remoteMessage = remote ? : null; return ( - + diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js index a3afbe24c..1b26b1402 100644 --- a/app/javascript/mastodon/features/getting_started/index.js +++ b/app/javascript/mastodon/features/getting_started/index.js @@ -7,7 +7,7 @@ import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { me, profile_directory, showTrends, enable_limited_timeline, enableEmptyColumn } from '../../initial_state'; +import { me, profile_directory, showTrends, enable_limited_timeline, enableEmptyColumn, defaultColumnWidth } from '../../initial_state'; import { fetchFollowRequests } from 'mastodon/actions/accounts'; import { fetchFavouriteDomains } from 'mastodon/actions/favourite_domains'; import { fetchFavouriteTags } from 'mastodon/actions/favourite_tags'; @@ -61,6 +61,7 @@ const mapStateToProps = state => ({ lists: getOrderedLists(state), favourite_domains: getOrderedDomains(state), favourite_tags: getOrderedTags(state), + columnWidth: defaultColumnWidth, }); const mapDispatchToProps = dispatch => ({ @@ -94,6 +95,7 @@ class GettingStarted extends ImmutablePureComponent { myAccount: ImmutablePropTypes.map.isRequired, columns: ImmutablePropTypes.list, multiColumn: PropTypes.bool, + columnWidth: PropTypes.string, fetchFollowRequests: PropTypes.func.isRequired, fetchFavouriteDomains: PropTypes.func.isRequired, fetchFavouriteTags: PropTypes.func.isRequired, @@ -118,7 +120,7 @@ class GettingStarted extends ImmutablePureComponent { } render () { - const { intl, myAccount, columns, multiColumn, unreadFollowRequests, lists, favourite_domains, favourite_tags } = this.props; + const { intl, myAccount, columns, multiColumn, unreadFollowRequests, lists, favourite_domains, favourite_tags, columnWidth } = this.props; const navItems = []; let height = (multiColumn) ? 0 : 60; @@ -264,7 +266,7 @@ class GettingStarted extends ImmutablePureComponent { } return ( - + {multiColumn &&