From d61a6271c68ecca1745f2683d25ec58573dd2819 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Sun, 9 Jun 2019 12:07:23 +0200 Subject: Add DM conversations mode similar to upstream --- .../flavours/glitch/actions/conversations.js | 84 +++++++++++++++++ .../flavours/glitch/actions/streaming.js | 4 + .../flavours/glitch/components/avatar_composite.js | 104 +++++++++++++++++++++ .../flavours/glitch/components/display_name.js | 44 ++++++++- .../flavours/glitch/components/status.js | 23 ++++- .../glitch/components/status_action_bar.js | 20 ++-- .../flavours/glitch/components/status_header.js | 82 ++++++++++------ .../flavours/glitch/components/status_icons.js | 6 +- .../direct_timeline/components/conversation.js | 64 +++++++++++++ .../components/conversations_list.js | 73 +++++++++++++++ .../containers/conversation_container.js | 19 ++++ .../containers/conversations_list_container.js | 15 +++ .../glitch/features/direct_timeline/index.js | 96 ++++++++++++++++--- .../flavours/glitch/reducers/conversations.js | 102 ++++++++++++++++++++ app/javascript/flavours/glitch/reducers/index.js | 2 + .../flavours/glitch/reducers/settings.js | 1 + .../glitch/styles/components/accounts.scss | 12 +++ .../flavours/glitch/styles/components/index.scss | 8 +- .../flavours/glitch/styles/components/status.scss | 9 +- .../glitch/styles/mastodon-light/diff.scss | 5 +- 20 files changed, 704 insertions(+), 69 deletions(-) create mode 100644 app/javascript/flavours/glitch/actions/conversations.js create mode 100644 app/javascript/flavours/glitch/components/avatar_composite.js create mode 100644 app/javascript/flavours/glitch/features/direct_timeline/components/conversation.js create mode 100644 app/javascript/flavours/glitch/features/direct_timeline/components/conversations_list.js create mode 100644 app/javascript/flavours/glitch/features/direct_timeline/containers/conversation_container.js create mode 100644 app/javascript/flavours/glitch/features/direct_timeline/containers/conversations_list_container.js create mode 100644 app/javascript/flavours/glitch/reducers/conversations.js (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/actions/conversations.js b/app/javascript/flavours/glitch/actions/conversations.js new file mode 100644 index 000000000..856f8f10f --- /dev/null +++ b/app/javascript/flavours/glitch/actions/conversations.js @@ -0,0 +1,84 @@ +import api, { getLinks } from 'flavours/glitch/util/api'; +import { + importFetchedAccounts, + importFetchedStatuses, + importFetchedStatus, +} from './importer'; + +export const CONVERSATIONS_MOUNT = 'CONVERSATIONS_MOUNT'; +export const CONVERSATIONS_UNMOUNT = 'CONVERSATIONS_UNMOUNT'; + +export const CONVERSATIONS_FETCH_REQUEST = 'CONVERSATIONS_FETCH_REQUEST'; +export const CONVERSATIONS_FETCH_SUCCESS = 'CONVERSATIONS_FETCH_SUCCESS'; +export const CONVERSATIONS_FETCH_FAIL = 'CONVERSATIONS_FETCH_FAIL'; +export const CONVERSATIONS_UPDATE = 'CONVERSATIONS_UPDATE'; + +export const CONVERSATIONS_READ = 'CONVERSATIONS_READ'; + +export const mountConversations = () => ({ + type: CONVERSATIONS_MOUNT, +}); + +export const unmountConversations = () => ({ + type: CONVERSATIONS_UNMOUNT, +}); + +export const markConversationRead = conversationId => (dispatch, getState) => { + dispatch({ + type: CONVERSATIONS_READ, + id: conversationId, + }); + + api(getState).post(`/api/v1/conversations/${conversationId}/read`); +}; + +export const expandConversations = ({ maxId } = {}) => (dispatch, getState) => { + dispatch(expandConversationsRequest()); + + const params = { max_id: maxId }; + + if (!maxId) { + params.since_id = getState().getIn(['conversations', 'items', 0, 'last_status']); + } + + const isLoadingRecent = !!params.since_id; + + api(getState).get('/api/v1/conversations', { params }) + .then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + + dispatch(importFetchedAccounts(response.data.reduce((aggr, item) => aggr.concat(item.accounts), []))); + dispatch(importFetchedStatuses(response.data.map(item => item.last_status).filter(x => !!x))); + dispatch(expandConversationsSuccess(response.data, next ? next.uri : null, isLoadingRecent)); + }) + .catch(err => dispatch(expandConversationsFail(err))); +}; + +export const expandConversationsRequest = () => ({ + type: CONVERSATIONS_FETCH_REQUEST, +}); + +export const expandConversationsSuccess = (conversations, next, isLoadingRecent) => ({ + type: CONVERSATIONS_FETCH_SUCCESS, + conversations, + next, + isLoadingRecent, +}); + +export const expandConversationsFail = error => ({ + type: CONVERSATIONS_FETCH_FAIL, + error, +}); + +export const updateConversations = conversation => dispatch => { + dispatch(importFetchedAccounts(conversation.accounts)); + + if (conversation.last_status) { + dispatch(importFetchedStatus(conversation.last_status)); + } + + dispatch({ + type: CONVERSATIONS_UPDATE, + conversation, + }); +}; diff --git a/app/javascript/flavours/glitch/actions/streaming.js b/app/javascript/flavours/glitch/actions/streaming.js index b5dd70989..21379f492 100644 --- a/app/javascript/flavours/glitch/actions/streaming.js +++ b/app/javascript/flavours/glitch/actions/streaming.js @@ -7,6 +7,7 @@ import { disconnectTimeline, } from './timelines'; import { updateNotifications, expandNotifications } from './notifications'; +import { updateConversations } from './conversations'; import { fetchFilters } from './filters'; import { getLocale } from 'mastodon/locales'; @@ -37,6 +38,9 @@ export function connectTimelineStream (timelineId, path, pollingRefresh = null, case 'notification': dispatch(updateNotifications(JSON.parse(data.payload), messages, locale)); break; + case 'conversation': + dispatch(updateConversations(JSON.parse(data.payload))); + break; case 'filters_changed': dispatch(fetchFilters()); break; diff --git a/app/javascript/flavours/glitch/components/avatar_composite.js b/app/javascript/flavours/glitch/components/avatar_composite.js new file mode 100644 index 000000000..c52df043a --- /dev/null +++ b/app/javascript/flavours/glitch/components/avatar_composite.js @@ -0,0 +1,104 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { autoPlayGif } from 'flavours/glitch/util/initial_state'; + +export default class AvatarComposite extends React.PureComponent { + + static propTypes = { + accounts: ImmutablePropTypes.list.isRequired, + animate: PropTypes.bool, + size: PropTypes.number.isRequired, + }; + + static defaultProps = { + animate: autoPlayGif, + }; + + renderItem (account, size, index) { + const { animate } = this.props; + + let width = 50; + let height = 100; + let top = 'auto'; + let left = 'auto'; + let bottom = 'auto'; + let right = 'auto'; + + if (size === 1) { + width = 100; + } + + if (size === 4 || (size === 3 && index > 0)) { + height = 50; + } + + if (size === 2) { + if (index === 0) { + right = '2px'; + } else { + left = '2px'; + } + } else if (size === 3) { + if (index === 0) { + right = '2px'; + } else if (index > 0) { + left = '2px'; + } + + if (index === 1) { + bottom = '2px'; + } else if (index > 1) { + top = '2px'; + } + } else if (size === 4) { + if (index === 0 || index === 2) { + right = '2px'; + } + + if (index === 1 || index === 3) { + left = '2px'; + } + + if (index < 2) { + bottom = '2px'; + } else { + top = '2px'; + } + } + + const style = { + left: left, + top: top, + right: right, + bottom: bottom, + width: `${width}%`, + height: `${height}%`, + backgroundSize: 'cover', + backgroundImage: `url(${account.get(animate ? 'avatar' : 'avatar_static')})`, + }; + + return ( + this.props.onAccountClick(account.get('id'), e)} + title={`@${account.get('acct')}`} + key={account.get('id')} + > +
+ + ); + } + + render() { + const { accounts, size } = this.props; + + return ( +
+ {accounts.take(4).map((account, i) => this.renderItem(account, accounts.size, i))} +
+ ); + } + +} diff --git a/app/javascript/flavours/glitch/components/display_name.js b/app/javascript/flavours/glitch/components/display_name.js index a26cff049..7f6ef5a5d 100644 --- a/app/javascript/flavours/glitch/components/display_name.js +++ b/app/javascript/flavours/glitch/components/display_name.js @@ -10,24 +10,56 @@ export default function DisplayName ({ className, inline, localDomain, + others, + onAccountClick, }) { const computedClass = classNames('display-name', { inline }, className); if (!account) return null; + let displayName, suffix; + let acct = account.get('acct'); + if (acct.indexOf('@') === -1 && localDomain) { acct = `${acct}@${localDomain}`; } - // The result. - return account ? ( + if (others && others.size > 0) { + displayName = others.take(2).map(a => ( + onAccountClick(a.get('id'), e)} + title={`@${a.get('acct')}`} + > + + + + + )).reduce((prev, cur) => [prev, ', ', cur]); + + if (others.size - 2 > 0) { + displayName.push(` +${others.size - 2}`); + } + + suffix = ( + onAccountClick(account.get('id'), e)}> + @{acct} + + ); + } else { + displayName = ; + suffix = @{acct}; + } + + return ( - + {displayName} {inline ? ' ' : null} - @{acct} + {suffix} - ) : null; + ); } // Props. @@ -36,4 +68,6 @@ DisplayName.propTypes = { className: PropTypes.string, inline: PropTypes.bool, localDomain: PropTypes.string, + others: ImmutablePropTypes.list, + handleClick: PropTypes.func, }; diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js index 7014cab17..4b9364ae5 100644 --- a/app/javascript/flavours/glitch/components/status.js +++ b/app/javascript/flavours/glitch/components/status.js @@ -66,6 +66,7 @@ export default class Status extends ImmutablePureComponent { containerId: PropTypes.string, id: PropTypes.string, status: ImmutablePropTypes.map, + otherAccounts: ImmutablePropTypes.list, account: ImmutablePropTypes.map, onReply: PropTypes.func, onFavourite: PropTypes.func, @@ -83,6 +84,7 @@ export default class Status extends ImmutablePureComponent { muted: PropTypes.bool, collapse: PropTypes.bool, hidden: PropTypes.bool, + unread: PropTypes.bool, prepend: PropTypes.string, withDismiss: PropTypes.bool, onMoveUp: PropTypes.func, @@ -93,6 +95,7 @@ export default class Status extends ImmutablePureComponent { intl: PropTypes.object.isRequired, cacheMediaWidth: PropTypes.func, cachedMediaWidth: PropTypes.number, + onClick: PropTypes.func, }; state = { @@ -321,17 +324,21 @@ export default class Status extends ImmutablePureComponent { const { status } = this.props; const { isCollapsed } = this.state; if (!router) return; - if (destination === undefined) { - destination = `/statuses/${ - status.getIn(['reblog', 'id'], status.get('id')) - }`; - } + if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey)) { if (isCollapsed) this.setCollapsed(false); else if (e.shiftKey) { this.setCollapsed(true); document.getSelection().removeAllRanges(); + } else if (this.props.onClick) { + this.props.onClick(); + return; } else { + if (destination === undefined) { + destination = `/statuses/${ + status.getIn(['reblog', 'id'], status.get('id')) + }`; + } let state = {...router.history.location.state}; state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1; router.history.push(destination, state); @@ -441,6 +448,7 @@ export default class Status extends ImmutablePureComponent { intl, status, account, + otherAccounts, settings, collapsed, muted, @@ -450,6 +458,7 @@ export default class Status extends ImmutablePureComponent { onOpenMedia, notification, hidden, + unread, featured, ...other } = this.props; @@ -617,6 +626,7 @@ export default class Status extends ImmutablePureComponent { collapsed: isCollapsed, 'has-background': isCollapsed && background, 'status__wrapper-reply': !!status.get('in_reply_to_id'), + read: unread === false, muted, }, 'focusable'); @@ -647,6 +657,7 @@ export default class Status extends ImmutablePureComponent { friend={account} collapsed={isCollapsed} parseClick={parseClick} + otherAccounts={otherAccounts} /> ) : null} @@ -656,6 +667,7 @@ export default class Status extends ImmutablePureComponent { collapsible={settings.getIn(['collapsed', 'enabled'])} collapsed={isCollapsed} setCollapsed={setCollapsed} + directMessage={!!otherAccounts} /> ) : null} {notification ? ( diff --git a/app/javascript/flavours/glitch/components/status_action_bar.js b/app/javascript/flavours/glitch/components/status_action_bar.js index 4c398fd19..85bc4a976 100644 --- a/app/javascript/flavours/glitch/components/status_action_bar.js +++ b/app/javascript/flavours/glitch/components/status_action_bar.js @@ -71,6 +71,7 @@ export default class StatusActionBar extends ImmutablePureComponent { onBookmark: PropTypes.func, withDismiss: PropTypes.bool, showReplyCount: PropTypes.bool, + directMessage: PropTypes.bool, intl: PropTypes.object.isRequired, }; @@ -191,7 +192,7 @@ export default class StatusActionBar extends ImmutablePureComponent { } render () { - const { status, intl, withDismiss, showReplyCount } = this.props; + const { status, intl, withDismiss, showReplyCount, directMessage } = this.props; const mutingConversation = status.get('muted'); const anonymousAccess = !me; @@ -282,14 +283,15 @@ export default class StatusActionBar extends ImmutablePureComponent { return (
{replyButton} - - - {shareButton} - - -
- -
+ {!directMessage && [ + , + , + shareButton, + , +
+ +
, + ]}
diff --git a/app/javascript/flavours/glitch/components/status_header.js b/app/javascript/flavours/glitch/components/status_header.js index f9321904c..23cff286a 100644 --- a/app/javascript/flavours/glitch/components/status_header.js +++ b/app/javascript/flavours/glitch/components/status_header.js @@ -6,6 +6,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; // Mastodon imports. import Avatar from './avatar'; import AvatarOverlay from './avatar_overlay'; +import AvatarComposite from './avatar_composite'; import DisplayName from './display_name'; export default class StatusHeader extends React.PureComponent { @@ -14,12 +15,18 @@ export default class StatusHeader extends React.PureComponent { status: ImmutablePropTypes.map.isRequired, friend: ImmutablePropTypes.map, parseClick: PropTypes.func.isRequired, + otherAccounts: ImmutablePropTypes.list, }; // Handles clicks on account name/image + handleClick = (id, e) => { + const { parseClick } = this.props; + parseClick(e, `/accounts/${id}`); + } + handleAccountClick = (e) => { - const { status, parseClick } = this.props; - parseClick(e, `/accounts/${status.getIn(['account', 'id'])}`); + const { status } = this.props; + this.handleClick(status.getIn(['account', 'id']), e); } // Rendering. @@ -27,36 +34,55 @@ export default class StatusHeader extends React.PureComponent { const { status, friend, + otherAccounts, } = this.props; const account = status.get('account'); - return ( - - ); + let statusAvatar; + if (otherAccounts && otherAccounts.size > 0) { + statusAvatar = ; + } else if (friend === undefined || friend === null) { + statusAvatar = ; + } else { + statusAvatar = ; + } + + if (!otherAccounts) { + return ( + + ); + } else { + // This is a DM conversation + return ( +
+ + {statusAvatar} + + + + + +
+ ); + } } } diff --git a/app/javascript/flavours/glitch/components/status_icons.js b/app/javascript/flavours/glitch/components/status_icons.js index c9747650f..4a2c62881 100644 --- a/app/javascript/flavours/glitch/components/status_icons.js +++ b/app/javascript/flavours/glitch/components/status_icons.js @@ -22,6 +22,7 @@ export default class StatusIcons extends React.PureComponent { mediaIcon: PropTypes.string, collapsible: PropTypes.bool, collapsed: PropTypes.bool, + directMessage: PropTypes.bool, setCollapsed: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, }; @@ -42,6 +43,7 @@ export default class StatusIcons extends React.PureComponent { mediaIcon, collapsible, collapsed, + directMessage, intl, } = this.props; @@ -59,9 +61,7 @@ export default class StatusIcons extends React.PureComponent { aria-hidden='true' /> ) : null} - {( - - )} + {!directMessage && } {collapsible ? ( { + if (!this.context.router) { + return; + } + + const { lastStatusId, unread, markRead } = this.props; + + if (unread) { + markRead(); + } + + this.context.router.history.push(`/statuses/${lastStatusId}`); + } + + handleHotkeyMoveUp = () => { + this.props.onMoveUp(this.props.conversationId); + } + + handleHotkeyMoveDown = () => { + this.props.onMoveDown(this.props.conversationId); + } + + render () { + const { accounts, lastStatusId, unread } = this.props; + + if (lastStatusId === null) { + return null; + } + + return ( + + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/direct_timeline/components/conversations_list.js b/app/javascript/flavours/glitch/features/direct_timeline/components/conversations_list.js new file mode 100644 index 000000000..4fa76fd6d --- /dev/null +++ b/app/javascript/flavours/glitch/features/direct_timeline/components/conversations_list.js @@ -0,0 +1,73 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import ConversationContainer from '../containers/conversation_container'; +import ScrollableList from 'flavours/glitch/components/scrollable_list'; +import { debounce } from 'lodash'; + +export default class ConversationsList extends ImmutablePureComponent { + + static propTypes = { + conversations: ImmutablePropTypes.list.isRequired, + hasMore: PropTypes.bool, + isLoading: PropTypes.bool, + onLoadMore: PropTypes.func, + }; + + getCurrentIndex = id => this.props.conversations.findIndex(x => x.get('id') === id) + + handleMoveUp = id => { + const elementIndex = this.getCurrentIndex(id) - 1; + this._selectChild(elementIndex, true); + } + + handleMoveDown = id => { + const elementIndex = this.getCurrentIndex(id) + 1; + this._selectChild(elementIndex, false); + } + + _selectChild (index, align_top) { + const container = this.node.node; + const element = container.querySelector(`article:nth-of-type(${index + 1}) .focusable`); + + if (element) { + if (align_top && container.scrollTop > element.offsetTop) { + element.scrollIntoView(true); + } else if (!align_top && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) { + element.scrollIntoView(false); + } + element.focus(); + } + } + + setRef = c => { + this.node = c; + } + + handleLoadOlder = debounce(() => { + const last = this.props.conversations.last(); + + if (last && last.get('last_status')) { + this.props.onLoadMore(last.get('last_status')); + } + }, 300, { leading: true }) + + render () { + const { conversations, onLoadMore, ...other } = this.props; + + return ( + + {conversations.map(item => ( + + ))} + + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/direct_timeline/containers/conversation_container.js b/app/javascript/flavours/glitch/features/direct_timeline/containers/conversation_container.js new file mode 100644 index 000000000..bd6f6bfb0 --- /dev/null +++ b/app/javascript/flavours/glitch/features/direct_timeline/containers/conversation_container.js @@ -0,0 +1,19 @@ +import { connect } from 'react-redux'; +import Conversation from '../components/conversation'; +import { markConversationRead } from '../../../actions/conversations'; + +const mapStateToProps = (state, { conversationId }) => { + const conversation = state.getIn(['conversations', 'items']).find(x => x.get('id') === conversationId); + + return { + accounts: conversation.get('accounts').map(accountId => state.getIn(['accounts', accountId], null)), + unread: conversation.get('unread'), + lastStatusId: conversation.get('last_status', null), + }; +}; + +const mapDispatchToProps = (dispatch, { conversationId }) => ({ + markRead: () => dispatch(markConversationRead(conversationId)), +}); + +export default connect(mapStateToProps, mapDispatchToProps)(Conversation); diff --git a/app/javascript/flavours/glitch/features/direct_timeline/containers/conversations_list_container.js b/app/javascript/flavours/glitch/features/direct_timeline/containers/conversations_list_container.js new file mode 100644 index 000000000..e10558f3a --- /dev/null +++ b/app/javascript/flavours/glitch/features/direct_timeline/containers/conversations_list_container.js @@ -0,0 +1,15 @@ +import { connect } from 'react-redux'; +import ConversationsList from '../components/conversations_list'; +import { expandConversations } from 'flavours/glitch/actions/conversations'; + +const mapStateToProps = state => ({ + conversations: state.getIn(['conversations', 'items']), + isLoading: state.getIn(['conversations', 'isLoading'], true), + hasMore: state.getIn(['conversations', 'hasMore'], false), +}); + +const mapDispatchToProps = dispatch => ({ + onLoadMore: maxId => dispatch(expandConversations({ maxId })), +}); + +export default connect(mapStateToProps, mapDispatchToProps)(ConversationsList); diff --git a/app/javascript/flavours/glitch/features/direct_timeline/index.js b/app/javascript/flavours/glitch/features/direct_timeline/index.js index dc7e0534d..6fe8a1ce8 100644 --- a/app/javascript/flavours/glitch/features/direct_timeline/index.js +++ b/app/javascript/flavours/glitch/features/direct_timeline/index.js @@ -5,10 +5,13 @@ import StatusListContainer from 'flavours/glitch/features/ui/containers/status_l import Column from 'flavours/glitch/components/column'; import ColumnHeader from 'flavours/glitch/components/column_header'; import { expandDirectTimeline } from 'flavours/glitch/actions/timelines'; +import { mountConversations, unmountConversations, expandConversations } from 'flavours/glitch/actions/conversations'; import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ColumnSettingsContainer from './containers/column_settings_container'; import { connectDirectStream } from 'flavours/glitch/actions/streaming'; +import { changeSetting } from 'flavours/glitch/actions/settings'; +import ConversationsListContainer from './containers/conversations_list_container'; const messages = defineMessages({ title: { id: 'column.direct', defaultMessage: 'Direct messages' }, @@ -16,6 +19,7 @@ const messages = defineMessages({ const mapStateToProps = state => ({ hasUnread: state.getIn(['timelines', 'direct', 'unread']) > 0, + conversationsMode: state.getIn(['settings', 'direct', 'conversations']), }); @connect(mapStateToProps) @@ -28,6 +32,7 @@ export default class DirectTimeline extends React.PureComponent { intl: PropTypes.object.isRequired, hasUnread: PropTypes.bool, multiColumn: PropTypes.bool, + conversationsMode: PropTypes.bool, }; handlePin = () => { @@ -50,13 +55,32 @@ export default class DirectTimeline extends React.PureComponent { } componentDidMount () { - const { dispatch } = this.props; + const { dispatch, conversationsMode } = this.props; + + dispatch(mountConversations()); + + if (conversationsMode) { + dispatch(expandConversations()); + } else { + dispatch(expandDirectTimeline()); + } - dispatch(expandDirectTimeline()); this.disconnect = dispatch(connectDirectStream()); } + componentDidUpdate(prevProps) { + const { dispatch, conversationsMode } = this.props; + + if (prevProps.conversationsMode && !conversationsMode) { + dispatch(expandDirectTimeline()); + } else if (!prevProps.conversationsMode && conversationsMode) { + dispatch(expandConversations()); + } + } + componentWillUnmount () { + this.props.dispatch(unmountConversations()); + if (this.disconnect) { this.disconnect(); this.disconnect = null; @@ -67,14 +91,49 @@ export default class DirectTimeline extends React.PureComponent { this.column = c; } - handleLoadMore = maxId => { + handleLoadMoreTimeline = maxId => { this.props.dispatch(expandDirectTimeline({ maxId })); } + handleLoadMoreConversations = maxId => { + this.props.dispatch(expandConversations({ maxId })); + } + + handleTimelineClick = () => { + this.props.dispatch(changeSetting(['direct', 'conversations'], false)); + } + + handleConversationsClick = () => { + this.props.dispatch(changeSetting(['direct', 'conversations'], true)); + } + render () { - const { intl, hasUnread, columnId, multiColumn } = this.props; + const { intl, hasUnread, columnId, multiColumn, conversationsMode } = this.props; const pinned = !!columnId; + let contents; + if (conversationsMode) { + contents = ( + } + /> + ); + } else { + contents = ( + } + /> + ); + } + return ( - } - /> +
+ + +
+ + {contents}
); } diff --git a/app/javascript/flavours/glitch/reducers/conversations.js b/app/javascript/flavours/glitch/reducers/conversations.js new file mode 100644 index 000000000..c01659da5 --- /dev/null +++ b/app/javascript/flavours/glitch/reducers/conversations.js @@ -0,0 +1,102 @@ +import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; +import { + CONVERSATIONS_MOUNT, + CONVERSATIONS_UNMOUNT, + CONVERSATIONS_FETCH_REQUEST, + CONVERSATIONS_FETCH_SUCCESS, + CONVERSATIONS_FETCH_FAIL, + CONVERSATIONS_UPDATE, + CONVERSATIONS_READ, +} from '../actions/conversations'; +import compareId from 'flavours/glitch/util/compare_id'; + +const initialState = ImmutableMap({ + items: ImmutableList(), + isLoading: false, + hasMore: true, + mounted: 0, +}); + +const conversationToMap = item => ImmutableMap({ + id: item.id, + unread: item.unread, + accounts: ImmutableList(item.accounts.map(a => a.id)), + last_status: item.last_status ? item.last_status.id : null, +}); + +const updateConversation = (state, item) => state.update('items', list => { + const index = list.findIndex(x => x.get('id') === item.id); + const newItem = conversationToMap(item); + + if (index === -1) { + return list.unshift(newItem); + } else { + return list.set(index, newItem); + } +}); + +const expandNormalizedConversations = (state, conversations, next, isLoadingRecent) => { + let items = ImmutableList(conversations.map(conversationToMap)); + + return state.withMutations(mutable => { + if (!items.isEmpty()) { + mutable.update('items', list => { + list = list.map(oldItem => { + const newItemIndex = items.findIndex(x => x.get('id') === oldItem.get('id')); + + if (newItemIndex === -1) { + return oldItem; + } + + const newItem = items.get(newItemIndex); + items = items.delete(newItemIndex); + + return newItem; + }); + + list = list.concat(items); + + return list.sortBy(x => x.get('last_status'), (a, b) => { + if(a === null || b === null) { + return -1; + } + + return compareId(a, b) * -1; + }); + }); + } + + if (!next && !isLoadingRecent) { + mutable.set('hasMore', false); + } + + mutable.set('isLoading', false); + }); +}; + +export default function conversations(state = initialState, action) { + switch (action.type) { + case CONVERSATIONS_FETCH_REQUEST: + return state.set('isLoading', true); + case CONVERSATIONS_FETCH_FAIL: + return state.set('isLoading', false); + case CONVERSATIONS_FETCH_SUCCESS: + return expandNormalizedConversations(state, action.conversations, action.next, action.isLoadingRecent); + case CONVERSATIONS_UPDATE: + return updateConversation(state, action.conversation); + case CONVERSATIONS_MOUNT: + return state.update('mounted', count => count + 1); + case CONVERSATIONS_UNMOUNT: + return state.update('mounted', count => count - 1); + case CONVERSATIONS_READ: + return state.update('items', list => list.map(item => { + if (item.get('id') === action.id) { + return item.set('unread', false); + } + + return item; + })); + default: + return state; + } +}; diff --git a/app/javascript/flavours/glitch/reducers/index.js b/app/javascript/flavours/glitch/reducers/index.js index 45b93b92c..266d87dc1 100644 --- a/app/javascript/flavours/glitch/reducers/index.js +++ b/app/javascript/flavours/glitch/reducers/index.js @@ -28,6 +28,7 @@ import lists from './lists'; import listEditor from './list_editor'; import listAdder from './list_adder'; import filters from './filters'; +import conversations from './conversations'; import suggestions from './suggestions'; import pinnedAccountsEditor from './pinned_accounts_editor'; import polls from './polls'; @@ -64,6 +65,7 @@ const reducers = { listEditor, listAdder, filters, + conversations, suggestions, pinnedAccountsEditor, polls, diff --git a/app/javascript/flavours/glitch/reducers/settings.js b/app/javascript/flavours/glitch/reducers/settings.js index cc86a6b20..a37863a69 100644 --- a/app/javascript/flavours/glitch/reducers/settings.js +++ b/app/javascript/flavours/glitch/reducers/settings.js @@ -72,6 +72,7 @@ const initialState = ImmutableMap({ }), direct: ImmutableMap({ + conversations: true, regex: ImmutableMap({ body: '', }), diff --git a/app/javascript/flavours/glitch/styles/components/accounts.scss b/app/javascript/flavours/glitch/styles/components/accounts.scss index c0340e3f8..d2233207d 100644 --- a/app/javascript/flavours/glitch/styles/components/accounts.scss +++ b/app/javascript/flavours/glitch/styles/components/accounts.scss @@ -46,6 +46,18 @@ vertical-align: middle; margin-right: 5px; } + + &-composite { + @include avatar-radius; + overflow: hidden; + + & div { + @include avatar-radius; + float: left; + position: relative; + box-sizing: border-box; + } + } } .account__avatar-overlay { diff --git a/app/javascript/flavours/glitch/styles/components/index.scss b/app/javascript/flavours/glitch/styles/components/index.scss index 63211392e..9db64bbcb 100644 --- a/app/javascript/flavours/glitch/styles/components/index.scss +++ b/app/javascript/flavours/glitch/styles/components/index.scss @@ -287,8 +287,12 @@ text-overflow: ellipsis; white-space: nowrap; + a { + color: inherit; + text-decoration: inherit; + } + strong { - display: block; height: 18px; font-size: 16px; font-weight: 500; @@ -308,7 +312,7 @@ white-space: nowrap; } - &:hover { + > a:hover { strong { text-decoration: underline; } diff --git a/app/javascript/flavours/glitch/styles/components/status.scss b/app/javascript/flavours/glitch/styles/components/status.scss index 327694a7e..ee4440e89 100644 --- a/app/javascript/flavours/glitch/styles/components/status.scss +++ b/app/javascript/flavours/glitch/styles/components/status.scss @@ -209,7 +209,7 @@ outline: 0; background: lighten($ui-base-color, 4%); - .status.status-direct { + &.status.status-direct:not(.read) { background: lighten($ui-base-color, 12%); &.muted { @@ -249,8 +249,9 @@ margin-top: 8px; } - &.status-direct { + &.status-direct:not(.read) { background: lighten($ui-base-color, 8%); + border-bottom-color: lighten($ui-base-color, 12%); } &.light { @@ -333,7 +334,7 @@ &:focus > .status__content:after { background: linear-gradient(rgba(lighten($ui-base-color, 4%), 0), rgba(lighten($ui-base-color, 4%), 1)); } - &.status-direct> .status__content:after { + &.status-direct:not(.read)> .status__content:after { background: linear-gradient(rgba(lighten($ui-base-color, 8%), 0), rgba(lighten($ui-base-color, 8%), 1)); } @@ -599,7 +600,7 @@ } } -.status__display-name, +a.status__display-name, .reply-indicator__display-name, .detailed-status__display-name, .account__display-name { diff --git a/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss b/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss index ce2a2eeb5..3e4a15c9f 100644 --- a/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss +++ b/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss @@ -27,15 +27,16 @@ } } -.status.status-direct { +.status.status-direct:not(.read) { background: darken($ui-base-color, 8%); + border-bottom-color: darken($ui-base-color, 12%); &.collapsed> .status__content:after { background: linear-gradient(rgba(darken($ui-base-color, 8%), 0), rgba(darken($ui-base-color, 8%), 1)); } } -.focusable:focus.status.status-direct { +.focusable:focus.status.status-direct:not(.read) { background: darken($ui-base-color, 4%); &.collapsed> .status__content:after { -- cgit From ccfb48d3eb354e5cdce24dc975ea8a3fb2a1c80e Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Fri, 7 Jun 2019 18:38:07 +0200 Subject: Add option to display a warning before boosting toots lacking media descriptions --- .../flavours/glitch/containers/status_container.js | 15 ++++++++++----- .../flavours/glitch/features/local_settings/page/index.js | 8 ++++++++ app/javascript/flavours/glitch/features/status/index.js | 10 +++++++--- .../flavours/glitch/features/ui/components/boost_modal.js | 11 +++++++++-- app/javascript/flavours/glitch/reducers/local_settings.js | 1 + 5 files changed, 35 insertions(+), 10 deletions(-) (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/containers/status_container.js b/app/javascript/flavours/glitch/containers/status_container.js index 98dc5bb87..a6069cb90 100644 --- a/app/javascript/flavours/glitch/containers/status_container.js +++ b/app/javascript/flavours/glitch/containers/status_container.js @@ -96,11 +96,16 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ }, onReblog (status, e) { - if (e.shiftKey || !boostModal) { - this.onModalReblog(status); - } else { - dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog })); - } + dispatch((_, getState) => { + let state = getState(); + if (state.getIn(['local_settings', 'confirm_boost_missing_media_description']) && status.get('media_attachments').some(item => !item.get('description')) && !status.get('reblogged')) { + dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog, missingMediaDescription: true })); + } else if (e.shiftKey || !boostModal) { + this.onModalReblog(status); + } else { + dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog })); + } + }); }, onBookmark (status) { diff --git a/app/javascript/flavours/glitch/features/local_settings/page/index.js b/app/javascript/flavours/glitch/features/local_settings/page/index.js index cd2d86713..8f5fc5401 100644 --- a/app/javascript/flavours/glitch/features/local_settings/page/index.js +++ b/app/javascript/flavours/glitch/features/local_settings/page/index.js @@ -51,6 +51,14 @@ export default class LocalSettingsPage extends React.PureComponent { + + +

{ + const { settings, dispatch } = this.props; + if (status.get('reblogged')) { - this.props.dispatch(unreblog(status)); + dispatch(unreblog(status)); } else { - if ((e && e.shiftKey) || !boostModal) { + if (settings.get('confirm_boost_missing_media_description') && status.get('media_attachments').some(item => !item.get('description'))) { + dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog, missingMediaDescription: true })); + } else if ((e && e.shiftKey) || !boostModal) { this.handleModalReblog(status); } else { - this.props.dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog })); + dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog })); } } } diff --git a/app/javascript/flavours/glitch/features/ui/components/boost_modal.js b/app/javascript/flavours/glitch/features/ui/components/boost_modal.js index ce7ec2479..81bd1e576 100644 --- a/app/javascript/flavours/glitch/features/ui/components/boost_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/boost_modal.js @@ -25,6 +25,7 @@ export default class BoostModal extends ImmutablePureComponent { status: ImmutablePropTypes.map.isRequired, onReblog: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired, + missingMediaDescription: PropTypes.bool, intl: PropTypes.object.isRequired, }; @@ -52,7 +53,7 @@ export default class BoostModal extends ImmutablePureComponent { } render () { - const { status, intl } = this.props; + const { status, missingMediaDescription, intl } = this.props; const buttonText = status.get('reblogged') ? messages.cancel_reblog : messages.reblog; return ( @@ -78,7 +79,13 @@ export default class BoostModal extends ImmutablePureComponent {
-
Shift + }} />
+
+ { missingMediaDescription ? + + : + Shift + }} /> + } +
diff --git a/app/javascript/flavours/glitch/reducers/local_settings.js b/app/javascript/flavours/glitch/reducers/local_settings.js index 5716c5982..68e1c8424 100644 --- a/app/javascript/flavours/glitch/reducers/local_settings.js +++ b/app/javascript/flavours/glitch/reducers/local_settings.js @@ -15,6 +15,7 @@ const initialState = ImmutableMap({ show_reply_count : false, always_show_spoilers_field: false, confirm_missing_media_description: false, + confirm_boost_missing_media_description: false, confirm_before_clearing_draft: true, preselect_on_reply: true, inline_preview_cards: true, -- cgit From b551d8aa53cf70ba051285276d020f468be46ef9 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Fri, 7 Jun 2019 18:57:10 +0200 Subject: Fix unboost confirmation dialog not showing up on detailed statuses --- app/javascript/flavours/glitch/features/status/index.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.js index 8722490d9..a35de5c4f 100644 --- a/app/javascript/flavours/glitch/features/status/index.js +++ b/app/javascript/flavours/glitch/features/status/index.js @@ -237,16 +237,12 @@ export default class Status extends ImmutablePureComponent { handleReblogClick = (status, e) => { const { settings, dispatch } = this.props; - if (status.get('reblogged')) { - dispatch(unreblog(status)); + if (settings.get('confirm_boost_missing_media_description') && status.get('media_attachments').some(item => !item.get('description')) && !status.get('reblogged')) { + dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog, missingMediaDescription: true })); + } else if ((e && e.shiftKey) || !boostModal) { + this.handleModalReblog(status); } else { - if (settings.get('confirm_boost_missing_media_description') && status.get('media_attachments').some(item => !item.get('description'))) { - dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog, missingMediaDescription: true })); - } else if ((e && e.shiftKey) || !boostModal) { - this.handleModalReblog(status); - } else { - dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog })); - } + dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog })); } } -- cgit From b45f555a0c7a7d50ed7640b938eb8b5a671a0e10 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Fri, 7 Jun 2019 18:57:31 +0200 Subject: Minor cleanup --- app/javascript/flavours/glitch/components/status.js | 2 -- 1 file changed, 2 deletions(-) (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js index 4b9364ae5..f6d73475a 100644 --- a/app/javascript/flavours/glitch/components/status.js +++ b/app/javascript/flavours/glitch/components/status.js @@ -114,8 +114,6 @@ export default class Status extends ImmutablePureComponent { 'account', 'settings', 'prepend', - 'boostModal', - 'favouriteModal', 'muted', 'collapse', 'notification', -- cgit From 59d214e54be005c9087ce20a1b1786328cc254ca Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 7 Jun 2019 03:39:24 +0200 Subject: [Glitch] Change preferences page into appearance, notifications, and other Port SCSS changes from 1db4117030ac69b1083ddfe7390dedb02cede421 to glitch-soc Signed-off-by: Thibaut Girka --- app/javascript/flavours/glitch/styles/accounts.scss | 3 ++- app/javascript/flavours/glitch/styles/forms.scss | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/styles/accounts.scss b/app/javascript/flavours/glitch/styles/accounts.scss index 8b8c615ee..0fae137f0 100644 --- a/app/javascript/flavours/glitch/styles/accounts.scss +++ b/app/javascript/flavours/glitch/styles/accounts.scss @@ -199,7 +199,8 @@ } } -.account-role { +.account-role, +.simple_form .recommended { display: inline-block; padding: 4px 6px; cursor: default; diff --git a/app/javascript/flavours/glitch/styles/forms.scss b/app/javascript/flavours/glitch/styles/forms.scss index 2b8d7a682..dae29a003 100644 --- a/app/javascript/flavours/glitch/styles/forms.scss +++ b/app/javascript/flavours/glitch/styles/forms.scss @@ -79,6 +79,12 @@ code { text-decoration: none; } } + + .recommended { + position: absolute; + margin: 0 4px; + margin-top: -2px; + } } } @@ -443,6 +449,10 @@ code { height: 41px; } + h4 { + margin-bottom: 15px !important; + } + .label_input { &__wrapper { position: relative; -- cgit From fc6d27daf39ca136b0e3e09e7572c5c46086ad72 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 7 Jun 2019 23:35:26 +0200 Subject: [Glitch] Fix RTL layout not being RTL within the columns area Port 25f93f40974c61b5a02770fe0b1d016213397d1d to glitch-soc Signed-off-by: Thibaut Girka --- app/javascript/flavours/glitch/styles/rtl.scss | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/styles/rtl.scss b/app/javascript/flavours/glitch/styles/rtl.scss index 9e0a93ec2..11fae3121 100644 --- a/app/javascript/flavours/glitch/styles/rtl.scss +++ b/app/javascript/flavours/glitch/styles/rtl.scss @@ -43,6 +43,10 @@ body.rtl { left: 10px; } + .columns-area { + direction: rtl; + } + .column-header__buttons { left: 0; right: auto; -- cgit From 82899b3d2e33d10175b1a6df41fb27c6d63d54b6 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 11 Jun 2019 02:26:37 +0200 Subject: [Glitch] Fix list not being automatically unpinned when it returns 404 in web UI Port 92b572e2a3830086497900fa78dcfcc2ae919e33 to glitch-soc Signed-off-by: Thibaut Girka --- app/javascript/flavours/glitch/actions/alerts.js | 3 ++- app/javascript/flavours/glitch/styles/components/columns.scss | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/actions/alerts.js b/app/javascript/flavours/glitch/actions/alerts.js index b2c7ab76a..ef2500e7b 100644 --- a/app/javascript/flavours/glitch/actions/alerts.js +++ b/app/javascript/flavours/glitch/actions/alerts.js @@ -8,6 +8,7 @@ const messages = defineMessages({ export const ALERT_SHOW = 'ALERT_SHOW'; export const ALERT_DISMISS = 'ALERT_DISMISS'; export const ALERT_CLEAR = 'ALERT_CLEAR'; +export const ALERT_NOOP = 'ALERT_NOOP'; export function dismissAlert(alert) { return { @@ -36,7 +37,7 @@ export function showAlertForError(error) { if (status === 404 || status === 410) { // Skip these errors as they are reflected in the UI - return {}; + return { type: ALERT_NOOP }; } let message = statusText; diff --git a/app/javascript/flavours/glitch/styles/components/columns.scss b/app/javascript/flavours/glitch/styles/components/columns.scss index 7a8accc27..0bf01ed8d 100644 --- a/app/javascript/flavours/glitch/styles/components/columns.scss +++ b/app/javascript/flavours/glitch/styles/components/columns.scss @@ -294,6 +294,10 @@ margin-left: 0; } +.column-header__links { + margin-bottom: 14px; +} + .column-header__links .text-btn { margin-right: 10px; } -- cgit From 32bdff09c1093b94c784a809e582f1e5f6ff9694 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Wed, 12 Jun 2019 09:46:39 +0200 Subject: Properly handle unboosting statuses from detailed view Fixes #1106 --- app/javascript/flavours/glitch/features/status/index.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.js index a35de5c4f..76bfaaffa 100644 --- a/app/javascript/flavours/glitch/features/status/index.js +++ b/app/javascript/flavours/glitch/features/status/index.js @@ -231,7 +231,13 @@ export default class Status extends ImmutablePureComponent { } handleModalReblog = (status) => { - this.props.dispatch(reblog(status)); + const { dispatch } = this.props; + + if (status.get('reblogged')) { + dispatch(unreblog(status)); + } else { + dispatch(reblog(status)); + } } handleReblogClick = (status, e) => { -- cgit From fe5c4f976cf7f447a0db6a7e32fce992431fffd6 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Thu, 13 Jun 2019 12:50:52 +0200 Subject: Fix clicking on the elefriend --- app/javascript/flavours/glitch/features/compose/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/features/compose/index.js b/app/javascript/flavours/glitch/features/compose/index.js index e60eedfd9..a7795a04d 100644 --- a/app/javascript/flavours/glitch/features/compose/index.js +++ b/app/javascript/flavours/glitch/features/compose/index.js @@ -29,7 +29,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ }, }); -export default @connect(mapStateToProps) +export default @connect(mapStateToProps, mapDispatchToProps) @injectIntl class Compose extends React.PureComponent { static propTypes = { -- cgit From 610b4b44c4f70583f2f3082dc8f494fadb0681ef Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 23 May 2019 01:35:22 +0200 Subject: [Glitch] Add single-column mode Port 9ddeb30f90f9402eb567c88354d4956fcfdf0361 to glitch-soc Signed-off-by: Thibaut Girka --- .../glitch/components/autosuggest_input.js | 2 +- .../flavours/glitch/features/compose/index.js | 10 +- .../glitch/features/getting_started/index.js | 17 +- .../glitch/features/ui/components/columns_area.js | 10 +- .../ui/components/notifications_counter_icon.js | 24 +++ .../glitch/features/ui/components/tabs_bar.js | 27 +--- .../flavours/glitch/features/ui/index.js | 14 +- .../flavours/glitch/reducers/settings.js | 2 + .../flavours/glitch/styles/components/columns.scss | 42 +---- .../flavours/glitch/styles/components/drawer.scss | 5 + .../flavours/glitch/styles/components/index.scss | 177 ++++++++++++++++++++- 11 files changed, 247 insertions(+), 83 deletions(-) create mode 100644 app/javascript/flavours/glitch/features/ui/components/notifications_counter_icon.js (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/components/autosuggest_input.js b/app/javascript/flavours/glitch/components/autosuggest_input.js index 5fc952d8e..a6ae3bc75 100644 --- a/app/javascript/flavours/glitch/components/autosuggest_input.js +++ b/app/javascript/flavours/glitch/components/autosuggest_input.js @@ -49,7 +49,7 @@ export default class AutosuggestInput extends ImmutablePureComponent { autoFocus: PropTypes.bool, className: PropTypes.string, id: PropTypes.string, - searchTokens: PropTypes.arrayOf(PropTypes.string), + searchTokens: ImmutablePropTypes.list, maxLength: PropTypes.number, }; diff --git a/app/javascript/flavours/glitch/features/compose/index.js b/app/javascript/flavours/glitch/features/compose/index.js index a7795a04d..d3070a199 100644 --- a/app/javascript/flavours/glitch/features/compose/index.js +++ b/app/javascript/flavours/glitch/features/compose/index.js @@ -61,12 +61,12 @@ class Compose extends React.PureComponent {
{!isSearchPage &&
+ - {multiColumn && ( -
- {mascot ? :
- )} + +
+ {mascot ? :
} diff --git a/app/javascript/flavours/glitch/features/getting_started/index.js b/app/javascript/flavours/glitch/features/getting_started/index.js index 7b645c9d0..25fff1974 100644 --- a/app/javascript/flavours/glitch/features/getting_started/index.js +++ b/app/javascript/flavours/glitch/features/getting_started/index.js @@ -10,10 +10,12 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { me, invitesEnabled, version } from 'flavours/glitch/util/initial_state'; import { fetchFollowRequests } from 'flavours/glitch/actions/accounts'; +import { changeSetting } from 'flavours/glitch/actions/settings'; import { List as ImmutableList } from 'immutable'; import { createSelector } from 'reselect'; import { fetchLists } from 'flavours/glitch/actions/lists'; import { preferencesLink, profileLink, signOutLink } from 'flavours/glitch/util/backend_links'; +import Toggle from 'react-toggle'; const messages = defineMessages({ heading: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, @@ -52,6 +54,7 @@ const makeMapStateToProps = () => { columns: state.getIn(['settings', 'columns']), unreadFollowRequests: state.getIn(['user_lists', 'follow_requests', 'items'], ImmutableList()).size, unreadNotifications: state.getIn(['notifications', 'unread']), + forceSingleColumn: state.getIn(['settings', 'forceSingleColumn'], false), }); return mapStateToProps; @@ -61,6 +64,7 @@ const mapDispatchToProps = dispatch => ({ fetchFollowRequests: () => dispatch(fetchFollowRequests()), fetchLists: () => dispatch(fetchLists()), openSettings: () => dispatch(openModal('SETTINGS', {})), + changeForceSingleColumn: checked => dispatch(changeSetting(['forceSingleColumn'], checked)), }); const badgeDisplay = (number, limit) => { @@ -88,6 +92,8 @@ export default class GettingStarted extends ImmutablePureComponent { lists: ImmutablePropTypes.list, fetchLists: PropTypes.func.isRequired, openSettings: PropTypes.func.isRequired, + forceSingleColumn: PropTypes.bool, + changeForceSingleColumn: PropTypes.func.isRequired, }; componentWillMount () { @@ -102,8 +108,12 @@ export default class GettingStarted extends ImmutablePureComponent { } } + handleForceSingleColumnChange = ({ target }) => { + this.props.changeForceSingleColumn(target.checked); + } + render () { - const { intl, myAccount, columns, multiColumn, unreadFollowRequests, unreadNotifications, lists, openSettings } = this.props; + const { intl, myAccount, columns, multiColumn, unreadFollowRequests, unreadNotifications, lists, openSettings, forceSingleColumn } = this.props; const navItems = []; let listItems = []; @@ -183,6 +193,11 @@ export default class GettingStarted extends ImmutablePureComponent {

+ + ); } 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 0fe580b9b..5e680a61c 100644 --- a/app/javascript/flavours/glitch/features/ui/components/columns_area.js +++ b/app/javascript/flavours/glitch/features/ui/components/columns_area.js @@ -5,7 +5,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import ReactSwipeableViews from 'react-swipeable-views'; -import { links, getIndex, getLink } from './tabs_bar'; +import TabsBar, { links, getIndex, getLink } from './tabs_bar'; import { Link } from 'react-router-dom'; import BundleContainer from '../containers/bundle_container'; @@ -139,7 +139,7 @@ export default class ColumnsArea extends ImmutablePureComponent { ; return ( -
+
{view}
); @@ -164,13 +164,17 @@ export default class ColumnsArea extends ImmutablePureComponent { const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : ; return columnIndex !== -1 ? [ + , + {links.map(this.renderView)} , floatingActionButton, ] : [ -
{children}
, + , + +
{children}
, floatingActionButton, ]; diff --git a/app/javascript/flavours/glitch/features/ui/components/notifications_counter_icon.js b/app/javascript/flavours/glitch/features/ui/components/notifications_counter_icon.js new file mode 100644 index 000000000..137658b94 --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/notifications_counter_icon.js @@ -0,0 +1,24 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import Icon from 'flavours/glitch/components/icon'; + +const mapStateToProps = state => ({ + count: state.getIn(['notifications', 'unread']), + showBadge: state.getIn(['local_settings', 'notifications', 'tab_badge']), +}); + +const formatNumber = num => num > 99 ? '99+' : num; + +const NotificationsCounterIcon = ({ count, showBadge }) => ( + + + {showBadge && count > 0 && {formatNumber(count)}} + +); + +NotificationsCounterIcon.propTypes = { + count: PropTypes.number.isRequired, +}; + +export default connect(mapStateToProps)(NotificationsCounterIcon); 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 b44a21a42..8963b16b3 100644 --- a/app/javascript/flavours/glitch/features/ui/components/tabs_bar.js +++ b/app/javascript/flavours/glitch/features/ui/components/tabs_bar.js @@ -4,34 +4,11 @@ import { NavLink, withRouter } from 'react-router-dom'; import { FormattedMessage, injectIntl } from 'react-intl'; import { debounce } from 'lodash'; import { isUserTouching } from 'flavours/glitch/util/is_mobile'; -import { connect } from 'react-redux'; - -const mapStateToProps = state => ({ - unreadNotifications: state.getIn(['notifications', 'unread']), - showBadge: state.getIn(['local_settings', 'notifications', 'tab_badge']), -}); - -@connect(mapStateToProps) -class NotificationsIcon extends React.PureComponent { - static propTypes = { - unreadNotifications: PropTypes.number, - showBadge: PropTypes.bool, - }; - - render() { - const { unreadNotifications, showBadge } = this.props; - return ( - - - { showBadge && unreadNotifications > 0 &&
} - - ); - } -} +import NotificationsCounterIcon from './notifications_counter_icon'; export const links = [ , - , + , , , diff --git a/app/javascript/flavours/glitch/features/ui/index.js b/app/javascript/flavours/glitch/features/ui/index.js index f8fff934d..36156a5ef 100644 --- a/app/javascript/flavours/glitch/features/ui/index.js +++ b/app/javascript/flavours/glitch/features/ui/index.js @@ -2,7 +2,6 @@ import React from 'react'; import NotificationsContainer from './containers/notifications_container'; import PropTypes from 'prop-types'; import LoadingBarContainer from './containers/loading_bar_container'; -import TabsBar from './components/tabs_bar'; import ModalContainer from './containers/modal_container'; import { connect } from 'react-redux'; import { Redirect, withRouter } from 'react-router-dom'; @@ -69,6 +68,7 @@ const mapStateToProps = state => ({ unreadNotifications: state.getIn(['notifications', 'unread']), showFaviconBadge: state.getIn(['local_settings', 'notifications', 'favicon_badge']), hicolorPrivacyIcons: state.getIn(['local_settings', 'hicolor_privacy_icons']), + forceSingleColumn: state.getIn(['settings', 'forceSingleColumn'], false), }); const keyMap = { @@ -126,6 +126,7 @@ export default class UI extends React.Component { dropdownMenuIsOpen: PropTypes.bool, unreadNotifications: PropTypes.number, showFaviconBadge: PropTypes.bool, + forceSingleColumn: PropTypes.bool, }; state = { @@ -431,7 +432,9 @@ export default class UI extends React.Component { render () { const { width, draggingOver } = this.state; - const { children, layout, isWide, navbarUnder, dropdownMenuIsOpen } = this.props; + const { children, layout, isWide, navbarUnder, dropdownMenuIsOpen, forceSingleColumn } = this.props; + const singleColumn = forceSingleColumn || isMobile(width, layout); + const redirect = singleColumn ? : ; const columnsClass = layout => { switch (layout) { @@ -475,11 +478,9 @@ export default class UI extends React.Component { return (
- {navbarUnder ? null : ()} - - + - + {redirect} @@ -518,7 +519,6 @@ export default class UI extends React.Component { - {navbarUnder ? () : null} diff --git a/app/javascript/flavours/glitch/reducers/settings.js b/app/javascript/flavours/glitch/reducers/settings.js index a37863a69..a568183df 100644 --- a/app/javascript/flavours/glitch/reducers/settings.js +++ b/app/javascript/flavours/glitch/reducers/settings.js @@ -15,6 +15,8 @@ const initialState = ImmutableMap({ skinTone: 1, + forceSingleColumn: false, + home: ImmutableMap({ shows: ImmutableMap({ reblog: true, diff --git a/app/javascript/flavours/glitch/styles/components/columns.scss b/app/javascript/flavours/glitch/styles/components/columns.scss index 0bf01ed8d..50438393e 100644 --- a/app/javascript/flavours/glitch/styles/components/columns.scss +++ b/app/javascript/flavours/glitch/styles/components/columns.scss @@ -13,16 +13,6 @@ position: relative; } -@include limited-single-column('screen and (min-width: 360px)', $parent: null) { - .columns-area { - padding: 10px; - } - - .react-swipeable-view-container .columns-area { - height: calc(100% - 20px) !important; - } -} - .react-swipeable-view-container { &, .columns-area, @@ -63,34 +53,6 @@ overflow: hidden; } -@include limited-single-column('screen and (min-width: 360px)', $parent: null) { - .tabs-bar { - margin: 10px; - margin-bottom: 0; - } -} - -:root { // Overrides .wide stylings for mobile view - @include single-column('screen and (max-width: 630px)', $parent: null) { - .column { - flex: auto; - width: 100%; - min-width: 0; - max-width: none; - padding: 0; - } - - .columns-area { - flex-direction: column; - } - - .search__input, - .autosuggest-textarea__textarea { - font-size: 16px; - } - } -} - @include multi-columns('screen and (min-width: 631px)', $parent: null) { .columns-area { padding: 0; @@ -442,6 +404,10 @@ contain: strict; } + & > span { + max-width: 400px; + } + a { color: $highlight-text-color; text-decoration: none; diff --git a/app/javascript/flavours/glitch/styles/components/drawer.scss b/app/javascript/flavours/glitch/styles/components/drawer.scss index 9f426448f..07558a8d0 100644 --- a/app/javascript/flavours/glitch/styles/components/drawer.scss +++ b/app/javascript/flavours/glitch/styles/components/drawer.scss @@ -276,6 +276,7 @@ background: lighten($ui-base-color, 13%) url('data:image/svg+xml;utf8,') no-repeat bottom / 100% auto; flex: 1; min-height: 47px; + display: none; > img { display: block; @@ -295,6 +296,10 @@ border: none; cursor: inherit; } + + @media screen and (min-height: 640px) { + display: block; + } } .pseudo-drawer { diff --git a/app/javascript/flavours/glitch/styles/components/index.scss b/app/javascript/flavours/glitch/styles/components/index.scss index 9db64bbcb..8f385e15a 100644 --- a/app/javascript/flavours/glitch/styles/components/index.scss +++ b/app/javascript/flavours/glitch/styles/components/index.scss @@ -553,6 +553,7 @@ } .tabs-bar { + box-sizing: border-box; display: flex; background: lighten($ui-base-color, 8%); flex: 0 0 auto; @@ -590,15 +591,130 @@ } } - span:last-child { + span { margin-left: 5px; display: none; } } -@include multi-columns('screen and (min-width: 631px)', $parent: null) { +@media screen and (min-width: 600px) { + .tabs-bar__link { + span { + display: inline; + } + } +} + +.columns-area--mobile { + flex-direction: column; + width: 100%; + max-width: 600px; + margin: 0 auto; + + .column, + .drawer { + width: 100%; + height: 100%; + padding: 0; + } + + .search__input, + .autosuggest-textarea__textarea { + font-size: 16px; + } + + @media screen and (min-width: 360px) { + padding: 10px; + } + + @media screen and (min-width: 630px) { + .detailed-status { + padding: 15px; + + .media-gallery, + .video-player { + margin-top: 15px; + } + } + + .account__header__bar { + padding: 5px 10px; + } + + .navigation-bar, + .compose-form { + padding: 15px; + } + + .compose-form .compose-form__publish .compose-form__publish-button-wrapper { + padding-top: 15px; + } + + .status { + padding: 15px 15px 15px (48px + 15px * 2); + min-height: 48px + 2px; + + &__avatar { + left: 15px; + top: 17px; + } + + &__content { + padding-top: 5px; + } + + &__prepend { + margin-left: 48px + 15px * 2; + padding-top: 15px; + } + + &__prepend-icon-wrapper { + left: -32px; + } + + .media-gallery, + &__action-bar, + .video-player { + margin-top: 10px; + } + } + } +} + +@media screen and (min-width: 360px) { .tabs-bar { - display: none; + margin: 10px auto; + margin-bottom: 0; + width: calc(100% - 20px); + max-width: 600px; + } + + .react-swipeable-view-container .columns-area--mobile { + height: calc(100% - 20px) !important; + } + + .getting-started__wrapper, + .getting-started__trends, + .search { + margin-bottom: 10px; + } +} + +.icon-with-badge { + position: relative; + + &__badge { + position: absolute; + right: -13px; + top: -13px; + background: $ui-highlight-color; + border: 2px solid lighten($ui-base-color, 8%); + padding: 1px 6px; + border-radius: 6px; + font-size: 10px; + font-weight: 500; + line-height: 14px; + color: $primary-text-color; } } @@ -1272,6 +1388,61 @@ height: 1em; } +.navigational-toggle { + padding: 10px; + display: flex; + align-items: center; + justify-content: space-between; + font-size: 14px; + color: $dark-text-color; +} + +.layout-toggle { + display: flex; + padding: 5px; + + button { + box-sizing: border-box; + flex: 0 0 50%; + background: transparent; + padding: 5px; + border: 0; + position: relative; + + &:hover, + &:focus, + &:active { + svg path:first-child { + fill: lighten($ui-base-color, 16%); + } + } + } + + svg { + width: 100%; + height: auto; + + path:first-child { + fill: lighten($ui-base-color, 12%); + } + + path:last-child { + fill: darken($ui-base-color, 14%); + } + } + + &__active { + color: $ui-highlight-color; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: lighten($ui-base-color, 12%); + border-radius: 50%; + padding: 0.35rem; + } +} + ::-webkit-scrollbar-thumb { border-radius: 0; } -- cgit From 0bd9f23e6d2599a3d3930b6456024a4e3c9a3535 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 23 May 2019 20:01:10 +0200 Subject: [Glitch] Various improvements to single column layout Port 84dc21d55d8627182ce201baeddb6fbbdf8748c2 to glitch-soc Signed-off-by: Thibaut Girka --- .../glitch/features/ui/components/columns_area.js | 27 ++++++++++------ .../flavours/glitch/styles/components/columns.scss | 37 ++++++++++++++++++++++ .../flavours/glitch/styles/components/index.scss | 6 ++-- 3 files changed, 56 insertions(+), 14 deletions(-) (limited to 'app/javascript/flavours/glitch') 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 5e680a61c..d3d8aebd1 100644 --- a/app/javascript/flavours/glitch/features/ui/components/columns_area.js +++ b/app/javascript/flavours/glitch/features/ui/components/columns_area.js @@ -163,21 +163,28 @@ export default class ColumnsArea extends ImmutablePureComponent { if (singleColumn) { const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : ; - return columnIndex !== -1 ? [ - , - + const content = columnIndex !== -1 ? ( {links.map(this.renderView)} - , + + ) : ( +
{children}
+ ); + + return ( +
+
- floatingActionButton, - ] : [ - , +
+ + {content} +
-
{children}
, +
- floatingActionButton, - ]; + {floatingActionButton} +
+ ); } return ( diff --git a/app/javascript/flavours/glitch/styles/components/columns.scss b/app/javascript/flavours/glitch/styles/components/columns.scss index 50438393e..f372a4830 100644 --- a/app/javascript/flavours/glitch/styles/components/columns.scss +++ b/app/javascript/flavours/glitch/styles/components/columns.scss @@ -11,6 +11,39 @@ justify-content: flex-start; overflow-x: auto; position: relative; + + &__panels { + display: flex; + justify-content: center; + width: 100%; + height: 100%; + + &__pane { + flex: 1 1 auto; + height: 100%; + overflow: hidden; + pointer-events: none; + display: flex; + justify-content: flex-end; + + &__inner { + pointer-events: auto; + height: 100%; + } + } + + &__main { + box-sizing: border-box; + width: 100%; + max-width: 600px; + display: flex; + flex-direction: column; + + @media screen and (min-width: 360px) { + padding: 0 10px; + } + } + } } .react-swipeable-view-container { @@ -496,4 +529,8 @@ &:active { background: lighten($ui-highlight-color, 7%); } + + @media screen and (min-width: 630px) { + display: none; + } } diff --git a/app/javascript/flavours/glitch/styles/components/index.scss b/app/javascript/flavours/glitch/styles/components/index.scss index 8f385e15a..53b678c9a 100644 --- a/app/javascript/flavours/glitch/styles/components/index.scss +++ b/app/javascript/flavours/glitch/styles/components/index.scss @@ -608,7 +608,6 @@ .columns-area--mobile { flex-direction: column; width: 100%; - max-width: 600px; margin: 0 auto; .column, @@ -624,7 +623,7 @@ } @media screen and (min-width: 360px) { - padding: 10px; + padding: 10px 0; } @media screen and (min-width: 630px) { @@ -685,8 +684,7 @@ .tabs-bar { margin: 10px auto; margin-bottom: 0; - width: calc(100% - 20px); - max-width: 600px; + width: 100%; } .react-swipeable-view-container .columns-area--mobile { -- cgit From d99a661f08398238838bf576e86c4be706ee7aa0 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sat, 25 May 2019 21:27:00 +0200 Subject: [Glitch] Add responsive panels to the single-column layout Port 1e5532e693d9533ee37f553aeb191e284178fa52 to glitch-soc Signed-off-by: Thibaut Girka --- app/javascript/flavours/glitch/actions/compose.js | 26 ++-- app/javascript/flavours/glitch/actions/statuses.js | 7 +- .../glitch/components/autosuggest_input.js | 2 +- .../features/compose/components/navigation_bar.js | 2 +- .../glitch/features/compose/components/search.js | 11 +- .../glitch/features/getting_started/index.js | 17 +-- .../flavours/glitch/features/search/index.js | 17 +++ .../glitch/features/ui/components/columns_area.js | 14 +- .../glitch/features/ui/components/compose_panel.js | 41 ++++++ .../glitch/features/ui/components/list_panel.js | 55 ++++++++ .../features/ui/components/navigation_panel.js | 27 ++++ .../ui/components/notifications_counter_icon.js | 6 +- .../glitch/features/ui/components/tabs_bar.js | 13 +- .../flavours/glitch/features/ui/index.js | 9 +- .../flavours/glitch/reducers/settings.js | 2 - .../flavours/glitch/styles/components/columns.scss | 57 ++++---- .../flavours/glitch/styles/components/index.scss | 157 +++++++++++++++++++-- .../flavours/glitch/util/async-components.js | 4 + .../flavours/glitch/util/initial_state.js | 1 + 19 files changed, 374 insertions(+), 94 deletions(-) create mode 100644 app/javascript/flavours/glitch/features/search/index.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/compose_panel.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/list_panel.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/navigation_panel.js (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/actions/compose.js b/app/javascript/flavours/glitch/actions/compose.js index 2fb97fa17..093952316 100644 --- a/app/javascript/flavours/glitch/actions/compose.js +++ b/app/javascript/flavours/glitch/actions/compose.js @@ -68,6 +68,14 @@ const messages = defineMessages({ uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' }, }); +const COMPOSE_PANEL_BREAKPOINT = 600 + (285 * 1) + (10 * 3); + +export const ensureComposeIsVisible = (getState, routerHistory) => { + if (!getState().getIn(['compose', 'mounted']) && window.innerWidth < COMPOSE_PANEL_BREAKPOINT) { + routerHistory.push('/statuses/new'); + } +}; + export function changeCompose(text) { return { type: COMPOSE_CHANGE, @@ -81,16 +89,14 @@ export function cycleElefriendCompose() { }; }; -export function replyCompose(status, router) { +export function replyCompose(status, routerHistory) { return (dispatch, getState) => { dispatch({ type: COMPOSE_REPLY, status: status, }); - if (router && !getState().getIn(['compose', 'mounted'])) { - router.push('/statuses/new'); - } + ensureComposeIsVisible(getState, routerHistory); }; }; @@ -106,29 +112,25 @@ export function resetCompose() { }; }; -export function mentionCompose(account, router) { +export function mentionCompose(account, routerHistory) { return (dispatch, getState) => { dispatch({ type: COMPOSE_MENTION, account: account, }); - if (!getState().getIn(['compose', 'mounted'])) { - router.push('/statuses/new'); - } + ensureComposeIsVisible(getState, routerHistory); }; }; -export function directCompose(account, router) { +export function directCompose(account, routerHistory) { return (dispatch, getState) => { dispatch({ type: COMPOSE_DIRECT, account: account, }); - if (!getState().getIn(['compose', 'mounted'])) { - router.push('/statuses/new'); - } + ensureComposeIsVisible(getState, routerHistory); }; }; diff --git a/app/javascript/flavours/glitch/actions/statuses.js b/app/javascript/flavours/glitch/actions/statuses.js index 7e22a7f98..4d2bda78b 100644 --- a/app/javascript/flavours/glitch/actions/statuses.js +++ b/app/javascript/flavours/glitch/actions/statuses.js @@ -2,6 +2,7 @@ import api from 'flavours/glitch/util/api'; import { deleteFromTimelines } from './timelines'; import { importFetchedStatus, importFetchedStatuses } from './importer'; +import { ensureComposeIsVisible } from './compose'; export const STATUS_FETCH_REQUEST = 'STATUS_FETCH_REQUEST'; export const STATUS_FETCH_SUCCESS = 'STATUS_FETCH_SUCCESS'; @@ -80,7 +81,7 @@ export function redraft(status, raw_text, content_type) { }; }; -export function deleteStatus(id, router, withRedraft = false) { +export function deleteStatus(id, routerHistory, withRedraft = false) { return (dispatch, getState) => { let status = getState().getIn(['statuses', id]); @@ -97,9 +98,7 @@ export function deleteStatus(id, router, withRedraft = false) { if (withRedraft) { dispatch(redraft(status, response.data.text, response.data.content_type)); - if (!getState().getIn(['compose', 'mounted'])) { - router.push('/statuses/new'); - } + ensureComposeIsVisible(getState, routerHistory); } }).catch(error => { dispatch(deleteStatusFail(id, error)); diff --git a/app/javascript/flavours/glitch/components/autosuggest_input.js b/app/javascript/flavours/glitch/components/autosuggest_input.js index a6ae3bc75..5fc952d8e 100644 --- a/app/javascript/flavours/glitch/components/autosuggest_input.js +++ b/app/javascript/flavours/glitch/components/autosuggest_input.js @@ -49,7 +49,7 @@ export default class AutosuggestInput extends ImmutablePureComponent { autoFocus: PropTypes.bool, className: PropTypes.string, id: PropTypes.string, - searchTokens: ImmutablePropTypes.list, + searchTokens: PropTypes.arrayOf(PropTypes.string), maxLength: PropTypes.number, }; diff --git a/app/javascript/flavours/glitch/features/compose/components/navigation_bar.js b/app/javascript/flavours/glitch/features/compose/components/navigation_bar.js index 59172bb23..3148434f1 100644 --- a/app/javascript/flavours/glitch/features/compose/components/navigation_bar.js +++ b/app/javascript/flavours/glitch/features/compose/components/navigation_bar.js @@ -17,7 +17,7 @@ export default class NavigationBar extends ImmutablePureComponent {
{this.props.account.get('acct')} - + diff --git a/app/javascript/flavours/glitch/features/compose/components/search.js b/app/javascript/flavours/glitch/features/compose/components/search.js index 5fed1567a..5f8c6cd52 100644 --- a/app/javascript/flavours/glitch/features/compose/components/search.js +++ b/app/javascript/flavours/glitch/features/compose/components/search.js @@ -60,6 +60,10 @@ class SearchPopout extends React.PureComponent { export default @injectIntl class Search extends React.PureComponent { + static contextTypes = { + router: PropTypes.object.isRequired, + }; + static propTypes = { value: PropTypes.string.isRequired, submitted: PropTypes.bool, @@ -67,6 +71,7 @@ class Search extends React.PureComponent { onSubmit: PropTypes.func.isRequired, onClear: PropTypes.func.isRequired, onShow: PropTypes.func.isRequired, + openInRoute: PropTypes.bool, intl: PropTypes.object.isRequired, }; @@ -109,8 +114,10 @@ class Search extends React.PureComponent { const { onSubmit } = this.props; switch (e.key) { case 'Enter': - if (onSubmit) { - onSubmit(); + onSubmit(); + + if (this.props.openInRoute) { + this.context.router.history.push('/search'); } break; case 'Escape': diff --git a/app/javascript/flavours/glitch/features/getting_started/index.js b/app/javascript/flavours/glitch/features/getting_started/index.js index 25fff1974..7b645c9d0 100644 --- a/app/javascript/flavours/glitch/features/getting_started/index.js +++ b/app/javascript/flavours/glitch/features/getting_started/index.js @@ -10,12 +10,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { me, invitesEnabled, version } from 'flavours/glitch/util/initial_state'; import { fetchFollowRequests } from 'flavours/glitch/actions/accounts'; -import { changeSetting } from 'flavours/glitch/actions/settings'; import { List as ImmutableList } from 'immutable'; import { createSelector } from 'reselect'; import { fetchLists } from 'flavours/glitch/actions/lists'; import { preferencesLink, profileLink, signOutLink } from 'flavours/glitch/util/backend_links'; -import Toggle from 'react-toggle'; const messages = defineMessages({ heading: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, @@ -54,7 +52,6 @@ const makeMapStateToProps = () => { columns: state.getIn(['settings', 'columns']), unreadFollowRequests: state.getIn(['user_lists', 'follow_requests', 'items'], ImmutableList()).size, unreadNotifications: state.getIn(['notifications', 'unread']), - forceSingleColumn: state.getIn(['settings', 'forceSingleColumn'], false), }); return mapStateToProps; @@ -64,7 +61,6 @@ const mapDispatchToProps = dispatch => ({ fetchFollowRequests: () => dispatch(fetchFollowRequests()), fetchLists: () => dispatch(fetchLists()), openSettings: () => dispatch(openModal('SETTINGS', {})), - changeForceSingleColumn: checked => dispatch(changeSetting(['forceSingleColumn'], checked)), }); const badgeDisplay = (number, limit) => { @@ -92,8 +88,6 @@ export default class GettingStarted extends ImmutablePureComponent { lists: ImmutablePropTypes.list, fetchLists: PropTypes.func.isRequired, openSettings: PropTypes.func.isRequired, - forceSingleColumn: PropTypes.bool, - changeForceSingleColumn: PropTypes.func.isRequired, }; componentWillMount () { @@ -108,12 +102,8 @@ export default class GettingStarted extends ImmutablePureComponent { } } - handleForceSingleColumnChange = ({ target }) => { - this.props.changeForceSingleColumn(target.checked); - } - render () { - const { intl, myAccount, columns, multiColumn, unreadFollowRequests, unreadNotifications, lists, openSettings, forceSingleColumn } = this.props; + const { intl, myAccount, columns, multiColumn, unreadFollowRequests, unreadNotifications, lists, openSettings } = this.props; const navItems = []; let listItems = []; @@ -193,11 +183,6 @@ export default class GettingStarted extends ImmutablePureComponent {

- - ); } diff --git a/app/javascript/flavours/glitch/features/search/index.js b/app/javascript/flavours/glitch/features/search/index.js new file mode 100644 index 000000000..b35c8ed49 --- /dev/null +++ b/app/javascript/flavours/glitch/features/search/index.js @@ -0,0 +1,17 @@ +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 d3d8aebd1..2e28bbe50 100644 --- a/app/javascript/flavours/glitch/features/ui/components/columns_area.js +++ b/app/javascript/flavours/glitch/features/ui/components/columns_area.js @@ -13,6 +13,8 @@ import ColumnLoading from './column_loading'; import DrawerLoading from './drawer_loading'; import BundleColumnError from './bundle_column_error'; import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, DirectTimeline, FavouritedStatuses, BookmarkedStatuses, ListTimeline } from 'flavours/glitch/util/async-components'; +import ComposePanel from './compose_panel'; +import NavigationPanel from './navigation_panel'; import detectPassiveEvents from 'detect-passive-events'; import { scrollRight } from 'flavours/glitch/util/scroll'; @@ -173,14 +175,22 @@ export default class ColumnsArea extends ImmutablePureComponent { return (
-
+
+
+ +
+
{content}
-
+
+
+ +
+
{floatingActionButton}
diff --git a/app/javascript/flavours/glitch/features/ui/components/compose_panel.js b/app/javascript/flavours/glitch/features/ui/components/compose_panel.js new file mode 100644 index 000000000..8acf89950 --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/compose_panel.js @@ -0,0 +1,41 @@ +import React from 'react'; +import SearchContainer from 'flavours/glitch/features/compose/containers/search_container'; +import ComposeFormContainer from 'flavours/glitch/features/compose/containers/compose_form_container'; +import NavigationContainer from 'flavours/glitch/features/compose/containers/navigation_container'; +import { invitesEnabled, version, repository, source_url } from 'mastodon/initial_state'; +import { Link } from 'react-router-dom'; +import { FormattedMessage } from 'react-intl'; + +const ComposePanel = () => ( +
+ + + + +
+ +
+
    + {invitesEnabled &&
  • ·
  • } +
  • ·
  • +
  • ·
  • +
  • ·
  • +
  • ·
  • +
  • ·
  • +
  • ·
  • +
  • ·
  • +
  • +
+ +

+ {repository} (v{version}) }} + /> +

+
+
+); + +export default ComposePanel; diff --git a/app/javascript/flavours/glitch/features/ui/components/list_panel.js b/app/javascript/flavours/glitch/features/ui/components/list_panel.js new file mode 100644 index 000000000..50592d357 --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/list_panel.js @@ -0,0 +1,55 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import { fetchLists } from 'flavours/glitch/actions/lists'; +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; +import { NavLink, withRouter } from 'react-router-dom'; +import Icon from 'flavours/glitch/components/icon'; + +const getOrderedLists = createSelector([state => state.get('lists')], lists => { + if (!lists) { + return lists; + } + + return lists.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title'))); +}); + +const mapStateToProps = state => ({ + lists: getOrderedLists(state), +}); + +export default @withRouter +@connect(mapStateToProps) +class ListPanel extends ImmutablePureComponent { + + static propTypes = { + dispatch: PropTypes.func.isRequired, + lists: ImmutablePropTypes.list, + }; + + componentDidMount () { + const { dispatch } = this.props; + dispatch(fetchLists()); + } + + render () { + const { lists } = this.props; + + if (!lists) { + return null; + } + + return ( +
+
+ + {lists.map(list => ( + {list.get('title')} + ))} +
+ ); + } + +} diff --git a/app/javascript/flavours/glitch/features/ui/components/navigation_panel.js b/app/javascript/flavours/glitch/features/ui/components/navigation_panel.js new file mode 100644 index 000000000..68dde7c5c --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/navigation_panel.js @@ -0,0 +1,27 @@ +import React from 'react'; +import { NavLink, withRouter } from 'react-router-dom'; +import { FormattedMessage } from 'react-intl'; +import Icon from 'flavours/glitch/components/icon'; +import NotificationsCounterIcon from './notifications_counter_icon'; +import ListPanel from './list_panel'; + +const NavigationPanel = () => ( +
+ + + + + + + + + + +
+ + + +
+); + +export default withRouter(NavigationPanel); diff --git a/app/javascript/flavours/glitch/features/ui/components/notifications_counter_icon.js b/app/javascript/flavours/glitch/features/ui/components/notifications_counter_icon.js index 137658b94..25df35264 100644 --- a/app/javascript/flavours/glitch/features/ui/components/notifications_counter_icon.js +++ b/app/javascript/flavours/glitch/features/ui/components/notifications_counter_icon.js @@ -10,15 +10,17 @@ const mapStateToProps = state => ({ const formatNumber = num => num > 99 ? '99+' : num; -const NotificationsCounterIcon = ({ count, showBadge }) => ( +const NotificationsCounterIcon = ({ count, className, showBadge }) => ( - + {showBadge && count > 0 && {formatNumber(count)}} ); NotificationsCounterIcon.propTypes = { count: PropTypes.number.isRequired, + showBadge: PropTypes.bool, + className: PropTypes.string, }; export default connect(mapStateToProps)(NotificationsCounterIcon); 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 8963b16b3..dbd08aa2b 100644 --- a/app/javascript/flavours/glitch/features/ui/components/tabs_bar.js +++ b/app/javascript/flavours/glitch/features/ui/components/tabs_bar.js @@ -7,14 +7,13 @@ import { isUserTouching } from 'flavours/glitch/util/is_mobile'; import NotificationsCounterIcon from './notifications_counter_icon'; export const links = [ - , - , + , + , - , - , - , - - , + , + , + , + , ]; export function getIndex (path) { diff --git a/app/javascript/flavours/glitch/features/ui/index.js b/app/javascript/flavours/glitch/features/ui/index.js index 36156a5ef..f08b2dab8 100644 --- a/app/javascript/flavours/glitch/features/ui/index.js +++ b/app/javascript/flavours/glitch/features/ui/index.js @@ -44,10 +44,11 @@ import { Mutes, PinnedStatuses, Lists, + Search, GettingStartedMisc, } from 'flavours/glitch/util/async-components'; import { HotKeys } from 'react-hotkeys'; -import { me } from 'flavours/glitch/util/initial_state'; +import { me, forceSingleColumn } from 'flavours/glitch/util/initial_state'; import { defineMessages, injectIntl } from 'react-intl'; // Dummy import, to make sure that ends up in the application bundle. @@ -68,7 +69,6 @@ const mapStateToProps = state => ({ unreadNotifications: state.getIn(['notifications', 'unread']), showFaviconBadge: state.getIn(['local_settings', 'notifications', 'favicon_badge']), hicolorPrivacyIcons: state.getIn(['local_settings', 'hicolor_privacy_icons']), - forceSingleColumn: state.getIn(['settings', 'forceSingleColumn'], false), }); const keyMap = { @@ -126,7 +126,6 @@ export default class UI extends React.Component { dropdownMenuIsOpen: PropTypes.bool, unreadNotifications: PropTypes.number, showFaviconBadge: PropTypes.bool, - forceSingleColumn: PropTypes.bool, }; state = { @@ -432,7 +431,7 @@ export default class UI extends React.Component { render () { const { width, draggingOver } = this.state; - const { children, layout, isWide, navbarUnder, dropdownMenuIsOpen, forceSingleColumn } = this.props; + const { children, layout, isWide, navbarUnder, dropdownMenuIsOpen } = this.props; const singleColumn = forceSingleColumn || isMobile(width, layout); const redirect = singleColumn ? : ; @@ -494,7 +493,7 @@ export default class UI extends React.Component { - + diff --git a/app/javascript/flavours/glitch/reducers/settings.js b/app/javascript/flavours/glitch/reducers/settings.js index a568183df..a37863a69 100644 --- a/app/javascript/flavours/glitch/reducers/settings.js +++ b/app/javascript/flavours/glitch/reducers/settings.js @@ -15,8 +15,6 @@ const initialState = ImmutableMap({ skinTone: 1, - forceSingleColumn: false, - home: ImmutableMap({ shows: ImmutableMap({ reblog: true, diff --git a/app/javascript/flavours/glitch/styles/components/columns.scss b/app/javascript/flavours/glitch/styles/components/columns.scss index f372a4830..80d198ff7 100644 --- a/app/javascript/flavours/glitch/styles/components/columns.scss +++ b/app/javascript/flavours/glitch/styles/components/columns.scss @@ -26,7 +26,12 @@ display: flex; justify-content: flex-end; + &--start { + justify-content: flex-start; + } + &__inner { + width: 285px; pointer-events: auto; height: 100%; } @@ -178,9 +183,31 @@ padding: 15px; text-decoration: none; - &:hover { + &:hover, + &:focus, + &:active { background: lighten($ui-base-color, 11%); } + + &:focus { + outline: 0; + } + + &--transparent { + background: transparent; + color: $ui-secondary-color; + + &:hover, + &:focus, + &:active { + background: transparent; + color: $primary-text-color; + } + + &.active { + color: $ui-highlight-color; + } + } } .column-link__icon { @@ -506,31 +533,3 @@ margin: 0 5px; } } - -.floating-action-button { - position: fixed; - display: flex; - justify-content: center; - align-items: center; - width: 3.9375rem; - height: 3.9375rem; - bottom: 1.3125rem; - right: 1.3125rem; - background: darken($ui-highlight-color, 3%); - color: $white; - border-radius: 50%; - font-size: 21px; - line-height: 21px; - text-decoration: none; - box-shadow: 2px 3px 9px rgba($base-shadow-color, 0.4); - - &:hover, - &:focus, - &:active { - background: lighten($ui-highlight-color, 7%); - } - - @media screen and (min-width: 630px) { - display: none; - } -} diff --git a/app/javascript/flavours/glitch/styles/components/index.scss b/app/javascript/flavours/glitch/styles/components/index.scss index 53b678c9a..81098e52c 100644 --- a/app/javascript/flavours/glitch/styles/components/index.scss +++ b/app/javascript/flavours/glitch/styles/components/index.scss @@ -564,6 +564,7 @@ display: block; flex: 1 1 auto; padding: 15px 10px; + padding-bottom: 13px; color: $primary-text-color; text-decoration: none; text-align: center; @@ -588,6 +589,7 @@ &:active { @include multi-columns('screen and (min-width: 631px)') { background: lighten($ui-base-color, 14%); + border-bottom-color: lighten($ui-base-color, 14%); } } @@ -617,11 +619,21 @@ padding: 0; } - .search__input, .autosuggest-textarea__textarea { font-size: 16px; } + .search__input { + line-height: 18px; + font-size: 16px; + padding: 15px; + padding-right: 30px; + } + + .search__icon .fa { + top: 15px; + } + @media screen and (min-width: 360px) { padding: 10px 0; } @@ -677,6 +689,58 @@ margin-top: 10px; } } + + .account { + padding: 15px 10px; + } + + .notification { + &__message { + margin-left: 48px + 15px * 2; + padding-top: 15px; + } + + &__favourite-icon-wrapper { + left: -32px; + } + + .status { + padding-top: 8px; + } + + .account { + padding-top: 8px; + } + + .account__avatar-wrapper { + margin-left: 17px; + margin-right: 15px; + } + } + } +} + +.floating-action-button { + position: fixed; + display: flex; + justify-content: center; + align-items: center; + width: 3.9375rem; + height: 3.9375rem; + bottom: 1.3125rem; + right: 1.3125rem; + background: darken($ui-highlight-color, 3%); + color: $white; + border-radius: 50%; + font-size: 21px; + line-height: 21px; + text-decoration: none; + box-shadow: 2px 3px 9px rgba($base-shadow-color, 0.4); + + &:hover, + &:focus, + &:active { + background: lighten($ui-highlight-color, 7%); } } @@ -698,12 +762,41 @@ } } +@media screen and (max-width: 600px + (285px * 1) + (10px * 1)) { + .columns-area__panels__pane--compositional { + display: none; + } +} + +@media screen and (min-width: 600px + (285px * 1) + (10px * 1)) { + .floating-action-button, + .tabs-bar__link.optional { + display: none; + } + + .search-page .search { + display: none; + } +} + +@media screen and (max-width: 600px + (285px * 2) + (10px * 2)) { + .columns-area__panels__pane--navigational { + display: none; + } +} + +@media screen and (min-width: 600px + (285px * 2) + (10px * 2)) { + .tabs-bar { + display: none; + } +} + .icon-with-badge { position: relative; &__badge { position: absolute; - right: -13px; + left: 9px; top: -13px; background: $ui-highlight-color; border: 2px solid lighten($ui-base-color, 8%); @@ -716,6 +809,57 @@ } } +.column-link--transparent .icon-with-badge__badge { + border-color: darken($ui-base-color, 8%); +} + +.compose-panel { + width: 285px; + margin-top: 10px; + display: flex; + flex-direction: column; + height: 100%; + + .search__input { + line-height: 18px; + font-size: 16px; + padding: 15px; + padding-right: 30px; + } + + .search__icon .fa { + top: 15px; + } + + .navigation-bar { + padding-top: 20px; + padding-bottom: 20px; + } + + .flex-spacer { + background: transparent; + } + + .autosuggest-textarea__textarea { + max-height: 200px; + } + + .compose-form__upload-thumbnail { + height: 80px; + } +} + +.navigation-panel { + margin-top: 10px; + + hr { + border: 0; + background: transparent; + border-top: 1px solid lighten($ui-base-color, 4%); + margin: 10px 0; + } +} + .scrollable { overflow-y: scroll; overflow-x: hidden; @@ -1386,15 +1530,6 @@ height: 1em; } -.navigational-toggle { - padding: 10px; - display: flex; - align-items: center; - justify-content: space-between; - font-size: 14px; - color: $dark-text-color; -} - .layout-toggle { display: flex; padding: 5px; diff --git a/app/javascript/flavours/glitch/util/async-components.js b/app/javascript/flavours/glitch/util/async-components.js index 094952204..f2aeda834 100644 --- a/app/javascript/flavours/glitch/util/async-components.js +++ b/app/javascript/flavours/glitch/util/async-components.js @@ -149,3 +149,7 @@ export function GettingStartedMisc () { export function ListAdder () { return import(/* webpackChunkName: "features/glitch/async/list_adder" */'flavours/glitch/features/list_adder'); } + +export function Search () { + return import(/*webpackChunkName: "features/glitch/async/search" */'flavours/glitch/features/search'); +} diff --git a/app/javascript/flavours/glitch/util/initial_state.js b/app/javascript/flavours/glitch/util/initial_state.js index 99d8a4dbc..72fd2e6f4 100644 --- a/app/javascript/flavours/glitch/util/initial_state.js +++ b/app/javascript/flavours/glitch/util/initial_state.js @@ -28,5 +28,6 @@ export const version = getMeta('version'); export const mascot = getMeta('mascot'); export const isStaff = getMeta('is_staff'); export const defaultContentType = getMeta('default_content_type'); +export const forceSingleColumn = !getMeta('advanced_layout'); export default initialState; -- cgit From ff88387a4afff249c14511780c59e485a49631b8 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 26 May 2019 02:55:37 +0200 Subject: [Glitch] Improvements to the single column layout Port 0e445ebb1392c8dbce320509d219f16c7c221406 to glitch-soc Signed-off-by: Thibaut Girka --- app/javascript/flavours/glitch/actions/compose.js | 2 +- .../flavours/glitch/components/icon_with_badge.js | 20 ++++++++++ .../glitch/features/follow_requests/index.js | 2 +- .../glitch/features/getting_started/index.js | 19 ++++++++-- .../ui/components/follow_requests_nav_link.js | 44 ++++++++++++++++++++++ .../glitch/features/ui/components/list_panel.js | 4 +- .../features/ui/components/navigation_panel.js | 2 + .../ui/components/notifications_counter_icon.js | 25 ++---------- 8 files changed, 89 insertions(+), 29 deletions(-) create mode 100644 app/javascript/flavours/glitch/components/icon_with_badge.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/follow_requests_nav_link.js (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/actions/compose.js b/app/javascript/flavours/glitch/actions/compose.js index 093952316..69cc6827f 100644 --- a/app/javascript/flavours/glitch/actions/compose.js +++ b/app/javascript/flavours/glitch/actions/compose.js @@ -68,7 +68,7 @@ const messages = defineMessages({ uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' }, }); -const COMPOSE_PANEL_BREAKPOINT = 600 + (285 * 1) + (10 * 3); +const COMPOSE_PANEL_BREAKPOINT = 600 + (285 * 1) + (10 * 1); export const ensureComposeIsVisible = (getState, routerHistory) => { if (!getState().getIn(['compose', 'mounted']) && window.innerWidth < COMPOSE_PANEL_BREAKPOINT) { diff --git a/app/javascript/flavours/glitch/components/icon_with_badge.js b/app/javascript/flavours/glitch/components/icon_with_badge.js new file mode 100644 index 000000000..4a15ee5b4 --- /dev/null +++ b/app/javascript/flavours/glitch/components/icon_with_badge.js @@ -0,0 +1,20 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Icon from 'flavours/glitch/components/icon'; + +const formatNumber = num => num > 40 ? '40+' : num; + +const IconWithBadge = ({ id, count, className }) => ( + + + {count > 0 && {formatNumber(count)}} + +); + +IconWithBadge.propTypes = { + id: PropTypes.string.isRequired, + count: PropTypes.number.isRequired, + className: PropTypes.string, +}; + +export default IconWithBadge; diff --git a/app/javascript/flavours/glitch/features/follow_requests/index.js b/app/javascript/flavours/glitch/features/follow_requests/index.js index bce6338ea..d0845769e 100644 --- a/app/javascript/flavours/glitch/features/follow_requests/index.js +++ b/app/javascript/flavours/glitch/features/follow_requests/index.js @@ -59,7 +59,7 @@ export default class FollowRequests extends ImmutablePureComponent { } return ( - + diff --git a/app/javascript/flavours/glitch/features/getting_started/index.js b/app/javascript/flavours/glitch/features/getting_started/index.js index 7b645c9d0..b07789562 100644 --- a/app/javascript/flavours/glitch/features/getting_started/index.js +++ b/app/javascript/flavours/glitch/features/getting_started/index.js @@ -73,9 +73,15 @@ const badgeDisplay = (number, limit) => { } }; -@connect(makeMapStateToProps, mapDispatchToProps) -@injectIntl -export default class GettingStarted extends ImmutablePureComponent { +const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2); + + export default @connect(makeMapStateToProps, mapDispatchToProps) + @injectIntl + class GettingStarted extends ImmutablePureComponent { + + static contextTypes = { + router: PropTypes.object.isRequired, + }; static propTypes = { intl: PropTypes.object.isRequired, @@ -97,6 +103,11 @@ export default class GettingStarted extends ImmutablePureComponent { componentDidMount () { const { myAccount, fetchFollowRequests } = this.props; + if (window.innerWidth >= NAVIGATION_PANEL_BREAKPOINT) { + this.context.router.history.replace('/timelines/home'); + return; + } + if (myAccount.get('locked')) { fetchFollowRequests(); } @@ -135,7 +146,7 @@ export default class GettingStarted extends ImmutablePureComponent { } if (myAccount.get('locked')) { - navItems.push(); + navItems.push(); } navItems.push(); diff --git a/app/javascript/flavours/glitch/features/ui/components/follow_requests_nav_link.js b/app/javascript/flavours/glitch/features/ui/components/follow_requests_nav_link.js new file mode 100644 index 000000000..189f403bd --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/follow_requests_nav_link.js @@ -0,0 +1,44 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { fetchFollowRequests } from 'flavours/glitch/actions/accounts'; +import { connect } from 'react-redux'; +import { NavLink, withRouter } from 'react-router-dom'; +import IconWithBadge from 'flavours/glitch/components/icon_with_badge'; +import { me } from 'flavours/glitch/util/initial_state'; +import { List as ImmutableList } from 'immutable'; +import { FormattedMessage } from 'react-intl'; + +const mapStateToProps = state => ({ + locked: state.getIn(['accounts', me, 'locked']), + count: state.getIn(['user_lists', 'follow_requests', 'items'], ImmutableList()).size, +}); + +export default @withRouter +@connect(mapStateToProps) +class FollowRequestsNavLink extends React.Component { + + static propTypes = { + dispatch: PropTypes.func.isRequired, + locked: PropTypes.bool, + count: PropTypes.number.isRequired, + }; + + componentDidMount () { + const { dispatch, locked } = this.props; + + if (locked) { + dispatch(fetchFollowRequests()); + } + } + + render () { + const { locked, count } = this.props; + + if (!locked || count === 0) { + return null; + } + + return ; + } + +} diff --git a/app/javascript/flavours/glitch/features/ui/components/list_panel.js b/app/javascript/flavours/glitch/features/ui/components/list_panel.js index 50592d357..b2e6925b7 100644 --- a/app/javascript/flavours/glitch/features/ui/components/list_panel.js +++ b/app/javascript/flavours/glitch/features/ui/components/list_panel.js @@ -13,7 +13,7 @@ const getOrderedLists = createSelector([state => state.get('lists')], lists => { return lists; } - return lists.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title'))); + return lists.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title'))).take(4); }); const mapStateToProps = state => ({ @@ -37,7 +37,7 @@ class ListPanel extends ImmutablePureComponent { render () { const { lists } = this.props; - if (!lists) { + if (!lists || lists.isEmpty()) { return null; } 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 68dde7c5c..c75bffe4d 100644 --- a/app/javascript/flavours/glitch/features/ui/components/navigation_panel.js +++ b/app/javascript/flavours/glitch/features/ui/components/navigation_panel.js @@ -3,12 +3,14 @@ import { NavLink, withRouter } from 'react-router-dom'; import { FormattedMessage } from 'react-intl'; import Icon from 'flavours/glitch/components/icon'; import NotificationsCounterIcon from './notifications_counter_icon'; +import FollowRequestsNavLink from './follow_requests_nav_link'; import ListPanel from './list_panel'; const NavigationPanel = () => (
+ diff --git a/app/javascript/flavours/glitch/features/ui/components/notifications_counter_icon.js b/app/javascript/flavours/glitch/features/ui/components/notifications_counter_icon.js index 25df35264..679c02dce 100644 --- a/app/javascript/flavours/glitch/features/ui/components/notifications_counter_icon.js +++ b/app/javascript/flavours/glitch/features/ui/components/notifications_counter_icon.js @@ -1,26 +1,9 @@ -import React from 'react'; -import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import Icon from 'flavours/glitch/components/icon'; +import IconWithBadge from 'flavours/glitch/components/icon'; const mapStateToProps = state => ({ - count: state.getIn(['notifications', 'unread']), - showBadge: state.getIn(['local_settings', 'notifications', 'tab_badge']), + count: state.getIn(['local_settings', 'notifications', 'tab_badge']) ? state.getIn(['notifications', 'unread']) : 0, + icon: 'bell', }); -const formatNumber = num => num > 99 ? '99+' : num; - -const NotificationsCounterIcon = ({ count, className, showBadge }) => ( - - - {showBadge && count > 0 && {formatNumber(count)}} - -); - -NotificationsCounterIcon.propTypes = { - count: PropTypes.number.isRequired, - showBadge: PropTypes.bool, - className: PropTypes.string, -}; - -export default connect(mapStateToProps)(NotificationsCounterIcon); +export default connect(mapStateToProps)(IconWithBadge); -- cgit From c095eed121b672794d9bd31409c2659b1f2b128b Mon Sep 17 00:00:00 2001 From: Hanage999 Date: Sun, 26 May 2019 19:22:33 +0900 Subject: [Glitch] Fix wrong redirect from getting started to home in advanced Web UI Port 4a818ac2deffaff9925ce5b160dbc5385b815a87 to glitch-soc Signed-off-by: Thibaut Girka --- app/javascript/flavours/glitch/features/getting_started/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/features/getting_started/index.js b/app/javascript/flavours/glitch/features/getting_started/index.js index b07789562..0b04120a8 100644 --- a/app/javascript/flavours/glitch/features/getting_started/index.js +++ b/app/javascript/flavours/glitch/features/getting_started/index.js @@ -101,9 +101,9 @@ const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2); } componentDidMount () { - const { myAccount, fetchFollowRequests } = this.props; + const { myAccount, fetchFollowRequests, multiColumn } = this.props; - if (window.innerWidth >= NAVIGATION_PANEL_BREAKPOINT) { + if (!multiColumn && window.innerWidth >= NAVIGATION_PANEL_BREAKPOINT) { this.context.router.history.replace('/timelines/home'); return; } -- cgit From 9bb4f796db1159f5dc57b6d02527f1304f481932 Mon Sep 17 00:00:00 2001 From: abcang Date: Tue, 28 May 2019 04:56:29 +0900 Subject: [Glitch] Display notifications count on a new single column Port 3593b854233da1dbb49e95c929019c60a53eca79 to glitch-soc Signed-off-by: Thibaut Girka --- app/javascript/flavours/glitch/reducers/notifications.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/reducers/notifications.js b/app/javascript/flavours/glitch/reducers/notifications.js index 04ae3d406..5bbf9c822 100644 --- a/app/javascript/flavours/glitch/reducers/notifications.js +++ b/app/javascript/flavours/glitch/reducers/notifications.js @@ -27,7 +27,7 @@ import compareId from 'flavours/glitch/util/compare_id'; const initialState = ImmutableMap({ items: ImmutableList(), hasMore: true, - top: true, + top: false, mounted: 0, unread: 0, lastReadId: '0', -- cgit From 763735f92e4cf4fb311c8de9b62b9e5fc06f3c5d Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 27 May 2019 21:58:41 +0200 Subject: [Glitch] Refactor footers in web UI into a single component Port 451e5980b609eca5b20041963aca0098508475d7 to glitch-soc Signed-off-by: Thibaut Girka --- .../glitch/features/getting_started/index.js | 25 +++------------ .../glitch/features/ui/components/compose_panel.js | 26 ++-------------- .../glitch/features/ui/components/link_footer.js | 36 ++++++++++++++++++++++ 3 files changed, 42 insertions(+), 45 deletions(-) create mode 100644 app/javascript/flavours/glitch/features/ui/components/link_footer.js (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/features/getting_started/index.js b/app/javascript/flavours/glitch/features/getting_started/index.js index 0b04120a8..f669220e3 100644 --- a/app/javascript/flavours/glitch/features/getting_started/index.js +++ b/app/javascript/flavours/glitch/features/getting_started/index.js @@ -8,12 +8,13 @@ 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, invitesEnabled, version } from 'flavours/glitch/util/initial_state'; +import { me } from 'flavours/glitch/util/initial_state'; import { fetchFollowRequests } from 'flavours/glitch/actions/accounts'; import { List as ImmutableList } from 'immutable'; import { createSelector } from 'reselect'; import { fetchLists } from 'flavours/glitch/actions/lists'; -import { preferencesLink, profileLink, signOutLink } from 'flavours/glitch/util/backend_links'; +import { preferencesLink, signOutLink } from 'flavours/glitch/util/backend_links'; +import LinkFooter from 'flavours/glitch/features/ui/components/link_footer'; const messages = defineMessages({ heading: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, @@ -174,25 +175,7 @@ const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2);
-
-
    - {invitesEnabled &&
  • ·
  • } -
  • ·
  • -
  • ·
  • -
  • ·
  • -
  • -
- -

- glitch-soc/mastodon (v{version}), - Mastodon: Mastodon }} - /> -

-
+
); diff --git a/app/javascript/flavours/glitch/features/ui/components/compose_panel.js b/app/javascript/flavours/glitch/features/ui/components/compose_panel.js index 8acf89950..115f0620c 100644 --- a/app/javascript/flavours/glitch/features/ui/components/compose_panel.js +++ b/app/javascript/flavours/glitch/features/ui/components/compose_panel.js @@ -2,9 +2,7 @@ import React from 'react'; import SearchContainer from 'flavours/glitch/features/compose/containers/search_container'; import ComposeFormContainer from 'flavours/glitch/features/compose/containers/compose_form_container'; import NavigationContainer from 'flavours/glitch/features/compose/containers/navigation_container'; -import { invitesEnabled, version, repository, source_url } from 'mastodon/initial_state'; -import { Link } from 'react-router-dom'; -import { FormattedMessage } from 'react-intl'; +import LinkFooter from './link_footer'; const ComposePanel = () => (
@@ -14,27 +12,7 @@ const ComposePanel = () => (
-
-
    - {invitesEnabled &&
  • ·
  • } -
  • ·
  • -
  • ·
  • -
  • ·
  • -
  • ·
  • -
  • ·
  • -
  • ·
  • -
  • ·
  • -
  • -
- -

- {repository} (v{version}) }} - /> -

-
+
); diff --git a/app/javascript/flavours/glitch/features/ui/components/link_footer.js b/app/javascript/flavours/glitch/features/ui/components/link_footer.js new file mode 100644 index 000000000..3e724fffb --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/link_footer.js @@ -0,0 +1,36 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { FormattedMessage } from 'react-intl'; +import { Link } from 'react-router-dom'; +import { invitesEnabled, version, repository, source_url } from 'flavours/glitch/util/initial_state'; +import { signOutLink } from 'flavours/glitch/util/backend_links'; + +const LinkFooter = () => ( +
+
    + {invitesEnabled &&
  • ·
  • } +
  • ·
  • +
  • ·
  • +
  • ·
  • +
  • ·
  • +
  • ·
  • +
  • ·
  • +
  • +
+ +

+ glitch-soc/mastodon (v{version}), + Mastodon: Mastodon }} + /> +

+
+); + +LinkFooter.propTypes = { +}; + +export default LinkFooter; -- cgit From 02d6187894ab7d11a18d8f391c47ceca71cc5bfa Mon Sep 17 00:00:00 2001 From: Hanage999 Date: Sat, 1 Jun 2019 13:59:21 +0900 Subject: [Glitch] Center 2-columns layout without side effect Port 7c1ca0c37b7e68bd4e388afb3bca55d2b71fd2ad to glitch-soc Signed-off-by: Thibaut Girka --- app/javascript/flavours/glitch/styles/components/columns.scss | 1 - 1 file changed, 1 deletion(-) (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/styles/components/columns.scss b/app/javascript/flavours/glitch/styles/components/columns.scss index 80d198ff7..b8ecf647e 100644 --- a/app/javascript/flavours/glitch/styles/components/columns.scss +++ b/app/javascript/flavours/glitch/styles/components/columns.scss @@ -19,7 +19,6 @@ height: 100%; &__pane { - flex: 1 1 auto; height: 100%; overflow: hidden; pointer-events: none; -- cgit From 1329308bc716b32bf2de57c9302c1434acbfe980 Mon Sep 17 00:00:00 2001 From: Takeshi Umeda Date: Sun, 2 Jun 2019 17:05:54 +0900 Subject: [Glitch] Improvement variable height in single column layout Port d93b82af87de90eaa29eb54a423722fb9fb45b38 to glitch-soc Signed-off-by: Thibaut Girka --- .../glitch/components/autosuggest_textarea.js | 56 ++++++++++++---------- .../features/compose/components/compose_form.js | 45 +++++++++-------- .../glitch/features/ui/components/compose_panel.js | 3 -- .../flavours/glitch/features/ui/index.js | 2 +- .../glitch/styles/components/composer.scss | 17 ++++++- .../flavours/glitch/styles/components/index.scss | 27 ++++++++++- .../glitch/styles/mastodon-light/diff.scss | 2 +- 7 files changed, 94 insertions(+), 58 deletions(-) (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/components/autosuggest_textarea.js b/app/javascript/flavours/glitch/components/autosuggest_textarea.js index cf3907fbf..7a6b9d8ac 100644 --- a/app/javascript/flavours/glitch/components/autosuggest_textarea.js +++ b/app/javascript/flavours/glitch/components/autosuggest_textarea.js @@ -192,7 +192,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { } render () { - const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus } = this.props; + const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, children } = this.props; const { suggestionsHidden } = this.state; const style = { direction: 'ltr' }; @@ -200,34 +200,38 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { style.direction = 'rtl'; } - return ( -
-