Added App Setttings Modal

This commit is contained in:
kibigo!
2017-06-28 22:00:54 -07:00
parent 6cbbdc805f
commit 595c6de32c
19 changed files with 561 additions and 94 deletions

View File

@@ -4,6 +4,7 @@ import NavigationContainer from './containers/navigation_container';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { mountCompose, unmountCompose } from '../../actions/compose';
import { openModal } from '../../actions/modal';
import { changeLocalSetting } from '../../actions/local_settings';
import Link from 'react-router-dom/Link';
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
@@ -16,13 +17,13 @@ const messages = defineMessages({
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
public: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' },
community: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
settings: { id: 'navigation_bar.app_settings', defaultMessage: 'App settings' },
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
});
const mapStateToProps = state => ({
showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
layout: state.getIn(['localSettings', 'layout']),
layout: state.getIn(['local_settings', 'layout']),
});
@connect(mapStateToProps)
@@ -51,6 +52,10 @@ export default class Compose extends React.PureComponent {
e.preventDefault();
}
openSettings = () => {
this.props.dispatch(openModal('SETTINGS', {}));
}
render () {
const { multiColumn, showSearch, intl, layout } = this.props;
@@ -62,7 +67,7 @@ export default class Compose extends React.PureComponent {
<Link to='/getting-started' className='drawer__tab' title={intl.formatMessage(messages.start)}><i role='img' aria-label={intl.formatMessage(messages.start)} className='fa fa-fw fa-asterisk' /></Link>
<Link to='/timelines/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)}><i role='img' aria-label={intl.formatMessage(messages.community)} className='fa fa-fw fa-users' /></Link>
<Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)}><i role='img' aria-label={intl.formatMessage(messages.public)} className='fa fa-fw fa-globe' /></Link>
<a href='/settings/preferences' className='drawer__tab' title={intl.formatMessage(messages.preferences)}><i role='img' aria-label={intl.formatMessage(messages.preferences)} className='fa fa-fw fa-cog' /></a>
<a onClick={this.openSettings} role='button' tabIndex='0' className='drawer__tab' title={intl.formatMessage(messages.settings)}><i role='img' aria-label={intl.formatMessage(messages.settings)} className='fa fa-fw fa-cogs' /></a>
<a href='/auth/sign_out' className='drawer__tab' data-method='delete' title={intl.formatMessage(messages.logout)}><i role='img' aria-label={intl.formatMessage(messages.logout)} className='fa fa-fw fa-sign-out' /></a>
</div>
);

View File

@@ -4,6 +4,7 @@ import ColumnLink from '../ui/components/column_link';
import ColumnSubheading from '../ui/components/column_subheading';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import { openModal } from '../../actions/modal';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
@@ -17,6 +18,7 @@ const messages = defineMessages({
settings_subheading: { id: 'column_subheading.settings', defaultMessage: 'Settings' },
community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
settings: { id: 'navigation_bar.app_settings', defaultMessage: 'App settings' },
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
sign_out: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
@@ -39,8 +41,13 @@ export default class GettingStarted extends ImmutablePureComponent {
me: ImmutablePropTypes.map.isRequired,
columns: ImmutablePropTypes.list,
multiColumn: PropTypes.bool,
dispatch: PropTypes.func.isRequired,
};
openSettings = () => {
this.props.dispatch(openModal('SETTINGS', {}));
}
render () {
const { intl, me, columns, multiColumn } = this.props;
@@ -79,27 +86,30 @@ export default class GettingStarted extends ImmutablePureComponent {
return (
<Column icon='asterisk' heading={intl.formatMessage(messages.heading)} hideHeadingOnMobile>
<div className='getting-started__wrapper'>
<ColumnSubheading text={intl.formatMessage(messages.navigation_subheading)} />
{navItems}
<ColumnSubheading text={intl.formatMessage(messages.settings_subheading)} />
<ColumnLink icon='book' text={intl.formatMessage(messages.info)} href='/about/more' />
<ColumnLink icon='cog' text={intl.formatMessage(messages.preferences)} href='/settings/preferences' />
<ColumnLink icon='sign-out' text={intl.formatMessage(messages.sign_out)} href='/auth/sign_out' method='delete' />
</div>
<div className='scrollable optionally-scrollable'>
<div className='getting-started__wrapper'>
<ColumnSubheading text={intl.formatMessage(messages.navigation_subheading)} />
{navItems}
<ColumnSubheading text={intl.formatMessage(messages.settings_subheading)} />
<ColumnLink icon='book' text={intl.formatMessage(messages.info)} href='/about/more' />
<ColumnLink icon='cog' text={intl.formatMessage(messages.preferences)} href='/settings/preferences' />
<ColumnLink icon='cogs' text={intl.formatMessage(messages.settings)} onClick={this.openSettings} />
<ColumnLink icon='sign-out' text={intl.formatMessage(messages.sign_out)} href='/auth/sign_out' method='delete' />
</div>
<div className='getting-started__footer scrollable optionally-scrollable'>
<div className='static-content getting-started'>
<p>
<a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/FAQ.md' rel='noopener' target='_blank'><FormattedMessage id='getting_started.faq' defaultMessage='FAQ' /></a> • <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/User-guide.md' rel='noopener' target='_blank'><FormattedMessage id='getting_started.userguide' defaultMessage='User Guide' /></a> • <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md' rel='noopener' target='_blank'><FormattedMessage id='getting_started.appsshort' defaultMessage='Apps' /></a>
</p>
<p>
<FormattedMessage
id='getting_started.open_source_notice'
defaultMessage='Glitchsoc is open source software, a friendly fork of {Mastodon}. You can contribute or report issues on GitHub at {github}.'
values={{ github: <a href='https://github.com/glitch-soc/mastodon' rel='noopener' target='_blank'>glitch-soc/mastodon</a>, Mastodon: <a href='https://github.com/tootsuite/mastodon' rel='noopener' target='_blank'>Mastodon</a> }}
/>
</p>
<div className='getting-started__footer'>
<div className='static-content getting-started'>
<p>
<a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/FAQ.md' rel='noopener' target='_blank'><FormattedMessage id='getting_started.faq' defaultMessage='FAQ' /></a> • <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/User-guide.md' rel='noopener' target='_blank'><FormattedMessage id='getting_started.userguide' defaultMessage='User Guide' /></a> • <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md' rel='noopener' target='_blank'><FormattedMessage id='getting_started.appsshort' defaultMessage='Apps' /></a>
</p>
<p>
<FormattedMessage
id='getting_started.open_source_notice'
defaultMessage='Glitchsoc is open source software, a friendly fork of {Mastodon}. You can contribute or report issues on GitHub at {github}.'
values={{ github: <a href='https://github.com/glitch-soc/mastodon' rel='noopener' target='_blank'>glitch-soc/mastodon</a>, Mastodon: <a href='https://github.com/tootsuite/mastodon' rel='noopener' target='_blank'>Mastodon</a> }}
/>
</p>
</div>
</div>
</div>
</Column>

View File

@@ -12,6 +12,7 @@ export default class Notification extends ImmutablePureComponent {
static propTypes = {
notification: ImmutablePropTypes.map.isRequired,
settings: ImmutablePropTypes.map.isRequired,
};
renderFollow (account, link) {
@@ -34,7 +35,7 @@ export default class Notification extends ImmutablePureComponent {
return <StatusContainer id={notification.get('status')} withDismiss />;
}
renderFavourite (notification, link) {
renderFavourite (notification, settings, link) {
return (
<div className='notification notification-favourite'>
<div className='notification__message'>
@@ -44,12 +45,12 @@ export default class Notification extends ImmutablePureComponent {
<FormattedMessage id='notification.favourite' defaultMessage='{name} favourited your status' values={{ name: link }} />
</div>
<StatusContainer id={notification.get('status')} account={notification.get('account')} muted collapse withDismiss />
<StatusContainer id={notification.get('status')} account={notification.get('account')} muted collapse={settings.getIn(['collapsed', 'auto', 'notifications'])} withDismiss />
</div>
);
}
renderReblog (notification, link) {
renderReblog (notification, settings, link) {
return (
<div className='notification notification-reblog'>
<div className='notification__message'>
@@ -59,13 +60,13 @@ export default class Notification extends ImmutablePureComponent {
<FormattedMessage id='notification.reblog' defaultMessage='{name} boosted your status' values={{ name: link }} />
</div>
<StatusContainer id={notification.get('status')} account={notification.get('account')} muted collapse withDismiss />
<StatusContainer id={notification.get('status')} account={notification.get('account')} muted collapse={settings.getIn(['collapsed', 'auto', 'notifications'])} withDismiss />
</div>
);
}
render () {
const { notification } = this.props;
const { notification, settings } = this.props;
const account = notification.get('account');
const displayName = account.get('display_name').length > 0 ? account.get('display_name') : account.get('username');
const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
@@ -77,9 +78,9 @@ export default class Notification extends ImmutablePureComponent {
case 'mention':
return this.renderMention(notification);
case 'favourite':
return this.renderFavourite(notification, link);
return this.renderFavourite(notification, settings, link);
case 'reblog':
return this.renderReblog(notification, link);
return this.renderReblog(notification, settings, link);
}
return null;

View File

@@ -7,6 +7,7 @@ const makeMapStateToProps = () => {
const mapStateToProps = (state, props) => ({
notification: getNotification(state, props.notification, props.accountId),
settings: state.get('local_settings'),
});
return mapStateToProps;

View File

@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import Link from 'react-router-dom/Link';
const ColumnLink = ({ icon, text, to, href, method, hideOnMobile }) => {
const ColumnLink = ({ icon, text, to, onClick, href, method, hideOnMobile }) => {
if (href) {
return (
<a href={href} className={`column-link ${hideOnMobile ? 'hidden-on-mobile' : ''}`} data-method={method}>
@@ -10,13 +10,20 @@ const ColumnLink = ({ icon, text, to, href, method, hideOnMobile }) => {
{text}
</a>
);
} else {
} else if (to) {
return (
<Link to={to} className={`column-link ${hideOnMobile ? 'hidden-on-mobile' : ''}`}>
<i className={`fa fa-fw fa-${icon} column-link__icon`} />
{text}
</Link>
);
} else {
return (
<a onClick={onClick} role='button' tabIndex='0' className={`column-link ${hideOnMobile ? 'hidden-on-mobile' : ''}`} data-method={method}>
<i className={`fa fa-fw fa-${icon} column-link__icon`} />
{text}
</a>
);
}
};
@@ -24,6 +31,7 @@ ColumnLink.propTypes = {
icon: PropTypes.string.isRequired,
text: PropTypes.string.isRequired,
to: PropTypes.string,
onClick: PropTypes.func,
href: PropTypes.string,
method: PropTypes.string,
hideOnMobile: PropTypes.bool,

View File

@@ -6,6 +6,7 @@ import VideoModal from './video_modal';
import BoostModal from './boost_modal';
import ConfirmationModal from './confirmation_modal';
import ReportModal from './report_modal';
import SettingsModal from '../containers/settings_modal_container';
import TransitionMotion from 'react-motion/lib/TransitionMotion';
import spring from 'react-motion/lib/spring';
@@ -16,6 +17,7 @@ const MODAL_COMPONENTS = {
'BOOST': BoostModal,
'CONFIRM': ConfirmationModal,
'REPORT': ReportModal,
'SETTINGS': SettingsModal,
};
export default class ModalRoot extends React.PureComponent {

View File

@@ -0,0 +1,212 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { FormattedMessage } from 'react-intl';
class SettingsItem extends React.PureComponent {
static propTypes = {
settings: ImmutablePropTypes.map.isRequired,
item: PropTypes.array.isRequired,
id: PropTypes.string.isRequired,
dependsOn: PropTypes.array,
dependsOnNot: PropTypes.array,
children: PropTypes.element.isRequired,
onChange: PropTypes.func.isRequired,
};
handleChange = (e) => {
const { item, onChange } = this.props;
onChange(item, e);
}
render () {
const { settings, item, id, children, dependsOn, dependsOnNot } = this.props;
let enabled = true;
if (dependsOn) {
for (let i = 0; i < dependsOn.length; i++) {
enabled = enabled && settings.getIn(dependsOn[i]);
}
}
if (dependsOnNot) {
for (let i = 0; i < dependsOnNot.length; i++) {
enabled = enabled && !settings.getIn(dependsOnNot[i]);
}
}
return (
<label htmlFor={id}>
<input
id={id}
type='checkbox'
checked={settings.getIn(item)}
onChange={this.handleChange}
disabled={!enabled}
/>
{children}
</label>
);
}
}
export default class SettingsModal extends React.PureComponent {
static propTypes = {
settings: ImmutablePropTypes.map.isRequired,
toggleSetting: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
};
state = {
currentIndex: 0,
};
General = () => {
return (
<div>
<h1><FormattedMessage id='settings.general' defaultMessage='General' /></h1>
<SettingsItem
settings={this.props.settings}
item={['stretch']}
id='mastodon-settings--stretch'
onChange={this.props.toggleSetting}
>
<FormattedMessage id='settings.wide_view' defaultMessage='Wide view (Desktop mode only)' />
</SettingsItem>
</div>
);
}
CollapsedStatuses = () => {
return (
<div>
<h1><FormattedMessage id='settings.collapsed_statuses' defaultMessage='Collapsed toots' /></h1>
<SettingsItem
settings={this.props.settings}
item={['collapsed', 'enabled']}
id='mastodon-settings--collapsed-enabled'
onChange={this.props.toggleSetting}
>
<FormattedMessage id='settings.enable_collapsed' defaultMessage='Enable collapsed toots' />
</SettingsItem>
<section>
<h2><FormattedMessage id='settings.auto_collapse' defaultMessage='Automatic collapsing' /></h2>
<SettingsItem
settings={this.props.settings}
item={['collapsed', 'auto', 'all']}
id='mastodon-settings--collapsed-auto-all'
onChange={this.props.toggleSetting}
dependsOn={[['collapsed', 'enabled']]}
>
<FormattedMessage id='settings.auto_collapse_all' defaultMessage='Everything' />
</SettingsItem>
<SettingsItem
settings={this.props.settings}
item={['collapsed', 'auto', 'notifications']}
id='mastodon-settings--collapsed-auto-notifications'
onChange={this.props.toggleSetting}
dependsOn={[['collapsed', 'enabled']]}
dependsOnNot={[['collapsed', 'auto', 'all']]}
>
<FormattedMessage id='settings.auto_collapse_notifications' defaultMessage='Notifications' />
</SettingsItem>
<SettingsItem
settings={this.props.settings}
item={['collapsed', 'auto', 'lengthy']}
id='mastodon-settings--collapsed-auto-lengthy'
onChange={this.props.toggleSetting}
dependsOn={[['collapsed', 'enabled']]}
dependsOnNot={[['collapsed', 'auto', 'all']]}
>
<FormattedMessage id='settings.auto_collapse_lengthy' defaultMessage='Lengthy toots' />
</SettingsItem>
<SettingsItem
settings={this.props.settings}
item={['collapsed', 'auto', 'replies']}
id='mastodon-settings--collapsed-auto-replies'
onChange={this.props.toggleSetting}
dependsOn={[['collapsed', 'enabled']]}
dependsOnNot={[['collapsed', 'auto', 'all']]}
>
<FormattedMessage id='settings.auto_collapse_replies' defaultMessage='Replies' />
</SettingsItem>
<SettingsItem
settings={this.props.settings}
item={['collapsed', 'auto', 'media']}
id='mastodon-settings--collapsed-auto-media'
onChange={this.props.toggleSetting}
dependsOn={[['collapsed', 'enabled']]}
dependsOnNot={[['collapsed', 'auto', 'all']]}
>
<FormattedMessage id='settings.auto_collapse_media' defaultMessage='Toots with media' />
</SettingsItem>
</section>
<section>
<h2><FormattedMessage id='settings.image_backgrounds' defaultMessage='Image backgrounds' /></h2>
<SettingsItem
settings={this.props.settings}
item={['collapsed', 'backgrounds', 'user_backgrounds']}
id='mastodon-settings--collapsed-user-backgrouns'
onChange={this.props.toggleSetting}
dependsOn={[['collapsed', 'enabled']]}
>
<FormattedMessage id='settings.image_backgrounds_users' defaultMessage='Give collapsed toots an image background' />
</SettingsItem>
<SettingsItem
settings={this.props.settings}
item={['collapsed', 'backgrounds', 'preview_images']}
id='mastodon-settings--collapsed-preview-images'
onChange={this.props.toggleSetting}
dependsOn={[['collapsed', 'enabled']]}
>
<FormattedMessage id='settings.image_backgrounds_media' defaultMessage='Preview collapsed toot media' />
</SettingsItem>
</section>
</div>
);
}
navigateTo = (e) =>
this.setState({ currentIndex: +e.currentTarget.getAttribute('data-mastodon-navigation_index') });
render () {
const { General, CollapsedStatuses, navigateTo } = this;
const { onClose } = this.props;
const { currentIndex } = this.state;
return (
<div className='modal-root__modal settings-modal'>
<nav className='settings-modal__navigation'>
<a onClick={navigateTo} role='button' data-mastodon-navigation_index='0' tabIndex='0' className={`settings-modal__navigation-item${currentIndex === 0 ? ' active' : ''}`}>
<FormattedMessage id='settings.general' defaultMessage='General' />
</a>
<a onClick={navigateTo} role='button' data-mastodon-navigation_index='1' tabIndex='0' className={`settings-modal__navigation-item${currentIndex === 1 ? ' active' : ''}`}>
<FormattedMessage id='settings.collapsed_statuses' defaultMessage='Collapsed toots' />
</a>
<a href='/settings/preferences' className='settings-modal__navigation-item'>
<i className='fa fa-fw fa-cogs' /> <FormattedMessage id='settings.preferences' defaultMessage='User preferences' />
</a>
<a onClick={onClose} role='button' tabIndex='0' className='settings-modal__navigation-close'>
<FormattedMessage id='settings.close' defaultMessage='Close' />
</a>
</nav>
<div className='settings-modal__content'>
{
[
<General />,
<CollapsedStatuses />,
][currentIndex] || <General />
}
</div>
</div>
);
}
}

View File

@@ -0,0 +1,19 @@
import { connect } from 'react-redux';
import { changeLocalSetting } from '../../../actions/local_settings';
import { closeModal } from '../../../actions/modal';
import SettingsModal from '../components/settings_modal';
const mapStateToProps = state => ({
settings: state.get('local_settings'),
});
const mapDispatchToProps = dispatch => ({
toggleSetting (setting, e) {
dispatch(changeLocalSetting(setting, e.target.checked));
},
onClose () {
dispatch(closeModal());
},
});
export default connect(mapStateToProps, mapDispatchToProps)(SettingsModal);

View File

@@ -73,7 +73,8 @@ class WrappedRoute extends React.Component {
}
const mapStateToProps = state => ({
layout: state.getIn(['localSettings', 'layout']),
layout: state.getIn(['local_settings', 'layout']),
isWide: state.getIn(['local_settings', 'stretch']),
});
@connect(mapStateToProps)
@@ -83,6 +84,7 @@ export default class UI extends React.PureComponent {
dispatch: PropTypes.func.isRequired,
children: PropTypes.node,
layout: PropTypes.string,
isWide: PropTypes.bool,
};
state = {
@@ -179,7 +181,7 @@ export default class UI extends React.PureComponent {
render () {
const { width, draggingOver } = this.state;
const { children, layout } = this.props;
const { children, layout, isWide } = this.props;
const columnsClass = layout => {
switch (layout) {
@@ -193,7 +195,7 @@ export default class UI extends React.PureComponent {
};
return (
<div className={'ui ' + columnsClass(layout)} ref={this.setRef}>
<div className={'ui ' + columnsClass(layout) + (isWide ? ' wide' : '')} ref={this.setRef}>
<TabsBar />
<ColumnsAreaContainer singleColumn={isMobile(width, layout)}>
<WrappedSwitch>