Merge remote-tracking branch 'upstream/main'

This commit is contained in:
Essem 2024-01-14 15:41:16 -06:00
parent 7374678aa7
commit ac71680dcb
No known key found for this signature in database
GPG key ID: 7D497397CC3A2A8C
166 changed files with 2081 additions and 1540 deletions

View file

@ -714,6 +714,21 @@ export function fetchPinnedAccountsFail(error) {
}; };
} }
export const updateAccount = ({ displayName, note, avatar, header, discoverable, indexable }) => (dispatch, getState) => {
const data = new FormData();
data.append('display_name', displayName);
data.append('note', note);
if (avatar) data.append('avatar', avatar);
if (header) data.append('header', header);
data.append('discoverable', discoverable);
data.append('indexable', indexable);
return api(getState).patch('/api/v1/accounts/update_credentials', data).then(response => {
dispatch(importFetchedAccount(response.data));
});
};
export function fetchPinnedAccountsSuggestions(q) { export function fetchPinnedAccountsSuggestions(q) {
return (dispatch, getState) => { return (dispatch, getState) => {
dispatch(fetchPinnedAccountsSuggestionsRequest()); dispatch(fetchPinnedAccountsSuggestionsRequest());

View file

@ -20,6 +20,7 @@ export interface ApiAccountJSON {
bot: boolean; bot: boolean;
created_at: string; created_at: string;
discoverable: boolean; discoverable: boolean;
indexable: boolean;
display_name: string; display_name: string;
emojis: ApiCustomEmojiJSON[]; emojis: ApiCustomEmojiJSON[];
fields: ApiAccountFieldJSON[]; fields: ApiAccountFieldJSON[];

View file

@ -51,7 +51,7 @@ export default class Retention extends PureComponent {
let content; let content;
if (loading) { if (loading) {
content = <FormattedMessage id='loading_indicator.label' defaultMessage='Loading...' />; content = <FormattedMessage id='loading_indicator.label' defaultMessage='Loading' />;
} else { } else {
content = ( content = (
<table className='retention__table'> <table className='retention__table'>

View file

@ -7,6 +7,8 @@ import classNames from 'classnames';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { ReactComponent as LinkIcon } from '@material-symbols/svg-600/outlined/link.svg';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
const filename = url => url.split('/').pop().split('#')[0].split('?')[0]; const filename = url => url.split('/').pop().split('#')[0].split('?')[0];
@ -25,7 +27,7 @@ export default class AttachmentList extends ImmutablePureComponent {
<div className={classNames('attachment-list', { compact })}> <div className={classNames('attachment-list', { compact })}>
{!compact && ( {!compact && (
<div className='attachment-list__icon'> <div className='attachment-list__icon'>
<Icon id='link' /> <Icon id='link' icon={LinkIcon} />
</div> </div>
)} )}
@ -36,7 +38,7 @@ export default class AttachmentList extends ImmutablePureComponent {
return ( return (
<li key={attachment.get('id')}> <li key={attachment.get('id')}>
<a href={displayUrl} target='_blank' rel='noopener noreferrer'> <a href={displayUrl} target='_blank' rel='noopener noreferrer'>
{compact && <Icon id='link' />} {compact && <Icon id='link' icon={LinkIcon} />}
{compact && ' ' } {compact && ' ' }
{displayUrl ? filename(displayUrl) : <FormattedMessage id='attachments_list.unprocessed' defaultMessage='(unprocessed)' />} {displayUrl ? filename(displayUrl) : <FormattedMessage id='attachments_list.unprocessed' defaultMessage='(unprocessed)' />}
</a> </a>

View file

@ -1,13 +0,0 @@
export const Check: React.FC = () => (
<svg
xmlns='http://www.w3.org/2000/svg'
viewBox='0 0 20 20'
fill='currentColor'
>
<path
fillRule='evenodd'
d='M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z'
clipRule='evenodd'
/>
</svg>
);

View file

@ -1,63 +0,0 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import { createPortal } from 'react-dom';
import { FormattedMessage } from 'react-intl';
import { withRouter } from 'react-router-dom';
import { Icon } from 'flavours/glitch/components/icon';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
export class ColumnBackButton extends PureComponent {
static propTypes = {
multiColumn: PropTypes.bool,
onClick: PropTypes.func,
...WithRouterPropTypes,
};
handleClick = () => {
const { onClick, history } = this.props;
if (onClick) {
onClick();
} else if (history.location?.state?.fromMastodon) {
history.goBack();
} else {
history.push('/');
}
};
render () {
const { multiColumn } = this.props;
const component = (
<button onClick={this.handleClick} className='column-back-button'>
<Icon id='chevron-left' className='column-back-button__icon' fixedWidth />
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
</button>
);
if (multiColumn) {
return component;
} else {
// The portal container and the component may be rendered to the DOM in
// the same React render pass, so the container might not be available at
// the time `render()` is called.
const container = document.getElementById('tabs-bar__portal');
if (container === null) {
// The container wasn't available, force a re-render so that the
// component can eventually be inserted in the container and not scroll
// with the rest of the area.
this.forceUpdate();
return component;
} else {
return createPortal(component, container);
}
}
}
}
export default withRouter(ColumnBackButton);

View file

@ -0,0 +1,45 @@
import { useCallback } from 'react';
import { FormattedMessage } from 'react-intl';
import { ReactComponent as ArrowBackIcon } from '@material-symbols/svg-600/outlined/arrow_back.svg';
import { Icon } from 'flavours/glitch/components/icon';
import { ButtonInTabsBar } from 'flavours/glitch/features/ui/util/columns_context';
import { useAppHistory } from './router';
type OnClickCallback = () => void;
function useHandleClick(onClick?: OnClickCallback) {
const history = useAppHistory();
return useCallback(() => {
if (onClick) {
onClick();
} else if (history.location.state?.fromMastodon) {
history.goBack();
} else {
history.push('/');
}
}, [history, onClick]);
}
export const ColumnBackButton: React.FC<{ onClick: OnClickCallback }> = ({
onClick,
}) => {
const handleClick = useHandleClick(onClick);
const component = (
<button onClick={handleClick} className='column-back-button'>
<Icon
id='chevron-left'
icon={ArrowBackIcon}
className='column-back-button__icon'
/>
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
</button>
);
return <ButtonInTabsBar>{component}</ButtonInTabsBar>;
};

View file

@ -1,40 +0,0 @@
import { PureComponent } from 'react';
import { FormattedMessage } from 'react-intl';
import { withRouter } from 'react-router-dom';
import { Icon } from 'flavours/glitch/components/icon';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
class ColumnBackButtonSlim extends PureComponent {
static propTypes = {
...WithRouterPropTypes,
};
handleClick = () => {
const { location, history } = this.props;
// Check if there is a previous page in the app to go back to per https://stackoverflow.com/a/70532858/9703201
// When upgrading to V6, check `location.key !== 'default'` instead per https://github.com/remix-run/history/blob/main/docs/api-reference.md#location
if (location.key) {
history.goBack();
} else {
history.push('/');
}
};
render () {
return (
<div className='column-back-button--slim'>
<div role='button' tabIndex={0} onClick={this.handleClick} className='column-back-button column-back-button--slim-button'>
<Icon id='chevron-left' className='column-back-button__icon' fixedWidth />
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
</div>
</div>
);
}
}
export default withRouter(ColumnBackButtonSlim);

View file

@ -1,15 +1,24 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { PureComponent } from 'react'; import { PureComponent, useCallback } from 'react';
import { createPortal } from 'react-dom';
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl'; import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { withRouter } from 'react-router-dom'; import { withRouter } from 'react-router-dom';
import { ReactComponent as AddIcon } from '@material-symbols/svg-600/outlined/add.svg';
import { ReactComponent as ArrowBackIcon } from '@material-symbols/svg-600/outlined/arrow_back.svg';
import { ReactComponent as ChevronLeftIcon } from '@material-symbols/svg-600/outlined/chevron_left.svg';
import { ReactComponent as ChevronRightIcon } from '@material-symbols/svg-600/outlined/chevron_right.svg';
import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg';
import { ReactComponent as TuneIcon } from '@material-symbols/svg-600/outlined/tune.svg';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { ButtonInTabsBar, useColumnsContext } from 'flavours/glitch/features/ui/util/columns_context';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
import { useAppHistory } from './router';
const messages = defineMessages({ const messages = defineMessages({
show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' }, show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' },
hide: { id: 'column_header.hide_settings', defaultMessage: 'Hide settings' }, hide: { id: 'column_header.hide_settings', defaultMessage: 'Hide settings' },
@ -17,6 +26,34 @@ const messages = defineMessages({
moveRight: { id: 'column_header.moveRight_settings', defaultMessage: 'Move column to the right' }, moveRight: { id: 'column_header.moveRight_settings', defaultMessage: 'Move column to the right' },
}); });
const BackButton = ({ pinned, show }) => {
const history = useAppHistory();
const { multiColumn } = useColumnsContext();
const handleBackClick = useCallback(() => {
if (history.location?.state?.fromMastodon) {
history.goBack();
} else {
history.push('/');
}
}, [history]);
const showButton = history && !pinned && ((multiColumn && history.location?.state?.fromMastodon) || show);
if(!showButton) return null;
return (<button onClick={handleBackClick} className='column-header__back-button'>
<Icon id='chevron-left' icon={ArrowBackIcon} className='column-back-button__icon' />
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
</button>);
};
BackButton.propTypes = {
pinned: PropTypes.bool,
show: PropTypes.bool,
};
class ColumnHeader extends PureComponent { class ColumnHeader extends PureComponent {
static contextTypes = { static contextTypes = {
@ -27,6 +64,7 @@ class ColumnHeader extends PureComponent {
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
title: PropTypes.node, title: PropTypes.node,
icon: PropTypes.string, icon: PropTypes.string,
iconComponent: PropTypes.func,
active: PropTypes.bool, active: PropTypes.bool,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
extraButton: PropTypes.node, extraButton: PropTypes.node,
@ -64,16 +102,6 @@ class ColumnHeader extends PureComponent {
this.props.onMove(1); this.props.onMove(1);
}; };
handleBackClick = () => {
const { history } = this.props;
if (history.location?.state?.fromMastodon) {
history.goBack();
} else {
history.push('/');
}
};
handleTransitionEnd = () => { handleTransitionEnd = () => {
this.setState({ animating: false }); this.setState({ animating: false });
}; };
@ -87,7 +115,7 @@ class ColumnHeader extends PureComponent {
}; };
render () { render () {
const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues, history } = this.props; const { title, icon, iconComponent, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues } = this.props;
const { collapsed, animating } = this.state; const { collapsed, animating } = this.state;
const wrapperClassName = classNames('column-header__wrapper', { const wrapperClassName = classNames('column-header__wrapper', {
@ -118,26 +146,19 @@ class ColumnHeader extends PureComponent {
} }
if (multiColumn && pinned) { if (multiColumn && pinned) {
pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><Icon id='times' /> <FormattedMessage id='column_header.unpin' defaultMessage='Unpin' /></button>; pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><Icon id='times' icon={CloseIcon} /> <FormattedMessage id='column_header.unpin' defaultMessage='Unpin' /></button>;
moveButtons = ( moveButtons = (
<div key='move-buttons' className='column-header__setting-arrows'> <div key='move-buttons' className='column-header__setting-arrows'>
<button title={formatMessage(messages.moveLeft)} aria-label={formatMessage(messages.moveLeft)} className='icon-button column-header__setting-btn' onClick={this.handleMoveLeft}><Icon id='chevron-left' /></button> <button title={formatMessage(messages.moveLeft)} aria-label={formatMessage(messages.moveLeft)} className='icon-button column-header__setting-btn' onClick={this.handleMoveLeft}><Icon id='chevron-left' icon={ChevronLeftIcon} /></button>
<button title={formatMessage(messages.moveRight)} aria-label={formatMessage(messages.moveRight)} className='icon-button column-header__setting-btn' onClick={this.handleMoveRight}><Icon id='chevron-right' /></button> <button title={formatMessage(messages.moveRight)} aria-label={formatMessage(messages.moveRight)} className='icon-button column-header__setting-btn' onClick={this.handleMoveRight}><Icon id='chevron-right' icon={ChevronRightIcon} /></button>
</div> </div>
); );
} else if (multiColumn && this.props.onPin) { } else if (multiColumn && this.props.onPin) {
pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><Icon id='plus' /> <FormattedMessage id='column_header.pin' defaultMessage='Pin' /></button>; pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><Icon id='plus' icon={AddIcon} /> <FormattedMessage id='column_header.pin' defaultMessage='Pin' /></button>;
} }
if (!pinned && ((multiColumn && history.location?.state?.fromMastodon) || showBackButton)) { backButton = <BackButton pinned={pinned} show={showBackButton} />;
backButton = (
<button onClick={this.handleBackClick} className='column-header__back-button'>
<Icon id='chevron-left' className='column-back-button__icon' fixedWidth />
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
</button>
);
}
const collapsedContent = [ const collapsedContent = [
extraContent, extraContent,
@ -157,21 +178,21 @@ class ColumnHeader extends PureComponent {
onClick={this.handleToggleClick} onClick={this.handleToggleClick}
> >
<i className='icon-with-badge'> <i className='icon-with-badge'>
<Icon id='sliders' /> <Icon id='sliders' icon={TuneIcon} />
{collapseIssues && <i className='icon-with-badge__issue-badge' />} {collapseIssues && <i className='icon-with-badge__issue-badge' />}
</i> </i>
</button> </button>
); );
} }
const hasTitle = icon && title; const hasTitle = (icon || iconComponent) && title;
const component = ( const component = (
<div className={wrapperClassName}> <div className={wrapperClassName}>
<h1 className={buttonClassName}> <h1 className={buttonClassName}>
{hasTitle && ( {hasTitle && (
<button onClick={this.handleTitleClick}> <button onClick={this.handleTitleClick}>
<Icon id={icon} fixedWidth className='column-header__icon' /> <Icon id={icon} icon={iconComponent} className='column-header__icon' />
{title} {title}
</button> </button>
)} )}
@ -195,22 +216,12 @@ class ColumnHeader extends PureComponent {
</div> </div>
); );
if (multiColumn || placeholder) { if (placeholder) {
return component; return component;
} else { } else {
// The portal container and the component may be rendered to the DOM in return (<ButtonInTabsBar>
// the same React render pass, so the container might not be available at {component}
// the time `render()` is called. </ButtonInTabsBar>);
const container = document.getElementById('tabs-bar__portal');
if (container === null) {
// The container wasn't available, force a re-render so that the
// component can eventually be inserted in the container and not scroll
// with the rest of the area.
this.forceUpdate();
return component;
} else {
return createPortal(component, container);
}
} }
} }

View file

@ -8,6 +8,8 @@ import { useCallback, useState, useEffect } from 'react';
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg';
import { changeSetting } from 'flavours/glitch/actions/settings'; import { changeSetting } from 'flavours/glitch/actions/settings';
import { bannerSettings } from 'flavours/glitch/settings'; import { bannerSettings } from 'flavours/glitch/settings';
import { useAppSelector, useAppDispatch } from 'flavours/glitch/store'; import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
@ -55,6 +57,7 @@ export const DismissableBanner: React.FC<PropsWithChildren<Props>> = ({
<div className='dismissable-banner__action'> <div className='dismissable-banner__action'>
<IconButton <IconButton
icon='times' icon='times'
iconComponent={CloseIcon}
title={intl.formatMessage(messages.dismiss)} title={intl.formatMessage(messages.dismiss)}
onClick={handleDismiss} onClick={handleDismiss}
/> />

View file

@ -2,6 +2,8 @@ import { useCallback } from 'react';
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
import { ReactComponent as LockOpenIcon } from '@material-symbols/svg-600/outlined/lock_open.svg';
import { IconButton } from './icon_button'; import { IconButton } from './icon_button';
const messages = defineMessages({ const messages = defineMessages({
@ -34,6 +36,7 @@ export const Domain: React.FC<Props> = ({ domain, onUnblockDomain }) => {
<IconButton <IconButton
active active
icon='unlock' icon='unlock'
iconComponent={LockOpenIcon}
title={intl.formatMessage(messages.unblockDomain, { domain })} title={intl.formatMessage(messages.unblockDomain, { domain })}
onClick={handleDomainUnblock} onClick={handleDomainUnblock}
/> />

View file

@ -6,6 +6,7 @@ import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg';
import { supportsPassiveEvents } from 'detect-passive-events'; import { supportsPassiveEvents } from 'detect-passive-events';
import Overlay from 'react-overlays/Overlay'; import Overlay from 'react-overlays/Overlay';
@ -163,6 +164,7 @@ class Dropdown extends PureComponent {
static propTypes = { static propTypes = {
children: PropTypes.node, children: PropTypes.node,
icon: PropTypes.string, icon: PropTypes.string,
iconComponent: PropTypes.func,
items: PropTypes.oneOfType([PropTypes.array, ImmutablePropTypes.list]).isRequired, items: PropTypes.oneOfType([PropTypes.array, ImmutablePropTypes.list]).isRequired,
loading: PropTypes.bool, loading: PropTypes.bool,
size: PropTypes.number, size: PropTypes.number,
@ -255,7 +257,7 @@ class Dropdown extends PureComponent {
}; };
findTarget = () => { findTarget = () => {
return this.target; return this.target?.buttonRef?.current ?? this.target;
}; };
componentWillUnmount = () => { componentWillUnmount = () => {
@ -271,6 +273,7 @@ class Dropdown extends PureComponent {
render () { render () {
const { const {
icon, icon,
iconComponent,
items, items,
size, size,
title, title,
@ -291,9 +294,11 @@ class Dropdown extends PureComponent {
onMouseDown: this.handleMouseDown, onMouseDown: this.handleMouseDown,
onKeyDown: this.handleButtonKeyDown, onKeyDown: this.handleButtonKeyDown,
onKeyPress: this.handleKeyPress, onKeyPress: this.handleKeyPress,
ref: this.setTargetRef,
}) : ( }) : (
<IconButton <IconButton
icon={icon} icon={!open ? icon : 'close'}
iconComponent={!open ? iconComponent : CloseIcon}
title={title} title={title}
active={open} active={open}
disabled={disabled} disabled={disabled}
@ -302,14 +307,14 @@ class Dropdown extends PureComponent {
onMouseDown={this.handleMouseDown} onMouseDown={this.handleMouseDown}
onKeyDown={this.handleButtonKeyDown} onKeyDown={this.handleButtonKeyDown}
onKeyPress={this.handleKeyPress} onKeyPress={this.handleKeyPress}
ref={this.setTargetRef}
/> />
); );
return ( return (
<> <>
<span ref={this.setTargetRef}>
{button} {button}
</span>
<Overlay show={open} offset={[5, 5]} placement={'bottom'} flip target={this.findTarget} popperConfig={{ strategy: 'fixed' }}> <Overlay show={open} offset={[5, 5]} placement={'bottom'} flip target={this.findTarget} popperConfig={{ strategy: 'fixed' }}>
{({ props, arrowProps, placement }) => ( {({ props, arrowProps, placement }) => (
<div {...props}> <div {...props}>

View file

@ -5,6 +5,8 @@ import { FormattedMessage, injectIntl } from 'react-intl';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as ArrowDropDownIcon } from '@material-symbols/svg-600/outlined/arrow_drop_down.svg';
import { openModal } from 'flavours/glitch/actions/modal'; import { openModal } from 'flavours/glitch/actions/modal';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import InlineAccount from 'flavours/glitch/components/inline_account'; import InlineAccount from 'flavours/glitch/components/inline_account';
@ -66,7 +68,7 @@ class EditedTimestamp extends PureComponent {
return ( return (
<DropdownMenu statusId={statusId} renderItem={this.renderItem} scrollable renderHeader={this.renderHeader} onItemClick={this.handleItemClick}> <DropdownMenu statusId={statusId} renderItem={this.renderItem} scrollable renderHeader={this.renderHeader} onItemClick={this.handleItemClick}>
<button className='dropdown-menu__text-button'> <button className='dropdown-menu__text-button'>
<FormattedMessage id='status.edited' defaultMessage='Edited {date}' values={{ date: intl.formatDate(timestamp, { hour12: false, month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) }} /> <Icon id='caret-down' /> <FormattedMessage id='status.edited' defaultMessage='Edited {date}' values={{ date: intl.formatDate(timestamp, { hour12: false, month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) }} /> <Icon id='caret-down' icon={ArrowDropDownIcon} />
</button> </button>
</DropdownMenu> </DropdownMenu>
); );

View file

@ -1,20 +1,52 @@
import classNames from 'classnames'; import classNames from 'classnames';
interface Props extends React.HTMLAttributes<HTMLImageElement> { import { ReactComponent as CheckBoxOutlineBlankIcon } from '@material-symbols/svg-600/outlined/check_box_outline_blank.svg';
id: string;
className?: string; interface SVGPropsWithTitle extends React.SVGProps<SVGSVGElement> {
fixedWidth?: boolean; title?: string;
}
export type IconProp = React.FC<SVGPropsWithTitle>;
interface Props extends React.SVGProps<SVGSVGElement> {
children?: never; children?: never;
id: string;
icon: IconProp;
title?: string;
} }
export const Icon: React.FC<Props> = ({ export const Icon: React.FC<Props> = ({
id, id,
icon: IconComponent,
className, className,
fixedWidth, title: titleProp,
...other ...other
}) => ( }) => {
<i // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
className={classNames('fa', `fa-${id}`, className, { 'fa-fw': fixedWidth })} if (!IconComponent) {
if (process.env.NODE_ENV !== 'production') {
throw new Error(
`<Icon id="${id}" className="${className}"> is missing an "icon" prop.`,
);
}
IconComponent = CheckBoxOutlineBlankIcon;
}
const ariaHidden = titleProp ? undefined : true;
const role = !ariaHidden ? 'img' : undefined;
// Set the title to an empty string to remove the built-in SVG one if any
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const title = titleProp || '';
return (
<IconComponent
className={classNames('icon', `icon-${id}`, className)}
title={title}
aria-hidden={ariaHidden}
role={role}
{...other} {...other}
/> />
); );
};

View file

@ -1,19 +1,20 @@
import { PureComponent } from 'react'; import { PureComponent, createRef } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { AnimatedNumber } from './animated_number'; import { AnimatedNumber } from './animated_number';
import type { IconProp } from './icon';
import { Icon } from './icon'; import { Icon } from './icon';
interface Props { interface Props {
className?: string; className?: string;
title: string; title: string;
icon: string; icon: string;
iconComponent: IconProp;
onClick?: React.MouseEventHandler<HTMLButtonElement>; onClick?: React.MouseEventHandler<HTMLButtonElement>;
onMouseDown?: React.MouseEventHandler<HTMLButtonElement>; onMouseDown?: React.MouseEventHandler<HTMLButtonElement>;
onKeyDown?: React.KeyboardEventHandler<HTMLButtonElement>; onKeyDown?: React.KeyboardEventHandler<HTMLButtonElement>;
onKeyPress?: React.KeyboardEventHandler<HTMLButtonElement>; onKeyPress?: React.KeyboardEventHandler<HTMLButtonElement>;
size: number;
active: boolean; active: boolean;
expanded?: boolean; expanded?: boolean;
style?: React.CSSProperties; style?: React.CSSProperties;
@ -34,8 +35,9 @@ interface States {
deactivate: boolean; deactivate: boolean;
} }
export class IconButton extends PureComponent<Props, States> { export class IconButton extends PureComponent<Props, States> {
buttonRef = createRef<HTMLButtonElement>();
static defaultProps = { static defaultProps = {
size: 18,
active: false, active: false,
disabled: false, disabled: false,
animate: false, animate: false,
@ -86,24 +88,10 @@ export class IconButton extends PureComponent<Props, States> {
}; };
render() { render() {
// Hack required for some icons which have an overriden size
let containerSize = '1.28571429em';
if (this.props.style?.fontSize) {
containerSize = `${this.props.size * 1.28571429}px`;
}
const style = { const style = {
fontSize: `${this.props.size}px`,
height: containerSize,
lineHeight: `${this.props.size}px`,
...this.props.style, ...this.props.style,
...(this.props.active ? this.props.activeStyle : {}), ...(this.props.active ? this.props.activeStyle : {}),
}; };
if (!this.props.label) {
style.width = containerSize;
} else {
style.textAlign = 'left';
}
const { const {
active, active,
@ -111,6 +99,7 @@ export class IconButton extends PureComponent<Props, States> {
disabled, disabled,
expanded, expanded,
icon, icon,
iconComponent,
inverted, inverted,
overlay, overlay,
tabIndex, tabIndex,
@ -133,13 +122,9 @@ export class IconButton extends PureComponent<Props, States> {
'icon-button--with-counter': typeof counter !== 'undefined', 'icon-button--with-counter': typeof counter !== 'undefined',
}); });
if (typeof counter !== 'undefined') {
style.width = 'auto';
}
let contents = ( let contents = (
<> <>
<Icon id={icon} fixedWidth aria-hidden='true' />{' '} <Icon id={icon} icon={iconComponent} aria-hidden='true' />{' '}
{typeof counter !== 'undefined' && ( {typeof counter !== 'undefined' && (
<span className='icon-button__counter'> <span className='icon-button__counter'>
<AnimatedNumber value={counter} obfuscate={obfuscateCount} /> <AnimatedNumber value={counter} obfuscate={obfuscateCount} />
@ -172,6 +157,7 @@ export class IconButton extends PureComponent<Props, States> {
style={style} style={style}
tabIndex={tabIndex} tabIndex={tabIndex}
disabled={disabled} disabled={disabled}
ref={this.buttonRef}
> >
{contents} {contents}
</button> </button>

View file

@ -1,21 +1,24 @@
import type { IconProp } from './icon';
import { Icon } from './icon'; import { Icon } from './icon';
const formatNumber = (num: number): number | string => (num > 40 ? '40+' : num); const formatNumber = (num: number): number | string => (num > 40 ? '40+' : num);
interface Props { interface Props {
id: string; id: string;
icon: IconProp;
count: number; count: number;
issueBadge: boolean; issueBadge: boolean;
className: string; className: string;
} }
export const IconWithBadge: React.FC<Props> = ({ export const IconWithBadge: React.FC<Props> = ({
id, id,
icon,
count, count,
issueBadge, issueBadge,
className, className,
}) => ( }) => (
<i className='icon-with-badge'> <i className='icon-with-badge'>
<Icon id={id} fixedWidth className={className} /> <Icon id={id} icon={icon} className={className} />
{count > 0 && ( {count > 0 && (
<i className='icon-with-badge__badge'>{formatNumber(count)}</i> <i className='icon-with-badge__badge'>{formatNumber(count)}</i>
)} )}

View file

@ -2,6 +2,8 @@ import { useCallback } from 'react';
import { useIntl, defineMessages } from 'react-intl'; import { useIntl, defineMessages } from 'react-intl';
import { ReactComponent as MoreHorizIcon } from '@material-symbols/svg-600/outlined/more_horiz.svg';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
const messages = defineMessages({ const messages = defineMessages({
@ -28,7 +30,7 @@ export const LoadGap: React.FC<Props> = ({ disabled, maxId, onClick }) => {
onClick={handleClick} onClick={handleClick}
aria-label={intl.formatMessage(messages.load_more)} aria-label={intl.formatMessage(messages.load_more)}
> >
<Icon id='ellipsis-h' /> <Icon id='ellipsis-h' icon={MoreHorizIcon} />
</button> </button>
); );
}; };

View file

@ -1,7 +1,23 @@
import { useIntl, defineMessages } from 'react-intl';
import { CircularProgress } from './circular_progress'; import { CircularProgress } from './circular_progress';
export const LoadingIndicator: React.FC = () => ( const messages = defineMessages({
<div className='loading-indicator'> loading: { id: 'loading_indicator.label', defaultMessage: 'Loading…' },
});
export const LoadingIndicator: React.FC = () => {
const intl = useIntl();
return (
<div
className='loading-indicator'
role='progressbar'
aria-busy
aria-live='polite'
aria-label={intl.formatMessage(messages.loading)}
>
<CircularProgress size={50} strokeWidth={6} /> <CircularProgress size={50} strokeWidth={6} />
</div> </div>
); );
};

View file

@ -8,6 +8,7 @@ import classNames from 'classnames';
import { is } from 'immutable'; import { is } from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { ReactComponent as VisibilityOffIcon } from '@material-symbols/svg-600/outlined/visibility_off.svg';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { Blurhash } from 'flavours/glitch/components/blurhash'; import { Blurhash } from 'flavours/glitch/components/blurhash';
@ -362,7 +363,7 @@ class MediaGallery extends PureComponent {
</button> </button>
); );
} else if (visible) { } else if (visible) {
spoilerButton = <IconButton title={intl.formatMessage(messages.toggle_visible, { number: size })} icon='eye-slash' overlay onClick={this.handleOpen} ariaHidden />; spoilerButton = <IconButton title={intl.formatMessage(messages.toggle_visible, { number: size })} icon='eye-slash' iconComponent={VisibilityOffIcon} overlay onClick={this.handleOpen} ariaHidden />;
} else { } else {
spoilerButton = ( spoilerButton = (
<button type='button' onClick={this.handleOpen} className='spoiler-button__overlay'> <button type='button' onClick={this.handleOpen} className='spoiler-button__overlay'>

View file

@ -4,8 +4,6 @@
* a Confirm and Abort buttons are shown in its place. * a Confirm and Abort buttons are shown in its place.
*/ */
// Package imports //
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
@ -14,6 +12,8 @@ import classNames from 'classnames';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { ReactComponent as DeleteIcon } from '@material-symbols/svg-600/outlined/delete.svg';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
const messages = defineMessages({ const messages = defineMessages({
@ -53,7 +53,7 @@ class NotificationPurgeButtons extends ImmutablePureComponent {
</button> </button>
<button onClick={this.props.onDeleteMarked} className='column-header__button'> <button onClick={this.props.onDeleteMarked} className='column-header__button'>
<Icon id='trash' /><br />{intl.formatMessage(messages.btnApply)} <Icon id='trash' icon={DeleteIcon} /><br />{intl.formatMessage(messages.btnApply)}
</button> </button>
</div> </div>
); );

View file

@ -5,6 +5,8 @@ import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as CancelPresentationIcon } from '@material-symbols/svg-600/outlined/cancel_presentation.svg';
import { removePictureInPicture } from 'flavours/glitch/actions/picture_in_picture'; import { removePictureInPicture } from 'flavours/glitch/actions/picture_in_picture';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
@ -22,7 +24,7 @@ class PictureInPicturePlaceholder extends PureComponent {
render () { render () {
return ( return (
<div className='picture-in-picture-placeholder' role='button' tabIndex={0} onClick={this.handleClick}> <div className='picture-in-picture-placeholder' role='button' tabIndex={0} onClick={this.handleClick}>
<Icon id='window-restore' /> <Icon id='window-restore' icon={CancelPresentationIcon} />
<FormattedMessage id='picture_in_picture.restore' defaultMessage='Put it back' /> <FormattedMessage id='picture_in_picture.restore' defaultMessage='Put it back' />
</div> </div>
); );

View file

@ -7,6 +7,7 @@ import classNames from 'classnames';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/check.svg';
import escapeTextContentForBrowser from 'escape-html'; import escapeTextContentForBrowser from 'escape-html';
import spring from 'react-motion/lib/spring'; import spring from 'react-motion/lib/spring';
@ -192,7 +193,7 @@ class Poll extends ImmutablePureComponent {
/> />
{!!voted && <span className='poll__voted'> {!!voted && <span className='poll__voted'>
<Icon id='check' className='poll__voted__mark' title={intl.formatMessage(messages.voted)} /> <Icon id='check' icon={CheckIcon} className='poll__voted__mark' title={intl.formatMessage(messages.voted)} />
</span>} </span>}
</label> </label>

View file

@ -1,7 +1,7 @@
import type { PropsWithChildren } from 'react'; import type { PropsWithChildren } from 'react';
import React from 'react'; import React from 'react';
import { Router as OriginalRouter } from 'react-router'; import { Router as OriginalRouter, useHistory } from 'react-router';
import type { import type {
LocationDescriptor, LocationDescriptor,
@ -16,18 +16,23 @@ interface MastodonLocationState {
fromMastodon?: boolean; fromMastodon?: boolean;
mastodonModalKey?: string; mastodonModalKey?: string;
} }
type HistoryPath = Path | LocationDescriptor<MastodonLocationState>;
const browserHistory = createBrowserHistory< type LocationState = MastodonLocationState | null | undefined;
MastodonLocationState | undefined
>(); type HistoryPath = Path | LocationDescriptor<LocationState>;
const browserHistory = createBrowserHistory<LocationState>();
const originalPush = browserHistory.push.bind(browserHistory); const originalPush = browserHistory.push.bind(browserHistory);
const originalReplace = browserHistory.replace.bind(browserHistory); const originalReplace = browserHistory.replace.bind(browserHistory);
export function useAppHistory() {
return useHistory<LocationState>();
}
function normalizePath( function normalizePath(
path: HistoryPath, path: HistoryPath,
state?: MastodonLocationState, state?: LocationState,
): LocationDescriptorObject<MastodonLocationState> { ): LocationDescriptorObject<LocationState> {
const location = typeof path === 'string' ? { pathname: path } : { ...path }; const location = typeof path === 'string' ? { pathname: path } : { ...path };
if (location.state === undefined && state !== undefined) { if (location.state === undefined && state !== undefined) {

View file

@ -8,9 +8,22 @@ import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { ReactComponent as AddReactionIcon } from '@material-symbols/svg-600/outlined/add_reaction.svg';
import { ReactComponent as BookmarkIcon } from '@material-symbols/svg-600/outlined/bookmark-fill.svg';
import { ReactComponent as BookmarkBorderIcon } from '@material-symbols/svg-600/outlined/bookmark.svg';
import { ReactComponent as MoreHorizIcon } from '@material-symbols/svg-600/outlined/more_horiz.svg';
import { ReactComponent as RepeatIcon } from '@material-symbols/svg-600/outlined/repeat.svg';
import { ReactComponent as ReplyIcon } from '@material-symbols/svg-600/outlined/reply.svg';
import { ReactComponent as ReplyAllIcon } from '@material-symbols/svg-600/outlined/reply_all.svg';
import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star-fill.svg';
import { ReactComponent as StarBorderIcon } from '@material-symbols/svg-600/outlined/star.svg';
import { ReactComponent as VisibilityIcon } from '@material-symbols/svg-600/outlined/visibility.svg';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions';
import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links'; import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
import { ReactComponent as RepeatDisabledIcon } from 'mastodon/../svg-icons/repeat_disabled.svg';
import { ReactComponent as RepeatPrivateIcon } from 'mastodon/../svg-icons/repeat_private.svg';
import DropdownMenuContainer from '../containers/dropdown_menu_container'; import DropdownMenuContainer from '../containers/dropdown_menu_container';
import EmojiPickerDropdown from '../features/compose/containers/emoji_picker_dropdown_container'; import EmojiPickerDropdown from '../features/compose/containers/emoji_picker_dropdown_container';
@ -217,6 +230,7 @@ class StatusActionBar extends ImmutablePureComponent {
let menu = []; let menu = [];
let reblogIcon = 'retweet'; let reblogIcon = 'retweet';
let replyIcon; let replyIcon;
let replyIconComponent;
let replyTitle; let replyTitle;
menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen }); menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen });
@ -286,27 +300,34 @@ class StatusActionBar extends ImmutablePureComponent {
if (status.get('in_reply_to_id', null) === null) { if (status.get('in_reply_to_id', null) === null) {
replyIcon = 'reply'; replyIcon = 'reply';
replyIconComponent = ReplyIcon;
replyTitle = intl.formatMessage(messages.reply); replyTitle = intl.formatMessage(messages.reply);
} else { } else {
replyIcon = 'reply-all'; replyIcon = 'reply-all';
replyIconComponent = ReplyAllIcon;
replyTitle = intl.formatMessage(messages.replyAll); replyTitle = intl.formatMessage(messages.replyAll);
} }
const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility') === 'private'; const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility') === 'private';
let reblogTitle = ''; let reblogTitle, reblogIconComponent;
if (status.get('reblogged')) { if (status.get('reblogged')) {
reblogTitle = intl.formatMessage(messages.cancel_reblog_private); reblogTitle = intl.formatMessage(messages.cancel_reblog_private);
reblogIconComponent = publicStatus ? RepeatIcon : RepeatPrivateIcon;
} else if (publicStatus) { } else if (publicStatus) {
reblogTitle = intl.formatMessage(messages.reblog); reblogTitle = intl.formatMessage(messages.reblog);
reblogIconComponent = RepeatIcon;
} else if (reblogPrivate) { } else if (reblogPrivate) {
reblogTitle = intl.formatMessage(messages.reblog_private); reblogTitle = intl.formatMessage(messages.reblog_private);
reblogIconComponent = RepeatPrivateIcon;
} else { } else {
reblogTitle = intl.formatMessage(messages.cannot_reblog); reblogTitle = intl.formatMessage(messages.cannot_reblog);
reblogIconComponent = RepeatDisabledIcon;
} }
const filterButton = this.props.onFilter && ( const filterButton = this.props.onFilter && (
<IconButton className='status__action-bar-button' title={intl.formatMessage(messages.hide)} icon='eye' onClick={this.handleHideClick} /> <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.hide)} icon='eye' iconComponent={VisibilityIcon} onClick={this.handleHideClick} />
); );
const canReact = permissions && status.get('reactions').filter(r => r.get('count') > 0 && r.get('me')).size < maxReactions; const canReact = permissions && status.get('reactions').filter(r => r.get('count') > 0 && r.get('me')).size < maxReactions;
@ -316,7 +337,8 @@ class StatusActionBar extends ImmutablePureComponent {
onClick={this.handleNoOp} // EmojiPickerDropdown handles that onClick={this.handleNoOp} // EmojiPickerDropdown handles that
title={intl.formatMessage(messages.react)} title={intl.formatMessage(messages.react)}
disabled={!canReact} disabled={!canReact}
icon='plus' icon='add_reaction'
iconComponent={AddReactionIcon}
/> />
); );
@ -326,32 +348,32 @@ class StatusActionBar extends ImmutablePureComponent {
className='status__action-bar-button' className='status__action-bar-button'
title={replyTitle} title={replyTitle}
icon={replyIcon} icon={replyIcon}
iconComponent={replyIconComponent}
onClick={this.handleReplyClick} onClick={this.handleReplyClick}
counter={showReplyCount ? status.get('replies_count') : undefined} counter={showReplyCount ? status.get('replies_count') : undefined}
obfuscateCount obfuscateCount
/> />
<IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon={reblogIcon} onClick={this.handleReblogClick} counter={withCounters ? status.get('reblogs_count') : undefined} /> <IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon={reblogIcon} iconComponent={reblogIconComponent} onClick={this.handleReblogClick} counter={withCounters ? status.get('reblogs_count') : undefined} />
<IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} counter={withCounters ? status.get('favourites_count') : undefined} /> <IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' iconComponent={status.get('favourited') ? StarIcon : StarBorderIcon} onClick={this.handleFavouriteClick} counter={withCounters ? status.get('favourites_count') : undefined} />
{ {
permissions permissions
? <EmojiPickerDropdown className='status__action-bar-button' onPickEmoji={this.handleEmojiPick} button={reactButton} disabled={!canReact} /> ? <EmojiPickerDropdown className='status__action-bar-button' onPickEmoji={this.handleEmojiPick} button={reactButton} disabled={!canReact} />
: reactButton : reactButton
} }
<IconButton className='status__action-bar-button bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} /> <IconButton className='status__action-bar-button bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' iconComponent={status.get('bookmarked') ? BookmarkIcon : BookmarkBorderIcon} onClick={this.handleBookmarkClick} />
{filterButton} {filterButton}
<div className='status__action-bar-dropdown'>
<DropdownMenuContainer <DropdownMenuContainer
scrollKey={scrollKey} scrollKey={scrollKey}
status={status} status={status}
items={menu} items={menu}
icon='ellipsis-h' icon='ellipsis-h'
size={18} size={18}
iconComponent={MoreHorizIcon}
direction='right' direction='right'
ariaLabel={intl.formatMessage(messages.more)} ariaLabel={intl.formatMessage(messages.more)}
/> />
</div>
<div className='status__action-bar-spacer' /> <div className='status__action-bar-spacer' />
<a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'> <a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'>

View file

@ -9,6 +9,12 @@ import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as ImageIcon } from '@material-symbols/svg-600/outlined/image.svg';
import { ReactComponent as InsertChartIcon } from '@material-symbols/svg-600/outlined/insert_chart.svg';
import { ReactComponent as LinkIcon } from '@material-symbols/svg-600/outlined/link.svg';
import { ReactComponent as MovieIcon } from '@material-symbols/svg-600/outlined/movie.svg';
import { ReactComponent as MusicNoteIcon } from '@material-symbols/svg-600/outlined/music_note.svg';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { autoPlayGif, languages as preloadedLanguages } from 'flavours/glitch/initial_state'; import { autoPlayGif, languages as preloadedLanguages } from 'flavours/glitch/initial_state';
import { decode as decodeIDNA } from 'flavours/glitch/utils/idna'; import { decode as decodeIDNA } from 'flavours/glitch/utils/idna';
@ -381,12 +387,21 @@ class StatusContent extends PureComponent {
/>, />,
]; ];
if (mediaIcons) { if (mediaIcons) {
const mediaComponents = {
'link': LinkIcon,
'picture-o': ImageIcon,
'tasks': InsertChartIcon,
'video-camera': MovieIcon,
'music': MusicNoteIcon,
};
mediaIcons.forEach((mediaIcon, idx) => { mediaIcons.forEach((mediaIcon, idx) => {
toggleText.push( toggleText.push(
<Icon <Icon
fixedWidth fixedWidth
className='status__content__spoiler-icon' className='status__content__spoiler-icon'
id={mediaIcon} id={mediaIcon}
icon={mediaComponents[mediaIcon]}
aria-hidden='true' aria-hidden='true'
key={`icon-${idx}`} key={`icon-${idx}`}
/>, />,

View file

@ -6,15 +6,21 @@ import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { ReactComponent as ExpandLessIcon } from '@material-symbols/svg-600/outlined/expand_less.svg';
import { ReactComponent as ForumIcon } from '@material-symbols/svg-600/outlined/forum.svg';
import { ReactComponent as HomeIcon } from '@material-symbols/svg-600/outlined/home.svg';
import { ReactComponent as ImageIcon } from '@material-symbols/svg-600/outlined/image.svg';
import { ReactComponent as InsertChartIcon } from '@material-symbols/svg-600/outlined/insert_chart.svg';
import { ReactComponent as LinkIcon } from '@material-symbols/svg-600/outlined/link.svg';
import { ReactComponent as MovieIcon } from '@material-symbols/svg-600/outlined/movie.svg';
import { ReactComponent as MusicNoteIcon } from '@material-symbols/svg-600/outlined/music_note.svg';
// Mastodon imports.
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { languages } from 'flavours/glitch/initial_state'; import { languages } from 'flavours/glitch/initial_state';
import { IconButton } from './icon_button'; import { IconButton } from './icon_button';
import VisibilityIcon from './status_visibility_icon'; import { VisibilityIcon } from './visibility_icon';
// Messages for use with internationalization stuff.
const messages = defineMessages({ const messages = defineMessages({
collapse: { id: 'status.collapse', defaultMessage: 'Collapse' }, collapse: { id: 'status.collapse', defaultMessage: 'Collapse' },
uncollapse: { id: 'status.uncollapse', defaultMessage: 'Uncollapse' }, uncollapse: { id: 'status.uncollapse', defaultMessage: 'Uncollapse' },
@ -65,34 +71,47 @@ class StatusIcons extends PureComponent {
} }
}; };
mediaIconTitleText (mediaIcon) { renderIcon (mediaIcon) {
const { intl } = this.props; const { intl } = this.props;
const message = { let title, iconComponent;
'link': messages.previewCard,
'picture-o': messages.pictures,
'tasks': messages.poll,
'video-camera': messages.video,
'music': messages.audio,
}[mediaIcon];
return message && intl.formatMessage(message); switch (mediaIcon) {
case 'link':
title = messages.previewCard;
iconComponent = LinkIcon;
break;
case 'picture-o':
title = messages.pictures;
iconComponent = ImageIcon;
break;
case 'tasks':
title = messages.poll;
iconComponent = InsertChartIcon;
break;
case 'video-camera':
title = messages.video;
iconComponent = MovieIcon;
break;
case 'music':
title = messages.audio;
iconComponent = MusicNoteIcon;
break;
} }
renderIcon (mediaIcon) {
return ( return (
<Icon <Icon
fixedWidth fixedWidth
className='status__media-icon' className='status__media-icon'
key={`media-icon--${mediaIcon}`} key={`media-icon--${mediaIcon}`}
id={mediaIcon} id={mediaIcon}
icon={iconComponent}
aria-hidden='true' aria-hidden='true'
title={this.mediaIconTitleText(mediaIcon)} title={title && intl.formatMessage(title)}
/> />
); );
} }
// Rendering.
render () { render () {
const { const {
status, status,
@ -109,16 +128,16 @@ class StatusIcons extends PureComponent {
{settings.get('reply') && status.get('in_reply_to_id', null) !== null ? ( {settings.get('reply') && status.get('in_reply_to_id', null) !== null ? (
<Icon <Icon
className='status__reply-icon' className='status__reply-icon'
fixedWidth
id='comment' id='comment'
icon={ForumIcon}
aria-hidden='true' aria-hidden='true'
title={intl.formatMessage(messages.inReplyTo)} title={intl.formatMessage(messages.inReplyTo)}
/> />
) : null} ) : null}
{settings.get('local_only') && status.get('local_only') && {settings.get('local_only') && status.get('local_only') &&
<Icon <Icon
fixedWidth
id='home' id='home'
icon={HomeIcon}
aria-hidden='true' aria-hidden='true'
title={intl.formatMessage(messages.localOnly)} title={intl.formatMessage(messages.localOnly)}
/>} />}
@ -135,6 +154,7 @@ class StatusIcons extends PureComponent {
intl.formatMessage(messages.collapse) intl.formatMessage(messages.collapse)
} }
icon='angle-double-up' icon='angle-double-up'
iconComponent={ExpandLessIcon}
onClick={this.handleCollapsedClick} onClick={this.handleCollapsedClick}
/> />
)} )}

View file

@ -6,6 +6,14 @@ import { FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { ReactComponent as AddReactionIcon } from '@material-symbols/svg-600/outlined/add_reaction.svg';
import { ReactComponent as EditIcon } from '@material-symbols/svg-600/outlined/edit.svg';
import { ReactComponent as HomeIcon } from '@material-symbols/svg-600/outlined/home-fill.svg';
import { ReactComponent as InsertChartIcon } from '@material-symbols/svg-600/outlined/insert_chart.svg';
import { ReactComponent as PushPinIcon } from '@material-symbols/svg-600/outlined/push_pin.svg';
import { ReactComponent as RepeatIcon } from '@material-symbols/svg-600/outlined/repeat.svg';
import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star-fill.svg';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { me } from 'flavours/glitch/initial_state'; import { me } from 'flavours/glitch/initial_state';
@ -115,30 +123,37 @@ export default class StatusPrepend extends PureComponent {
const { Message } = this; const { Message } = this;
const { type } = this.props; const { type } = this.props;
let iconId; let iconId, iconComponent;
switch(type) { switch(type) {
case 'favourite': case 'favourite':
iconId = 'star'; iconId = 'star';
iconComponent = StarIcon;
break; break;
case 'reaction': case 'reaction':
iconId = 'plus'; iconId = 'add_reaction';
iconComponent = AddReactionIcon;
break; break;
case 'featured': case 'featured':
iconId = 'thumb-tack'; iconId = 'thumb-tack';
iconComponent = PushPinIcon;
break; break;
case 'poll': case 'poll':
iconId = 'tasks'; iconId = 'tasks';
iconComponent = InsertChartIcon;
break; break;
case 'reblog': case 'reblog':
case 'reblogged_by': case 'reblogged_by':
iconId = 'retweet'; iconId = 'retweet';
iconComponent = RepeatIcon;
break; break;
case 'status': case 'status':
iconId = 'bell'; iconId = 'bell';
iconComponent = HomeIcon;
break; break;
case 'update': case 'update':
iconId = 'pencil'; iconId = 'pencil';
iconComponent = EditIcon;
break; break;
} }
@ -148,6 +163,7 @@ export default class StatusPrepend extends PureComponent {
<Icon <Icon
className={`status__prepend-icon ${type === 'favourite' ? 'star-icon' : ''}`} className={`status__prepend-icon ${type === 'favourite' ? 'star-icon' : ''}`}
id={iconId} id={iconId}
icon={iconComponent}
/> />
</div> </div>
<Message /> <Message />

View file

@ -1,54 +0,0 @@
// Package imports //
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { Icon } from 'flavours/glitch/components/icon';
const messages = defineMessages({
public: { id: 'privacy.public.short', defaultMessage: 'Public' },
unlisted: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
private: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
direct: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
});
class VisibilityIcon extends ImmutablePureComponent {
static propTypes = {
visibility: PropTypes.string,
intl: PropTypes.object.isRequired,
withLabel: PropTypes.bool,
};
render() {
const { withLabel, visibility, intl } = this.props;
const visibilityIcon = {
public: 'globe',
unlisted: 'unlock',
private: 'lock',
direct: 'envelope',
}[visibility];
const label = intl.formatMessage(messages[visibility]);
const icon = (<Icon
className='status__visibility-icon'
fixedWidth
id={visibilityIcon}
title={label}
aria-hidden='true'
/>);
if (withLabel) {
return (<span style={{ whiteSpace: 'nowrap' }}>{icon} {label}</span>);
} else {
return icon;
}
}
}
export default injectIntl(VisibilityIcon);

View file

@ -1,3 +1,5 @@
import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/check.svg';
import { Icon } from './icon'; import { Icon } from './icon';
const domParser = new DOMParser(); const domParser = new DOMParser();
@ -21,7 +23,7 @@ interface Props {
} }
export const VerifiedBadge: React.FC<Props> = ({ link }) => ( export const VerifiedBadge: React.FC<Props> = ({ link }) => (
<span className='verified-badge'> <span className='verified-badge'>
<Icon id='check' className='verified-badge__mark' /> <Icon id='check' icon={CheckIcon} className='verified-badge__mark' />
<span dangerouslySetInnerHTML={stripRelMe(link)} /> <span dangerouslySetInnerHTML={stripRelMe(link)} />
</span> </span>
); );

View file

@ -0,0 +1,63 @@
import { defineMessages, useIntl } from 'react-intl';
import { ReactComponent as LockIcon } from '@material-symbols/svg-600/outlined/lock.svg';
import { ReactComponent as LockOpenIcon } from '@material-symbols/svg-600/outlined/lock_open.svg';
import { ReactComponent as MailIcon } from '@material-symbols/svg-600/outlined/mail.svg';
import { ReactComponent as PublicIcon } from '@material-symbols/svg-600/outlined/public.svg';
import { Icon } from './icon';
type Visibility = 'public' | 'unlisted' | 'private' | 'direct';
const messages = defineMessages({
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
private_short: {
id: 'privacy.private.short',
defaultMessage: 'Followers only',
},
direct_short: {
id: 'privacy.direct.short',
defaultMessage: 'Mentioned people only',
},
});
export const VisibilityIcon: React.FC<{ visibility: Visibility }> = ({
visibility,
}) => {
const intl = useIntl();
const visibilityIconInfo = {
public: {
icon: 'globe',
iconComponent: PublicIcon,
text: intl.formatMessage(messages.public_short),
},
unlisted: {
icon: 'unlock',
iconComponent: LockOpenIcon,
text: intl.formatMessage(messages.unlisted_short),
},
private: {
icon: 'lock',
iconComponent: LockIcon,
text: intl.formatMessage(messages.private_short),
},
direct: {
icon: 'envelope',
iconComponent: MailIcon,
text: intl.formatMessage(messages.direct_short),
},
};
const visibilityIcon = visibilityIconInfo[visibility];
return (
<Icon
id={visibilityIcon.icon}
icon={visibilityIcon.iconComponent}
title={visibilityIcon.text}
className={'status__visibility-icon'}
/>
);
};

View file

@ -10,6 +10,9 @@ import { List as ImmutableList } from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as ChevronRightIcon } from '@material-symbols/svg-600/outlined/chevron_right.svg';
import { ReactComponent as ExpandMoreIcon } from '@material-symbols/svg-600/outlined/expand_more.svg';
import { fetchServer, fetchExtendedDescription, fetchDomainBlocks } from 'flavours/glitch/actions/server'; import { fetchServer, fetchExtendedDescription, fetchDomainBlocks } from 'flavours/glitch/actions/server';
import Column from 'flavours/glitch/components/column'; import Column from 'flavours/glitch/components/column';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
@ -73,7 +76,7 @@ class Section extends PureComponent {
return ( return (
<div className={classNames('about__section', { active: !collapsed })}> <div className={classNames('about__section', { active: !collapsed })}>
<div className='about__section__title' role='button' tabIndex={0} onClick={this.handleClick}> <div className='about__section__title' role='button' tabIndex={0} onClick={this.handleClick}>
<Icon id={collapsed ? 'chevron-right' : 'chevron-down'} fixedWidth /> {title} <Icon id={collapsed ? 'chevron-right' : 'chevron-down'} icon={collapsed ? ChevronRightIcon : ExpandMoreIcon} /> {title}
</div> </div>
{!collapsed && ( {!collapsed && (

View file

@ -6,6 +6,8 @@ import { NavLink } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { ReactComponent as InfoIcon } from '@material-symbols/svg-600/outlined/info.svg';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
class ActionBar extends PureComponent { class ActionBar extends PureComponent {
@ -28,7 +30,7 @@ class ActionBar extends PureComponent {
return ( return (
<div> <div>
<div className='account__disclaimer'> <div className='account__disclaimer'>
<Icon id='info-circle' fixedWidth /> <FormattedMessage <Icon id='info-circle' icon={InfoIcon} /> <FormattedMessage
id='account.suspended_disclaimer_full' id='account.suspended_disclaimer_full'
defaultMessage='This user has been suspended by a moderator.' defaultMessage='This user has been suspended by a moderator.'
/> />
@ -42,7 +44,7 @@ class ActionBar extends PureComponent {
if (account.get('acct') !== account.get('username')) { if (account.get('acct') !== account.get('username')) {
extraInfo = ( extraInfo = (
<div className='account__disclaimer'> <div className='account__disclaimer'>
<Icon id='info-circle' fixedWidth /> <FormattedMessage <Icon id='info-circle' icon={InfoIcon} /> <FormattedMessage
id='account.disclaimer_full' id='account.disclaimer_full'
defaultMessage="Information below may reflect the user's profile incompletely." defaultMessage="Information below may reflect the user's profile incompletely."
/> />

View file

@ -3,6 +3,9 @@ import { FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/check.svg';
import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
export default class FollowRequestNote extends ImmutablePureComponent { export default class FollowRequestNote extends ImmutablePureComponent {
@ -22,12 +25,12 @@ export default class FollowRequestNote extends ImmutablePureComponent {
<div className='follow-request-banner__action'> <div className='follow-request-banner__action'>
<button type='button' className='button button-tertiary button--confirmation' onClick={onAuthorize}> <button type='button' className='button button-tertiary button--confirmation' onClick={onAuthorize}>
<Icon id='check' fixedWidth /> <Icon id='check' icon={CheckIcon} />
<FormattedMessage id='follow_request.authorize' defaultMessage='Authorize' /> <FormattedMessage id='follow_request.authorize' defaultMessage='Authorize' />
</button> </button>
<button type='button' className='button button-tertiary button--destructive' onClick={onReject}> <button type='button' className='button button-tertiary button--destructive' onClick={onReject}>
<Icon id='times' fixedWidth /> <Icon id='times' icon={CloseIcon} />
<FormattedMessage id='follow_request.reject' defaultMessage='Reject' /> <FormattedMessage id='follow_request.reject' defaultMessage='Reject' />
</button> </button>
</div> </div>

View file

@ -9,6 +9,12 @@ import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/check.svg';
import { ReactComponent as LockIcon } from '@material-symbols/svg-600/outlined/lock.svg';
import { ReactComponent as MoreHorizIcon } from '@material-symbols/svg-600/outlined/more_horiz.svg';
import { ReactComponent as NotificationsIcon } from '@material-symbols/svg-600/outlined/notifications.svg';
import { ReactComponent as NotificationsActiveIcon } from '@material-symbols/svg-600/outlined/notifications_active-fill.svg';
import { Avatar } from 'flavours/glitch/components/avatar'; import { Avatar } from 'flavours/glitch/components/avatar';
import { Badge, AutomatedBadge, GroupBadge } from 'flavours/glitch/components/badge'; import { Badge, AutomatedBadge, GroupBadge } from 'flavours/glitch/components/badge';
import { Button } from 'flavours/glitch/components/button'; import { Button } from 'flavours/glitch/components/button';
@ -189,7 +195,7 @@ class Header extends ImmutablePureComponent {
} }
if (account.getIn(['relationship', 'requested']) || account.getIn(['relationship', 'following'])) { if (account.getIn(['relationship', 'requested']) || account.getIn(['relationship', 'following'])) {
bellBtn = <IconButton icon={account.getIn(['relationship', 'notifying']) ? 'bell' : 'bell-o'} size={24} active={account.getIn(['relationship', 'notifying'])} title={intl.formatMessage(account.getIn(['relationship', 'notifying']) ? messages.disableNotifications : messages.enableNotifications, { name: account.get('username') })} onClick={this.props.onNotifyToggle} />; bellBtn = <IconButton icon={account.getIn(['relationship', 'notifying']) ? 'bell' : 'bell-o'} iconComponent={account.getIn(['relationship', 'notifying']) ? NotificationsActiveIcon : NotificationsIcon} active={account.getIn(['relationship', 'notifying'])} title={intl.formatMessage(account.getIn(['relationship', 'notifying']) ? messages.disableNotifications : messages.enableNotifications, { name: account.get('username') })} onClick={this.props.onNotifyToggle} />;
} }
if (me !== account.get('id')) { if (me !== account.get('id')) {
@ -215,7 +221,7 @@ class Header extends ImmutablePureComponent {
} }
if (account.get('locked')) { if (account.get('locked')) {
lockedIcon = <Icon id='lock' title={intl.formatMessage(messages.account_locked)} />; lockedIcon = <Icon id='lock' icon={LockIcon} title={intl.formatMessage(messages.account_locked)} />;
} }
if (signedIn && account.get('id') !== me && !account.get('suspended')) { if (signedIn && account.get('id') !== me && !account.get('suspended')) {
@ -347,7 +353,7 @@ class Header extends ImmutablePureComponent {
</> </>
)} )}
<DropdownMenuContainer disabled={menu.length === 0} items={menu} icon='ellipsis-v' size={24} direction='right' /> <DropdownMenuContainer disabled={menu.length === 0} items={menu} icon='ellipsis-v' iconComponent={MoreHorizIcon} size={24} direction='right' />
</div> </div>
</div> </div>
@ -378,7 +384,7 @@ class Header extends ImmutablePureComponent {
<dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} /> <dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} />
<dd className={pair.get('verified_at') && 'verified'} title={pair.get('value_plain')}> <dd className={pair.get('verified_at') && 'verified'} title={pair.get('value_plain')}>
{pair.get('verified_at') && <span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(pair.get('verified_at'), dateFormatOptions) })}><Icon id='check' className='verified__mark' /></span>} <span dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} className='translate' /> {pair.get('verified_at') && <span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(pair.get('verified_at'), dateFormatOptions) })}><Icon id='check' icon={CheckIcon} className='verified__mark' /></span>} <span dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} className='translate' />
</dd> </dd>
</dl> </dl>
))} ))}

View file

@ -3,6 +3,8 @@ import { PureComponent } from 'react';
import { injectIntl, defineMessages } from 'react-intl'; import { injectIntl, defineMessages } from 'react-intl';
import { ReactComponent as PersonIcon } from '@material-symbols/svg-600/outlined/person.svg';
import ColumnHeader from '../../../components/column_header'; import ColumnHeader from '../../../components/column_header';
const messages = defineMessages({ const messages = defineMessages({
@ -23,6 +25,7 @@ class ProfileColumnHeader extends PureComponent {
return ( return (
<ColumnHeader <ColumnHeader
icon='user-circle' icon='user-circle'
iconComponent={PersonIcon}
title={intl.formatMessage(messages.profile)} title={intl.formatMessage(messages.profile)}
onClick={onClick} onClick={onClick}
showBackButton showBackButton

View file

@ -5,6 +5,10 @@ import classNames from 'classnames';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { ReactComponent as AudiotrackIcon } from '@material-symbols/svg-600/outlined/music_note.svg';
import { ReactComponent as PlayArrowIcon } from '@material-symbols/svg-600/outlined/play_arrow.svg';
import { ReactComponent as VisibilityOffIcon } from '@material-symbols/svg-600/outlined/visibility_off.svg';
import { Blurhash } from 'flavours/glitch/components/blurhash'; import { Blurhash } from 'flavours/glitch/components/blurhash';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { autoPlayGif, displayMedia, useBlurhash } from 'flavours/glitch/initial_state'; import { autoPlayGif, displayMedia, useBlurhash } from 'flavours/glitch/initial_state';
@ -69,7 +73,7 @@ export default class MediaItem extends ImmutablePureComponent {
if (!visible) { if (!visible) {
icon = ( icon = (
<span className='account-gallery__item__icons'> <span className='account-gallery__item__icons'>
<Icon id='eye-slash' /> <Icon id='eye-slash' icon={VisibilityOffIcon} />
</span> </span>
); );
} else { } else {
@ -84,9 +88,9 @@ export default class MediaItem extends ImmutablePureComponent {
); );
if (attachment.get('type') === 'audio') { if (attachment.get('type') === 'audio') {
label = <Icon id='music' />; label = <Icon id='music' icon={AudiotrackIcon} />;
} else { } else {
label = <Icon id='play' />; label = <Icon id='play' icon={PlayArrowIcon} />;
} }
} else if (attachment.get('type') === 'image') { } else if (attachment.get('type') === 'image') {
const focusX = attachment.getIn(['meta', 'focus', 'x']) || 0; const focusX = attachment.getIn(['meta', 'focus', 'x']) || 0;

View file

@ -5,6 +5,8 @@ import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { ReactComponent as TripIcon } from '@material-symbols/svg-600/outlined/trip.svg';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
@ -35,7 +37,7 @@ class MovedNote extends ImmutablePureComponent {
return ( return (
<div className='account__moved-note'> <div className='account__moved-note'>
<div className='account__moved-note__message'> <div className='account__moved-note__message'>
<div className='account__moved-note__icon-wrapper'><Icon id='suitcase' className='account__moved-note__icon' fixedWidth /></div> <div className='account__moved-note__icon-wrapper'><Icon id='suitcase' className='account__moved-note__icon' icon={TripIcon} /></div>
<FormattedMessage id='account.moved_to' defaultMessage='{name} has indicated that their new account is now:' values={{ name: <bdi><strong dangerouslySetInnerHTML={displayNameHtml} /></bdi> }} /> <FormattedMessage id='account.moved_to' defaultMessage='{name} has indicated that their new account is now:' values={{ name: <bdi><strong dangerouslySetInnerHTML={displayNameHtml} /></bdi> }} />
</div> </div>

View file

@ -7,6 +7,12 @@ import classNames from 'classnames';
import { is } from 'immutable'; import { is } from 'immutable';
import { ReactComponent as DownloadIcon } from '@material-symbols/svg-600/outlined/download.svg';
import { ReactComponent as PauseIcon } from '@material-symbols/svg-600/outlined/pause.svg';
import { ReactComponent as PlayArrowIcon } from '@material-symbols/svg-600/outlined/play_arrow-fill.svg';
import { ReactComponent as VisibilityOffIcon } from '@material-symbols/svg-600/outlined/visibility_off.svg';
import { ReactComponent as VolumeOffIcon } from '@material-symbols/svg-600/outlined/volume_off-fill.svg';
import { ReactComponent as VolumeUpIcon } from '@material-symbols/svg-600/outlined/volume_up-fill.svg';
import { throttle, debounce } from 'lodash'; import { throttle, debounce } from 'lodash';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
@ -560,8 +566,8 @@ class Audio extends PureComponent {
<div className='video-player__controls active'> <div className='video-player__controls active'>
<div className='video-player__buttons-bar'> <div className='video-player__buttons-bar'>
<div className='video-player__buttons left'> <div className='video-player__buttons left'>
<button type='button' title={intl.formatMessage(paused ? messages.play : messages.pause)} aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} className='player-button' onClick={this.togglePlay}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button> <button type='button' title={intl.formatMessage(paused ? messages.play : messages.pause)} aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} className='player-button' onClick={this.togglePlay}><Icon id={paused ? 'play' : 'pause'} icon={paused ? PlayArrowIcon : PauseIcon} /></button>
<button type='button' title={intl.formatMessage(muted ? messages.unmute : messages.mute)} aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} className='player-button' onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button> <button type='button' title={intl.formatMessage(muted ? messages.unmute : messages.mute)} aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} className='player-button' onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} icon={muted ? VolumeOffIcon : VolumeUpIcon} /></button>
<div className={classNames('video-player__volume', { active: this.state.hovered })} ref={this.setVolumeRef} onMouseDown={this.handleVolumeMouseDown}> <div className={classNames('video-player__volume', { active: this.state.hovered })} ref={this.setVolumeRef} onMouseDown={this.handleVolumeMouseDown}>
<div className='video-player__volume__current' style={{ width: `${muted ? 0 : volume * 100}%`, backgroundColor: this._getAccentColor() }} /> <div className='video-player__volume__current' style={{ width: `${muted ? 0 : volume * 100}%`, backgroundColor: this._getAccentColor() }} />
@ -581,9 +587,9 @@ class Audio extends PureComponent {
</div> </div>
<div className='video-player__buttons right'> <div className='video-player__buttons right'>
{!editable && <button type='button' title={intl.formatMessage(messages.hide)} aria-label={intl.formatMessage(messages.hide)} className='player-button' onClick={this.toggleReveal}><Icon id='eye-slash' fixedWidth /></button>} {!editable && <button type='button' title={intl.formatMessage(messages.hide)} aria-label={intl.formatMessage(messages.hide)} className='player-button' onClick={this.toggleReveal}><Icon id='eye-slash' icon={VisibilityOffIcon} /></button>}
<a title={intl.formatMessage(messages.download)} aria-label={intl.formatMessage(messages.download)} className='video-player__download__icon player-button' href={this.props.src} download> <a title={intl.formatMessage(messages.download)} aria-label={intl.formatMessage(messages.download)} className='video-player__download__icon player-button' href={this.props.src} download>
<Icon id={'download'} fixedWidth /> <Icon id={'download'} icon={DownloadIcon} />
</a> </a>
</div> </div>
</div> </div>

View file

@ -6,10 +6,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as BlockIcon } from '@material-symbols/svg-600/outlined/block-fill.svg';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { fetchBlocks, expandBlocks } from '../../actions/blocks'; import { fetchBlocks, expandBlocks } from '../../actions/blocks';
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import { LoadingIndicator } from '../../components/loading_indicator'; import { LoadingIndicator } from '../../components/loading_indicator';
import ScrollableList from '../../components/scrollable_list'; import ScrollableList from '../../components/scrollable_list';
import AccountContainer from '../../containers/account_container'; import AccountContainer from '../../containers/account_container';
@ -59,8 +59,7 @@ class Blocks extends ImmutablePureComponent {
const emptyMessage = <FormattedMessage id='empty_column.blocks' defaultMessage="You haven't blocked any users yet." />; const emptyMessage = <FormattedMessage id='empty_column.blocks' defaultMessage="You haven't blocked any users yet." />;
return ( return (
<Column bindToDocument={!multiColumn} icon='ban' heading={intl.formatMessage(messages.heading)}> <Column bindToDocument={!multiColumn} icon='ban' iconComponent={BlockIcon} heading={intl.formatMessage(messages.heading)} alwaysShowBackButton>
<ColumnBackButtonSlim />
<ScrollableList <ScrollableList
scrollKey='blocks' scrollKey='blocks'
onLoadMore={this.handleLoadMore} onLoadMore={this.handleLoadMore}

View file

@ -8,6 +8,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as BookmarksIcon } from '@material-symbols/svg-600/outlined/bookmarks-fill.svg';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { fetchBookmarkedStatuses, expandBookmarkedStatuses } from 'flavours/glitch/actions/bookmarks'; import { fetchBookmarkedStatuses, expandBookmarkedStatuses } from 'flavours/glitch/actions/bookmarks';
@ -79,7 +80,8 @@ class Bookmarks extends ImmutablePureComponent {
return ( return (
<Column bindToDocument={!multiColumn} ref={this.setRef}> <Column bindToDocument={!multiColumn} ref={this.setRef}>
<ColumnHeader <ColumnHeader
icon='bookmark' icon='bookmarks'
iconComponent={BookmarksIcon}
title={intl.formatMessage(messages.heading)} title={intl.formatMessage(messages.heading)}
onPin={this.handlePin} onPin={this.handlePin}
onMove={this.handleMove} onMove={this.handleMove}

View file

@ -7,6 +7,8 @@ import { Helmet } from 'react-helmet';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as PeopleIcon } from '@material-symbols/svg-600/outlined/group.svg';
import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner'; import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner';
import { domain } from 'flavours/glitch/initial_state'; import { domain } from 'flavours/glitch/initial_state';
@ -131,6 +133,7 @@ class CommunityTimeline extends PureComponent {
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
<ColumnHeader <ColumnHeader
icon='users' icon='users'
iconComponent={PeopleIcon}
active={hasUnread} active={hasUnread}
title={intl.formatMessage(messages.title)} title={intl.formatMessage(messages.title)}
onPin={this.handlePin} onPin={this.handlePin}

View file

@ -5,6 +5,8 @@ import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { ReactComponent as MenuIcon } from '@material-symbols/svg-600/outlined/menu.svg';
import { preferencesLink, profileLink } from 'flavours/glitch/utils/backend_links'; import { preferencesLink, profileLink } from 'flavours/glitch/utils/backend_links';
import DropdownMenuContainer from '../../../containers/dropdown_menu_container'; import DropdownMenuContainer from '../../../containers/dropdown_menu_container';
@ -62,7 +64,7 @@ class ActionBar extends PureComponent {
return ( return (
<div className='compose__action-bar'> <div className='compose__action-bar'>
<div className='compose__action-bar-dropdown'> <div className='compose__action-bar-dropdown'>
<DropdownMenuContainer items={menu} icon='bars' size={18} direction='right' /> <DropdownMenuContainer items={menu} icon='bars' iconComponent={MenuIcon} size={24} direction='right' />
</div> </div>
</div> </div>
); );

View file

@ -18,8 +18,10 @@ export default class ComposerOptionsDropdown extends PureComponent {
isUserTouching: PropTypes.func, isUserTouching: PropTypes.func,
disabled: PropTypes.bool, disabled: PropTypes.bool,
icon: PropTypes.string, icon: PropTypes.string,
iconComponent: PropTypes.func,
items: PropTypes.arrayOf(PropTypes.shape({ items: PropTypes.arrayOf(PropTypes.shape({
icon: PropTypes.string, icon: PropTypes.string,
iconComponent: PropTypes.func,
meta: PropTypes.string, meta: PropTypes.string,
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
text: PropTypes.string, text: PropTypes.string,
@ -174,6 +176,7 @@ export default class ComposerOptionsDropdown extends PureComponent {
disabled, disabled,
title, title,
icon, icon,
iconComponent,
items, items,
onChange, onChange,
value, value,
@ -183,20 +186,18 @@ export default class ComposerOptionsDropdown extends PureComponent {
} = this.props; } = this.props;
const { open, placement } = this.state; const { open, placement } = this.state;
const active = value && items.findIndex(({ name }) => name === value) === (placement === 'bottom' ? 0 : (items.length - 1));
return ( return (
<div <div
className={classNames('privacy-dropdown', placement, { active: open })} className={classNames('privacy-dropdown', placement, { active: open })}
onKeyDown={this.handleKeyDown} onKeyDown={this.handleKeyDown}
ref={this.setTargetRef} ref={this.setTargetRef}
> >
<div className={classNames('privacy-dropdown__value', { active })}>
<IconButton <IconButton
active={open} active={open}
className='privacy-dropdown__value-icon' className='privacy-dropdown__value-icon'
disabled={disabled} disabled={disabled}
icon={icon} icon={icon}
iconComponent={iconComponent}
inverted inverted
onClick={this.handleToggle} onClick={this.handleToggle}
onMouseDown={this.handleMouseDown} onMouseDown={this.handleMouseDown}
@ -209,7 +210,6 @@ export default class ComposerOptionsDropdown extends PureComponent {
}} }}
title={title} title={title}
/> />
</div>
<Overlay <Overlay
containerPadding={20} containerPadding={20}

View file

@ -17,6 +17,7 @@ export default class ComposerOptionsDropdownContent extends PureComponent {
static propTypes = { static propTypes = {
items: PropTypes.arrayOf(PropTypes.shape({ items: PropTypes.arrayOf(PropTypes.shape({
icon: PropTypes.string, icon: PropTypes.string,
iconComponent: PropTypes.func,
meta: PropTypes.node, meta: PropTypes.node,
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
text: PropTypes.node, text: PropTypes.node,
@ -144,7 +145,7 @@ export default class ComposerOptionsDropdownContent extends PureComponent {
}; };
renderItem = (item, i) => { renderItem = (item, i) => {
const { name, icon, meta, text } = item; const { name, icon, iconComponent, meta, text } = item;
const active = (name === (this.props.value || this.state.value)); const active = (name === (this.props.value || this.state.value));
@ -155,7 +156,7 @@ export default class ComposerOptionsDropdownContent extends PureComponent {
if (!contents) { if (!contents) {
contents = ( contents = (
<> <>
{icon && <Icon className='icon' fixedWidth id={icon} />} {icon && <Icon className='icon' id={icon} icon={iconComponent} />}
<div className='privacy-dropdown__option__content'> <div className='privacy-dropdown__option__content'>
<strong>{text}</strong> <strong>{text}</strong>

View file

@ -7,6 +7,14 @@ import { Link } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { ReactComponent as PeopleIcon } from '@material-symbols/svg-600/outlined/group.svg';
import { ReactComponent as HomeIcon } from '@material-symbols/svg-600/outlined/home-fill.svg';
import { ReactComponent as LogoutIcon } from '@material-symbols/svg-600/outlined/logout.svg';
import { ReactComponent as ManufacturingIcon } from '@material-symbols/svg-600/outlined/manufacturing.svg';
import { ReactComponent as MenuIcon } from '@material-symbols/svg-600/outlined/menu.svg';
import { ReactComponent as NotificationsIcon } from '@material-symbols/svg-600/outlined/notifications-fill.svg';
import { ReactComponent as PublicIcon } from '@material-symbols/svg-600/outlined/public.svg';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { signOutLink } from 'flavours/glitch/utils/backend_links'; import { signOutLink } from 'flavours/glitch/utils/backend_links';
import { conditionalRender } from 'flavours/glitch/utils/react_helpers'; import { conditionalRender } from 'flavours/glitch/utils/react_helpers';
@ -79,22 +87,25 @@ class Header extends ImmutablePureComponent {
aria-label={intl.formatMessage(messages.start)} aria-label={intl.formatMessage(messages.start)}
title={intl.formatMessage(messages.start)} title={intl.formatMessage(messages.start)}
to='/getting-started' to='/getting-started'
><Icon id='asterisk' /></Link> className='drawer__tab'
><Icon id='bars' icon={MenuIcon} /></Link>
{renderForColumn('HOME', ( {renderForColumn('HOME', (
<Link <Link
aria-label={intl.formatMessage(messages.home_timeline)} aria-label={intl.formatMessage(messages.home_timeline)}
title={intl.formatMessage(messages.home_timeline)} title={intl.formatMessage(messages.home_timeline)}
to='/home' to='/home'
><Icon id='home' /></Link> className='drawer__tab'
><Icon id='home' icon={HomeIcon} /></Link>
))} ))}
{renderForColumn('NOTIFICATIONS', ( {renderForColumn('NOTIFICATIONS', (
<Link <Link
aria-label={intl.formatMessage(messages.notifications)} aria-label={intl.formatMessage(messages.notifications)}
title={intl.formatMessage(messages.notifications)} title={intl.formatMessage(messages.notifications)}
to='/notifications' to='/notifications'
className='drawer__tab'
> >
<span className='icon-badge-wrapper'> <span className='icon-badge-wrapper'>
<Icon id='bell' /> <Icon id='bell' icon={NotificationsIcon} />
{ showNotificationsBadge && unreadNotifications > 0 && <div className='icon-badge' />} { showNotificationsBadge && unreadNotifications > 0 && <div className='icon-badge' />}
</span> </span>
</Link> </Link>
@ -104,27 +115,31 @@ class Header extends ImmutablePureComponent {
aria-label={intl.formatMessage(messages.community)} aria-label={intl.formatMessage(messages.community)}
title={intl.formatMessage(messages.community)} title={intl.formatMessage(messages.community)}
to='/public/local' to='/public/local'
><Icon id='users' /></Link> className='drawer__tab'
><Icon id='users' icon={PeopleIcon} /></Link>
))} ))}
{renderForColumn('PUBLIC', ( {renderForColumn('PUBLIC', (
<Link <Link
aria-label={intl.formatMessage(messages.public)} aria-label={intl.formatMessage(messages.public)}
title={intl.formatMessage(messages.public)} title={intl.formatMessage(messages.public)}
to='/public' to='/public'
><Icon id='globe' /></Link> className='drawer__tab'
><Icon id='globe' icon={PublicIcon} /></Link>
))} ))}
<a <a
aria-label={intl.formatMessage(messages.settings)} aria-label={intl.formatMessage(messages.settings)}
onClick={onSettingsClick} onClick={onSettingsClick}
href='/settings/preferences' href='/settings/preferences'
title={intl.formatMessage(messages.settings)} title={intl.formatMessage(messages.settings)}
><Icon id='cogs' /></a> className='drawer__tab'
><Icon id='cogs' icon={ManufacturingIcon} /></a>
<a <a
aria-label={intl.formatMessage(messages.logout)} aria-label={intl.formatMessage(messages.logout)}
onClick={this.handleLogoutClick} onClick={this.handleLogoutClick}
href={signOutLink} href={signOutLink}
title={intl.formatMessage(messages.logout)} title={intl.formatMessage(messages.logout)}
><Icon id='sign-out' /></a> className='drawer__tab'
><Icon id='sign-out' icon={LogoutIcon} /></a>
</nav> </nav>
); );
} }

View file

@ -6,6 +6,14 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as AttachFileIcon } from '@material-symbols/svg-600/outlined/attach_file.svg';
import { ReactComponent as BrushIcon } from '@material-symbols/svg-600/outlined/brush.svg';
import { ReactComponent as CodeIcon } from '@material-symbols/svg-600/outlined/code.svg';
import { ReactComponent as DescriptionIcon } from '@material-symbols/svg-600/outlined/description.svg';
import { ReactComponent as InsertChartIcon } from '@material-symbols/svg-600/outlined/insert_chart.svg';
import { ReactComponent as MarkdownIcon } from '@material-symbols/svg-600/outlined/markdown.svg';
import { ReactComponent as MoreHorizIcon } from '@material-symbols/svg-600/outlined/more_horiz.svg';
import { ReactComponent as UploadFileIcon } from '@material-symbols/svg-600/outlined/upload_file.svg';
import Toggle from 'react-toggle'; import Toggle from 'react-toggle';
import { IconButton } from 'flavours/glitch/components/icon_button'; import { IconButton } from 'flavours/glitch/components/icon_button';
@ -205,16 +213,19 @@ class ComposerOptions extends ImmutablePureComponent {
const contentTypeItems = { const contentTypeItems = {
plain: { plain: {
icon: 'file-text', icon: 'file-text',
iconComponent: DescriptionIcon,
name: 'text/plain', name: 'text/plain',
text: formatMessage(messages.plain), text: formatMessage(messages.plain),
}, },
html: { html: {
icon: 'code', icon: 'code',
iconComponent: CodeIcon,
name: 'text/html', name: 'text/html',
text: formatMessage(messages.html), text: formatMessage(messages.html),
}, },
markdown: { markdown: {
icon: 'arrow-circle-down', icon: 'arrow-circle-down',
iconComponent: MarkdownIcon,
name: 'text/markdown', name: 'text/markdown',
text: formatMessage(messages.markdown), text: formatMessage(messages.markdown),
}, },
@ -236,14 +247,17 @@ class ComposerOptions extends ImmutablePureComponent {
<DropdownContainer <DropdownContainer
disabled={disabled || !allowMedia} disabled={disabled || !allowMedia}
icon='paperclip' icon='paperclip'
iconComponent={AttachFileIcon}
items={[ items={[
{ {
icon: 'cloud-upload', icon: 'cloud-upload',
iconComponent: UploadFileIcon,
name: 'upload', name: 'upload',
text: formatMessage(messages.upload), text: formatMessage(messages.upload),
}, },
{ {
icon: 'paint-brush', icon: 'paint-brush',
iconComponent: BrushIcon,
name: 'doodle', name: 'doodle',
text: formatMessage(messages.doodle), text: formatMessage(messages.doodle),
}, },
@ -261,6 +275,7 @@ class ComposerOptions extends ImmutablePureComponent {
active={hasPoll} active={hasPoll}
disabled={disabled} disabled={disabled}
icon='tasks' icon='tasks'
iconComponent={InsertChartIcon}
inverted inverted
onClick={onTogglePoll} onClick={onTogglePoll}
size={18} size={18}
@ -271,12 +286,12 @@ class ComposerOptions extends ImmutablePureComponent {
title={formatMessage(hasPoll ? messages.remove_poll : messages.add_poll)} title={formatMessage(hasPoll ? messages.remove_poll : messages.add_poll)}
/> />
)} )}
<hr />
<PrivacyDropdownContainer disabled={disabled || isEditing} /> <PrivacyDropdownContainer disabled={disabled || isEditing} />
{showContentTypeChoice && ( {showContentTypeChoice && (
<DropdownContainer <DropdownContainer
disabled={disabled} disabled={disabled}
icon={(contentTypeItems[contentType.split('/')[1]] || {}).icon} icon={(contentTypeItems[contentType.split('/')[1]] || {}).icon}
iconComponent={(contentTypeItems[contentType.split('/')[1]] || {}).iconComponent}
items={[ items={[
contentTypeItems.plain, contentTypeItems.plain,
contentTypeItems.html, contentTypeItems.html,
@ -300,6 +315,7 @@ class ComposerOptions extends ImmutablePureComponent {
<DropdownContainer <DropdownContainer
disabled={disabled || isEditing} disabled={disabled || isEditing}
icon='ellipsis-h' icon='ellipsis-h'
iconComponent={MoreHorizIcon}
items={advancedOptions ? [ items={advancedOptions ? [
{ {
meta: formatMessage(messages.local_only_long), meta: formatMessage(messages.local_only_long),

View file

@ -8,6 +8,9 @@ import classNames from 'classnames';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { ReactComponent as AddIcon } from '@material-symbols/svg-600/outlined/add.svg';
import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg';
import AutosuggestInput from 'flavours/glitch/components/autosuggest_input'; import AutosuggestInput from 'flavours/glitch/components/autosuggest_input';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { IconButton } from 'flavours/glitch/components/icon_button'; import { IconButton } from 'flavours/glitch/components/icon_button';
@ -87,7 +90,7 @@ class OptionIntl extends PureComponent {
</label> </label>
<div className='poll__cancel'> <div className='poll__cancel'>
<IconButton disabled={index < 1} title={intl.formatMessage(messages.remove_option)} icon='times' onClick={this.handleOptionRemove} /> <IconButton disabled={index < 1} title={intl.formatMessage(messages.remove_option)} icon='times' iconComponent={CloseIcon} onClick={this.handleOptionRemove} />
</div> </div>
</li> </li>
); );
@ -143,7 +146,7 @@ class PollForm extends ImmutablePureComponent {
{options.size < pollLimits.max_options && ( {options.size < pollLimits.max_options && (
<label className='poll__text editable'> <label className='poll__text editable'>
<span className={classNames('poll__input')} style={{ opacity: 0 }} /> <span className={classNames('poll__input')} style={{ opacity: 0 }} />
<button className='button button-secondary' onClick={this.handleAddOption} type='button'><Icon id='plus' /> <FormattedMessage {...messages.add_option} /></button> <button className='button button-secondary' onClick={this.handleAddOption} type='button'><Icon id='plus' icon={AddIcon} /> <FormattedMessage {...messages.add_option} /></button>
</label> </label>
)} )}
</ul> </ul>

View file

@ -3,6 +3,11 @@ import { PureComponent } from 'react';
import { injectIntl, defineMessages } from 'react-intl'; import { injectIntl, defineMessages } from 'react-intl';
import { ReactComponent as LockIcon } from '@material-symbols/svg-600/outlined/lock.svg';
import { ReactComponent as LockOpenIcon } from '@material-symbols/svg-600/outlined/lock_open.svg';
import { ReactComponent as MailIcon } from '@material-symbols/svg-600/outlined/mail.svg';
import { ReactComponent as PublicIcon } from '@material-symbols/svg-600/outlined/public.svg';
import Dropdown from './dropdown'; import Dropdown from './dropdown';
const messages = defineMessages({ const messages = defineMessages({
@ -39,24 +44,28 @@ class PrivacyDropdown extends PureComponent {
const privacyItems = { const privacyItems = {
direct: { direct: {
icon: 'envelope', icon: 'envelope',
iconComponent: MailIcon,
meta: formatMessage(messages.direct_long), meta: formatMessage(messages.direct_long),
name: 'direct', name: 'direct',
text: formatMessage(messages.direct_short), text: formatMessage(messages.direct_short),
}, },
private: { private: {
icon: 'lock', icon: 'lock',
iconComponent: LockIcon,
meta: formatMessage(messages.private_long), meta: formatMessage(messages.private_long),
name: 'private', name: 'private',
text: formatMessage(messages.private_short), text: formatMessage(messages.private_short),
}, },
public: { public: {
icon: 'globe', icon: 'globe',
iconComponent: PublicIcon,
meta: formatMessage(messages.public_long), meta: formatMessage(messages.public_long),
name: 'public', name: 'public',
text: formatMessage(messages.public_short), text: formatMessage(messages.public_short),
}, },
unlisted: { unlisted: {
icon: 'unlock', icon: 'unlock',
iconComponent: LockOpenIcon,
meta: formatMessage(messages.unlisted_long), meta: formatMessage(messages.unlisted_long),
name: 'unlisted', name: 'unlisted',
text: formatMessage(messages.unlisted_short), text: formatMessage(messages.unlisted_short),
@ -73,6 +82,7 @@ class PrivacyDropdown extends PureComponent {
<Dropdown <Dropdown
disabled={disabled} disabled={disabled}
icon={(privacyItems[value] || {}).icon} icon={(privacyItems[value] || {}).icon}
iconComponent={(privacyItems[value] || {}).iconComponent}
items={items} items={items}
onChange={onChange} onChange={onChange}
isUserTouching={isUserTouching} isUserTouching={isUserTouching}

View file

@ -4,6 +4,11 @@ import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { ReactComponent as LockIcon } from '@material-symbols/svg-600/outlined/lock.svg';
import { ReactComponent as LockOpenIcon } from '@material-symbols/svg-600/outlined/lock_open.svg';
import { ReactComponent as MailIcon } from '@material-symbols/svg-600/outlined/mail.svg';
import { ReactComponent as PublicIcon } from '@material-symbols/svg-600/outlined/public.svg';
import { Button } from 'flavours/glitch/components/button'; import { Button } from 'flavours/glitch/components/button';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
@ -37,16 +42,33 @@ class Publisher extends ImmutablePureComponent {
render () { render () {
const { disabled, intl, onSecondarySubmit, privacy, sideArm, isEditing } = this.props; const { disabled, intl, onSecondarySubmit, privacy, sideArm, isEditing } = this.props;
const privacyIcons = { direct: 'envelope', private: 'lock', public: 'globe', unlisted: 'unlock' }; const privacyIcons = {
direct: {
id: 'envelope',
icon: MailIcon,
},
private: {
id: 'lock',
icon: LockIcon,
},
public: {
id: 'globe',
icon: PublicIcon,
},
unlisted: {
id: 'unlock',
icon: LockOpenIcon,
},
};
let publishText; let publishText;
if (isEditing) { if (isEditing) {
publishText = intl.formatMessage(messages.saveChanges); publishText = intl.formatMessage(messages.saveChanges);
} else if (privacy === 'private' || privacy === 'direct') { } else if (privacy === 'private' || privacy === 'direct') {
const iconId = privacyIcons[privacy]; const icon = privacyIcons[privacy];
publishText = ( publishText = (
<span> <span>
<Icon id={iconId} /> {intl.formatMessage(messages.publish)} <Icon {...icon} /> {intl.formatMessage(messages.publish)}
</span> </span>
); );
} else { } else {
@ -69,7 +91,7 @@ class Publisher extends ImmutablePureComponent {
disabled={disabled} disabled={disabled}
onClick={onSecondarySubmit} onClick={onSecondarySubmit}
style={{ padding: null }} style={{ padding: null }}
text={<Icon id={privacyIcons[sideArm]} />} text={<Icon {...privacyIcons[sideArm]} />}
title={`${intl.formatMessage(messages.publish)}: ${intl.formatMessage(privacyNames[sideArm])}`} title={`${intl.formatMessage(messages.publish)}: ${intl.formatMessage(privacyNames[sideArm])}`}
/> />
</div> </div>

View file

@ -5,6 +5,8 @@ import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg';
import AttachmentList from 'flavours/glitch/components/attachment_list'; import AttachmentList from 'flavours/glitch/components/attachment_list';
import { WithOptionalRouterPropTypes, withOptionalRouter } from 'flavours/glitch/utils/react_router'; import { WithOptionalRouterPropTypes, withOptionalRouter } from 'flavours/glitch/utils/react_router';
@ -48,7 +50,7 @@ class ReplyIndicator extends ImmutablePureComponent {
return ( return (
<div className='reply-indicator'> <div className='reply-indicator'>
<div className='reply-indicator__header'> <div className='reply-indicator__header'>
<div className='reply-indicator__cancel'><IconButton title={intl.formatMessage(messages.cancel)} icon='times' onClick={this.handleClick} inverted /></div> <div className='reply-indicator__cancel'><IconButton title={intl.formatMessage(messages.cancel)} icon='times' iconComponent={CloseIcon} onClick={this.handleClick} inverted /></div>
<a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='reply-indicator__display-name' target='_blank' rel='noopener noreferrer'> <a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='reply-indicator__display-name' target='_blank' rel='noopener noreferrer'>
<div className='reply-indicator__display-avatar'><Avatar account={status.get('account')} size={24} /></div> <div className='reply-indicator__display-avatar'><Avatar account={status.get('account')} size={24} /></div>

View file

@ -8,6 +8,10 @@ import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { ReactComponent as CancelIcon } from '@material-symbols/svg-600/outlined/cancel-fill.svg';
import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg';
import { ReactComponent as SearchIcon } from '@material-symbols/svg-600/outlined/search.svg';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { domain, searchEnabled } from 'flavours/glitch/initial_state'; import { domain, searchEnabled } from 'flavours/glitch/initial_state';
import { HASHTAG_REGEX } from 'flavours/glitch/utils/hashtags'; import { HASHTAG_REGEX } from 'flavours/glitch/utils/hashtags';
@ -333,8 +337,8 @@ class Search extends PureComponent {
/> />
<div role='button' tabIndex={0} className='search__icon' onClick={this.handleClear}> <div role='button' tabIndex={0} className='search__icon' onClick={this.handleClear}>
<Icon id='search' className={hasValue ? '' : 'active'} /> <Icon id='search' icon={SearchIcon} className={hasValue ? '' : 'active'} />
<Icon id='times-circle' className={hasValue ? 'active' : ''} /> <Icon id='times-circle' icon={CancelIcon} className={hasValue ? 'active' : ''} />
</div> </div>
<div className='search__popout'> <div className='search__popout'>
@ -346,7 +350,7 @@ class Search extends PureComponent {
{recent.size > 0 ? this._getOptions().map(({ label, action, forget }, i) => ( {recent.size > 0 ? this._getOptions().map(({ label, action, forget }, i) => (
<button key={label} onMouseDown={action} className={classNames('search__popout__menu__item search__popout__menu__item--flex', { selected: selectedOption === i })}> <button key={label} onMouseDown={action} className={classNames('search__popout__menu__item search__popout__menu__item--flex', { selected: selectedOption === i })}>
<span>{label}</span> <span>{label}</span>
<button className='icon-button' onMouseDown={forget}><Icon id='times' /></button> <button className='icon-button' onMouseDown={forget}><Icon id='times' icon={CloseIcon} /></button>
</button> </button>
)) : ( )) : (
<div className='search__popout__menu__message'> <div className='search__popout__menu__message'>

View file

@ -5,6 +5,11 @@ import { FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { ReactComponent as FindInPageIcon } from '@material-symbols/svg-600/outlined/find_in_page.svg';
import { ReactComponent as PeopleIcon } from '@material-symbols/svg-600/outlined/group.svg';
import { ReactComponent as SearchIcon } from '@material-symbols/svg-600/outlined/search.svg';
import { ReactComponent as TagIcon } from '@material-symbols/svg-600/outlined/tag.svg';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { LoadMore } from 'flavours/glitch/components/load_more'; import { LoadMore } from 'flavours/glitch/components/load_more';
import { SearchSection } from 'flavours/glitch/features/explore/components/search_section'; import { SearchSection } from 'flavours/glitch/features/explore/components/search_section';
@ -44,7 +49,7 @@ class SearchResults extends ImmutablePureComponent {
if (results.get('accounts') && results.get('accounts').size > 0) { if (results.get('accounts') && results.get('accounts').size > 0) {
accounts = ( accounts = (
<SearchSection title={<><Icon id='users' fixedWidth /><FormattedMessage id='search_results.accounts' defaultMessage='Profiles' /></>}> <SearchSection title={<><Icon id='users' icon={PeopleIcon} /><FormattedMessage id='search_results.accounts' defaultMessage='Profiles' /></>}>
{withoutLastResult(results.get('accounts')).map(accountId => <AccountContainer key={accountId} id={accountId} />)} {withoutLastResult(results.get('accounts')).map(accountId => <AccountContainer key={accountId} id={accountId} />)}
{(results.get('accounts').size > INITIAL_PAGE_LIMIT && results.get('accounts').size % INITIAL_PAGE_LIMIT === 1) && <LoadMore visible onClick={this.handleLoadMoreAccounts} />} {(results.get('accounts').size > INITIAL_PAGE_LIMIT && results.get('accounts').size % INITIAL_PAGE_LIMIT === 1) && <LoadMore visible onClick={this.handleLoadMoreAccounts} />}
</SearchSection> </SearchSection>
@ -53,7 +58,7 @@ class SearchResults extends ImmutablePureComponent {
if (results.get('hashtags') && results.get('hashtags').size > 0) { if (results.get('hashtags') && results.get('hashtags').size > 0) {
hashtags = ( hashtags = (
<SearchSection title={<><Icon id='hashtag' fixedWidth /><FormattedMessage id='search_results.hashtags' defaultMessage='Hashtags' /></>}> <SearchSection title={<><Icon id='hashtag' icon={TagIcon} /><FormattedMessage id='search_results.hashtags' defaultMessage='Hashtags' /></>}>
{withoutLastResult(results.get('hashtags')).map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />)} {withoutLastResult(results.get('hashtags')).map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />)}
{(results.get('hashtags').size > INITIAL_PAGE_LIMIT && results.get('hashtags').size % INITIAL_PAGE_LIMIT === 1) && <LoadMore visible onClick={this.handleLoadMoreHashtags} />} {(results.get('hashtags').size > INITIAL_PAGE_LIMIT && results.get('hashtags').size % INITIAL_PAGE_LIMIT === 1) && <LoadMore visible onClick={this.handleLoadMoreHashtags} />}
</SearchSection> </SearchSection>
@ -62,7 +67,7 @@ class SearchResults extends ImmutablePureComponent {
if (results.get('statuses') && results.get('statuses').size > 0) { if (results.get('statuses') && results.get('statuses').size > 0) {
statuses = ( statuses = (
<SearchSection title={<><Icon id='quote-right' fixedWidth /><FormattedMessage id='search_results.statuses' defaultMessage='Posts' /></>}> <SearchSection title={<><Icon id='quote-right' icon={FindInPageIcon} /><FormattedMessage id='search_results.statuses' defaultMessage='Posts' /></>}>
{withoutLastResult(results.get('statuses')).map(statusId => <StatusContainer key={statusId} id={statusId} />)} {withoutLastResult(results.get('statuses')).map(statusId => <StatusContainer key={statusId} id={statusId} />)}
{(results.get('statuses').size > INITIAL_PAGE_LIMIT && results.get('statuses').size % INITIAL_PAGE_LIMIT === 1) && <LoadMore visible onClick={this.handleLoadMoreStatuses} />} {(results.get('statuses').size > INITIAL_PAGE_LIMIT && results.get('statuses').size % INITIAL_PAGE_LIMIT === 1) && <LoadMore visible onClick={this.handleLoadMoreStatuses} />}
</SearchSection> </SearchSection>
@ -73,7 +78,7 @@ class SearchResults extends ImmutablePureComponent {
return ( return (
<div className='drawer--results'> <div className='drawer--results'>
<header className='search-results__header'> <header className='search-results__header'>
<Icon id='search' fixedWidth /> <Icon id='search' icon={SearchIcon} />
<FormattedMessage id='explore.search_results' defaultMessage='Search results' /> <FormattedMessage id='explore.search_results' defaultMessage='Search results' />
</header> </header>

View file

@ -1,4 +1,3 @@
// Package imports.
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
@ -6,9 +5,11 @@ import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
// Components. import { ReactComponent as ForumIcon } from '@material-symbols/svg-600/outlined/forum.svg';
import { ReactComponent as HomeIcon } from '@material-symbols/svg-600/outlined/home-fill.svg';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
// Messages.
const messages = defineMessages({ const messages = defineMessages({
localOnly: { localOnly: {
defaultMessage: 'This post is local-only', defaultMessage: 'This post is local-only',
@ -23,8 +24,8 @@ const messages = defineMessages({
// We use an array of tuples here instead of an object because it // We use an array of tuples here instead of an object because it
// preserves order. // preserves order.
const iconMap = [ const iconMap = [
['do_not_federate', 'home', messages.localOnly], ['do_not_federate', 'home', HomeIcon, messages.localOnly],
['threaded_mode', 'comments', messages.threadedMode], ['threaded_mode', 'comments', ForumIcon, messages.threadedMode],
]; ];
class TextareaIcons extends ImmutablePureComponent { class TextareaIcons extends ImmutablePureComponent {
@ -38,20 +39,17 @@ class TextareaIcons extends ImmutablePureComponent {
const { advancedOptions, intl } = this.props; const { advancedOptions, intl } = this.props;
return ( return (
<div className='compose-form__textarea-icons'> <div className='compose-form__textarea-icons'>
{advancedOptions ? iconMap.map( {advancedOptions && iconMap.map(
([key, icon, message]) => advancedOptions.get(key) ? ( ([key, icon, iconComponent, message]) => advancedOptions.get(key) && (
<span <span
className='textarea_icon' className='textarea_icon'
key={key} key={key}
title={intl.formatMessage(message)} title={intl.formatMessage(message)}
> >
<Icon <Icon id={icon} icon={iconComponent} />
fixedWidth
id={icon}
/>
</span> </span>
) : null, ),
) : null} )}
</div> </div>
); );
} }

View file

@ -5,6 +5,9 @@ import { FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg';
import { ReactComponent as EditIcon } from '@material-symbols/svg-600/outlined/edit.svg';
import { ReactComponent as InfoIcon } from '@material-symbols/svg-600/outlined/info.svg';
import spring from 'react-motion/lib/spring'; import spring from 'react-motion/lib/spring';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
@ -47,13 +50,13 @@ export default class Upload extends ImmutablePureComponent {
{({ scale }) => ( {({ scale }) => (
<div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}> <div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}>
<div className='compose-form__upload__actions'> <div className='compose-form__upload__actions'>
<button type='button' className='icon-button' onClick={this.handleUndoClick}><Icon id='times' /> <FormattedMessage id='upload_form.undo' defaultMessage='Delete' /></button> <button type='button' className='icon-button' onClick={this.handleUndoClick}><Icon id='times' icon={CloseIcon} /> <FormattedMessage id='upload_form.undo' defaultMessage='Delete' /></button>
<button type='button' className='icon-button' onClick={this.handleFocalPointClick}><Icon id='pencil' /> <FormattedMessage id='upload_form.edit' defaultMessage='Edit' /></button> <button type='button' className='icon-button' onClick={this.handleFocalPointClick}><Icon id='pencil' icon={EditIcon} /> <FormattedMessage id='upload_form.edit' defaultMessage='Edit' /></button>
</div> </div>
{(media.get('description') || '').length === 0 && ( {(media.get('description') || '').length === 0 && (
<div className='compose-form__upload__warning'> <div className='compose-form__upload__warning'>
<button type='button' className='icon-button' onClick={this.handleFocalPointClick}><Icon id='info-circle' /> <FormattedMessage id='upload_form.description_missing' defaultMessage='No description added' /></button> <button type='button' className='icon-button' onClick={this.handleFocalPointClick}><Icon id='info-circle' icon={InfoIcon} /> <FormattedMessage id='upload_form.description_missing' defaultMessage='No description added' /></button>
</div> </div>
)} )}
</div> </div>

View file

@ -3,6 +3,7 @@ import { PureComponent } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { ReactComponent as UploadFileIcon } from '@material-symbols/svg-600/outlined/upload_file.svg';
import spring from 'react-motion/lib/spring'; import spring from 'react-motion/lib/spring';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
@ -35,7 +36,7 @@ export default class UploadProgress extends PureComponent {
return ( return (
<div className='upload-progress'> <div className='upload-progress'>
<div className='upload-progress__icon'> <div className='upload-progress__icon'>
<Icon id='upload' /> <Icon id='upload' icon={UploadFileIcon} />
</div> </div>
<div className='upload-progress__message'> <div className='upload-progress__message'>

View file

@ -8,6 +8,8 @@ import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { ReactComponent as MoreHorizIcon } from '@material-symbols/svg-600/outlined/more_horiz.svg';
import { ReactComponent as ReplyIcon } from '@material-symbols/svg-600/outlined/reply.svg';
import { HotKeys } from 'react-hotkeys'; import { HotKeys } from 'react-hotkeys';
import AttachmentList from 'flavours/glitch/components/attachment_list'; import AttachmentList from 'flavours/glitch/components/attachment_list';
@ -208,7 +210,7 @@ class Conversation extends ImmutablePureComponent {
/> />
<div className='status__action-bar'> <div className='status__action-bar'>
<IconButton className='status__action-bar-button' title={intl.formatMessage(messages.reply)} icon='reply' onClick={this.handleReply} /> <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.reply)} icon='reply' iconComponent={ReplyIcon} onClick={this.handleReply} />
<div className='status__action-bar-dropdown'> <div className='status__action-bar-dropdown'>
<DropdownMenuContainer <DropdownMenuContainer
@ -216,6 +218,7 @@ class Conversation extends ImmutablePureComponent {
status={lastStatus} status={lastStatus}
items={menu} items={menu}
icon='ellipsis-h' icon='ellipsis-h'
iconComponent={MoreHorizIcon}
size={18} size={18}
direction='right' direction='right'
title={intl.formatMessage(messages.more)} title={intl.formatMessage(messages.more)}

View file

@ -7,6 +7,8 @@ import { Helmet } from 'react-helmet';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as MailIcon } from '@material-symbols/svg-600/outlined/mail.svg';
import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns'; import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns';
import { mountConversations, unmountConversations, expandConversations } from 'flavours/glitch/actions/conversations'; import { mountConversations, unmountConversations, expandConversations } from 'flavours/glitch/actions/conversations';
import { connectDirectStream } from 'flavours/glitch/actions/streaming'; import { connectDirectStream } from 'flavours/glitch/actions/streaming';
@ -139,6 +141,7 @@ class DirectTimeline extends PureComponent {
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
<ColumnHeader <ColumnHeader
icon='envelope' icon='envelope'
iconComponent={MailIcon}
active={hasUnread} active={hasUnread}
title={intl.formatMessage(messages.title)} title={intl.formatMessage(messages.title)}
onPin={this.handlePin} onPin={this.handlePin}

View file

@ -9,6 +9,8 @@ import { List as ImmutableList } from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as PeopleIcon } from '@material-symbols/svg-600/outlined/group.svg';
import { addColumn, removeColumn, moveColumn, changeColumnParams } from 'flavours/glitch/actions/columns'; import { addColumn, removeColumn, moveColumn, changeColumnParams } from 'flavours/glitch/actions/columns';
import { fetchDirectory, expandDirectory } from 'flavours/glitch/actions/directory'; import { fetchDirectory, expandDirectory } from 'flavours/glitch/actions/directory';
import Column from 'flavours/glitch/components/column'; import Column from 'flavours/glitch/components/column';
@ -156,6 +158,7 @@ class Directory extends PureComponent {
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
<ColumnHeader <ColumnHeader
icon='address-book-o' icon='address-book-o'
iconComponent={PeopleIcon}
title={intl.formatMessage(messages.title)} title={intl.formatMessage(messages.title)}
onPin={this.handlePin} onPin={this.handlePin}
onMove={this.handleMove} onMove={this.handleMove}

View file

@ -8,10 +8,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as BlockIcon } from '@material-symbols/svg-600/outlined/block-fill.svg';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_blocks'; import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_blocks';
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import { LoadingIndicator } from '../../components/loading_indicator'; import { LoadingIndicator } from '../../components/loading_indicator';
import ScrollableList from '../../components/scrollable_list'; import ScrollableList from '../../components/scrollable_list';
import DomainContainer from '../../containers/domain_container'; import DomainContainer from '../../containers/domain_container';
@ -60,9 +60,7 @@ class Blocks extends ImmutablePureComponent {
const emptyMessage = <FormattedMessage id='empty_column.domain_blocks' defaultMessage='There are no blocked domains yet.' />; const emptyMessage = <FormattedMessage id='empty_column.domain_blocks' defaultMessage='There are no blocked domains yet.' />;
return ( return (
<Column bindToDocument={!multiColumn} icon='minus-circle' heading={intl.formatMessage(messages.heading)}> <Column bindToDocument={!multiColumn} icon='ban' iconComponent={BlockIcon} heading={intl.formatMessage(messages.heading)} alwaysShowBackButton>
<ColumnBackButtonSlim />
<ScrollableList <ScrollableList
scrollKey='domain_blocks' scrollKey='domain_blocks'
onLoadMore={this.handleLoadMore} onLoadMore={this.handleLoadMore}

View file

@ -8,6 +8,9 @@ import { NavLink, Switch, Route } from 'react-router-dom';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as SearchIcon } from '@material-symbols/svg-600/outlined/search.svg';
import { ReactComponent as TagIcon } from '@material-symbols/svg-600/outlined/tag.svg';
import Column from 'flavours/glitch/components/column'; import Column from 'flavours/glitch/components/column';
import ColumnHeader from 'flavours/glitch/components/column_header'; import ColumnHeader from 'flavours/glitch/components/column_header';
import Search from 'flavours/glitch/features/compose/containers/search_container'; import Search from 'flavours/glitch/features/compose/containers/search_container';
@ -57,6 +60,7 @@ class Explore extends PureComponent {
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
<ColumnHeader <ColumnHeader
icon={isSearching ? 'search' : 'hashtag'} icon={isSearching ? 'search' : 'hashtag'}
iconComponent={isSearching ? SearchIcon : TagIcon}
title={intl.formatMessage(isSearching ? messages.searchResults : messages.title)} title={intl.formatMessage(isSearching ? messages.searchResults : messages.title)}
onClick={this.handleHeaderClick} onClick={this.handleHeaderClick}
multiColumn={multiColumn} multiColumn={multiColumn}

View file

@ -9,6 +9,10 @@ import { List as ImmutableList } from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as FindInPageIcon } from '@material-symbols/svg-600/outlined/find_in_page.svg';
import { ReactComponent as PeopleIcon } from '@material-symbols/svg-600/outlined/group.svg';
import { ReactComponent as TagIcon } from '@material-symbols/svg-600/outlined/tag.svg';
import { submitSearch, expandSearch } from 'flavours/glitch/actions/search'; import { submitSearch, expandSearch } from 'flavours/glitch/actions/search';
import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag'; import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
@ -165,19 +169,19 @@ class Results extends PureComponent {
filteredResults = (accounts.size + hashtags.size + statuses.size) > 0 ? ( filteredResults = (accounts.size + hashtags.size + statuses.size) > 0 ? (
<> <>
{accounts.size > 0 && ( {accounts.size > 0 && (
<SearchSection key='accounts' title={<><Icon id='users' fixedWidth /><FormattedMessage id='search_results.accounts' defaultMessage='Profiles' /></>} onClickMore={this.handleLoadMoreAccounts}> <SearchSection key='accounts' title={<><Icon id='users' icon={PeopleIcon} /><FormattedMessage id='search_results.accounts' defaultMessage='Profiles' /></>} onClickMore={this.handleLoadMoreAccounts}>
{accounts.take(INITIAL_DISPLAY).map(id => <Account key={id} id={id} />)} {accounts.take(INITIAL_DISPLAY).map(id => <Account key={id} id={id} />)}
</SearchSection> </SearchSection>
)} )}
{hashtags.size > 0 && ( {hashtags.size > 0 && (
<SearchSection key='hashtags' title={<><Icon id='hashtag' fixedWidth /><FormattedMessage id='search_results.hashtags' defaultMessage='Hashtags' /></>} onClickMore={this.handleLoadMoreHashtags}> <SearchSection key='hashtags' title={<><Icon id='hashtag' icon={TagIcon} /><FormattedMessage id='search_results.hashtags' defaultMessage='Hashtags' /></>} onClickMore={this.handleLoadMoreHashtags}>
{hashtags.take(INITIAL_DISPLAY).map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />)} {hashtags.take(INITIAL_DISPLAY).map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />)}
</SearchSection> </SearchSection>
)} )}
{statuses.size > 0 && ( {statuses.size > 0 && (
<SearchSection key='statuses' title={<><Icon id='quote-right' fixedWidth /><FormattedMessage id='search_results.statuses' defaultMessage='Posts' /></>} onClickMore={this.handleLoadMoreStatuses}> <SearchSection key='statuses' title={<><Icon id='quote-right' icon={FindInPageIcon} /><FormattedMessage id='search_results.statuses' defaultMessage='Posts' /></>} onClickMore={this.handleLoadMoreStatuses}>
{statuses.take(INITIAL_DISPLAY).map(id => <Status key={id} id={id} />)} {statuses.take(INITIAL_DISPLAY).map(id => <Status key={id} id={id} />)}
</SearchSection> </SearchSection>
)} )}

View file

@ -8,6 +8,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star-fill.svg';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns'; import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns';
@ -80,6 +81,7 @@ class Favourites extends ImmutablePureComponent {
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.heading)}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.heading)}>
<ColumnHeader <ColumnHeader
icon='star' icon='star'
iconComponent={StarIcon}
title={intl.formatMessage(messages.heading)} title={intl.formatMessage(messages.heading)}
onPin={this.handlePin} onPin={this.handlePin}
onMove={this.handleMove} onMove={this.handleMove}

View file

@ -8,6 +8,8 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as RefreshIcon } from '@material-symbols/svg-600/outlined/refresh.svg';
import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star-fill.svg';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { fetchFavourites, expandFavourites } from 'flavours/glitch/actions/interactions'; import { fetchFavourites, expandFavourites } from 'flavours/glitch/actions/interactions';
@ -80,12 +82,13 @@ class Favourites extends ImmutablePureComponent {
<Column ref={this.setRef}> <Column ref={this.setRef}>
<ColumnHeader <ColumnHeader
icon='star' icon='star'
iconComponent={StarIcon}
title={intl.formatMessage(messages.heading)} title={intl.formatMessage(messages.heading)}
onClick={this.handleHeaderClick} onClick={this.handleHeaderClick}
showBackButton showBackButton
multiColumn={multiColumn} multiColumn={multiColumn}
extraButton={( extraButton={(
<button type='button' className='column-header__button' title={intl.formatMessage(messages.refresh)} aria-label={intl.formatMessage(messages.refresh)} onClick={this.handleRefresh}><Icon id='refresh' /></button> <button type='button' className='column-header__button' title={intl.formatMessage(messages.refresh)} aria-label={intl.formatMessage(messages.refresh)} onClick={this.handleRefresh}><Icon id='refresh' icon={RefreshIcon} /></button>
)} )}
/> />

View file

@ -5,6 +5,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as AddIcon } from '@material-symbols/svg-600/outlined/add.svg';
import fuzzysort from 'fuzzysort'; import fuzzysort from 'fuzzysort';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
@ -78,7 +79,7 @@ class SelectFilter extends PureComponent {
renderCreateNew (name) { renderCreateNew (name) {
return ( return (
<div key='add-new-filter' role='button' tabIndex={0} className='language-dropdown__dropdown__results__item' onClick={this.handleNewFilterClick} onKeyDown={this.handleKeyDown}> <div key='add-new-filter' role='button' tabIndex={0} className='language-dropdown__dropdown__results__item' onClick={this.handleNewFilterClick} onKeyDown={this.handleKeyDown}>
<Icon id='plus' fixedWidth /> <FormattedMessage id='filter_modal.select_filter.prompt_new' defaultMessage='New category: {name}' values={{ name }} /> <Icon id='plus' icon={AddIcon} /> <FormattedMessage id='filter_modal.select_filter.prompt_new' defaultMessage='New category: {name}' values={{ name }} />
</div> </div>
); );
} }

View file

@ -6,6 +6,8 @@ import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import { NavLink } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
import { ReactComponent as PublicIcon } from '@material-symbols/svg-600/outlined/public.svg';
import { addColumn } from 'flavours/glitch/actions/columns'; import { addColumn } from 'flavours/glitch/actions/columns';
import { changeSetting } from 'flavours/glitch/actions/settings'; import { changeSetting } from 'flavours/glitch/actions/settings';
import { connectPublicStream, connectCommunityStream } from 'flavours/glitch/actions/streaming'; import { connectPublicStream, connectCommunityStream } from 'flavours/glitch/actions/streaming';
@ -179,6 +181,7 @@ const Firehose = ({ feedType, multiColumn }) => {
<Column bindToDocument={!multiColumn} ref={columnRef} label={intl.formatMessage(messages.title)}> <Column bindToDocument={!multiColumn} ref={columnRef} label={intl.formatMessage(messages.title)}>
<ColumnHeader <ColumnHeader
icon='globe' icon='globe'
iconComponent={PublicIcon}
active={hasUnread} active={hasUnread}
title={intl.formatMessage(messages.title)} title={intl.formatMessage(messages.title)}
onPin={handlePin} onPin={handlePin}

View file

@ -5,6 +5,9 @@ import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/check.svg';
import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg';
import { Avatar } from '../../../components/avatar'; import { Avatar } from '../../../components/avatar';
import { DisplayName } from '../../../components/display_name'; import { DisplayName } from '../../../components/display_name';
import { IconButton } from '../../../components/icon_button'; import { IconButton } from '../../../components/icon_button';
@ -40,8 +43,8 @@ class AccountAuthorize extends ImmutablePureComponent {
</div> </div>
<div className='account--panel'> <div className='account--panel'>
<div className='account--panel__button'><IconButton title={intl.formatMessage(messages.authorize)} icon='check' onClick={onAuthorize} /></div> <div className='account--panel__button'><IconButton title={intl.formatMessage(messages.authorize)} icon='check' iconComponent={CheckIcon} onClick={onAuthorize} /></div>
<div className='account--panel__button'><IconButton title={intl.formatMessage(messages.reject)} icon='times' onClick={onReject} /></div> <div className='account--panel__button'><IconButton title={intl.formatMessage(messages.reject)} icon='times' iconComponent={CloseIcon} onClick={onReject} /></div>
</div> </div>
</div> </div>
); );

View file

@ -8,10 +8,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as PersonAddIcon } from '@material-symbols/svg-600/outlined/person_add.svg';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { fetchFollowRequests, expandFollowRequests } from '../../actions/accounts'; import { fetchFollowRequests, expandFollowRequests } from '../../actions/accounts';
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import ScrollableList from '../../components/scrollable_list'; import ScrollableList from '../../components/scrollable_list';
import { me } from '../../initial_state'; import { me } from '../../initial_state';
import Column from '../ui/components/column'; import Column from '../ui/components/column';
@ -67,8 +67,7 @@ class FollowRequests extends ImmutablePureComponent {
); );
return ( return (
<Column bindToDocument={!multiColumn} icon='user-plus' heading={intl.formatMessage(messages.heading)}> <Column bindToDocument={!multiColumn} icon='user-plus' iconComponent={PersonAddIcon} heading={intl.formatMessage(messages.heading)} alwaysShowBackButton>
<ColumnBackButtonSlim />
<ScrollableList <ScrollableList
scrollKey='follow_requests' scrollKey='follow_requests'
onLoadMore={this.handleLoadMore} onLoadMore={this.handleLoadMore}

View file

@ -8,6 +8,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as TagIcon } from '@material-symbols/svg-600/outlined/tag.svg';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { expandFollowedHashtags, fetchFollowedHashtags } from 'flavours/glitch/actions/tags'; import { expandFollowedHashtags, fetchFollowedHashtags } from 'flavours/glitch/actions/tags';
@ -55,6 +56,7 @@ class FollowedTags extends ImmutablePureComponent {
<Column bindToDocument={!multiColumn}> <Column bindToDocument={!multiColumn}>
<ColumnHeader <ColumnHeader
icon='hashtag' icon='hashtag'
iconComponent={TagIcon}
title={intl.formatMessage(messages.heading)} title={intl.formatMessage(messages.heading)}
showBackButton showBackButton
multiColumn={multiColumn} multiColumn={multiColumn}

View file

@ -9,6 +9,9 @@ import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { ReactComponent as AddIcon } from '@material-symbols/svg-600/outlined/add.svg';
import { ReactComponent as ChevronLeftIcon } from '@material-symbols/svg-600/outlined/chevron_left.svg';
import { ReactComponent as ChevronRightIcon } from '@material-symbols/svg-600/outlined/chevron_right.svg';
import TransitionMotion from 'react-motion/lib/TransitionMotion'; import TransitionMotion from 'react-motion/lib/TransitionMotion';
import spring from 'react-motion/lib/spring'; import spring from 'react-motion/lib/spring';
import ReactSwipeableViews from 'react-swipeable-views'; import ReactSwipeableViews from 'react-swipeable-views';
@ -294,7 +297,7 @@ class ReactionsBar extends ImmutablePureComponent {
/> />
))} ))}
{visibleReactions.size < 8 && <EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} button={<Icon id='plus' />} />} {visibleReactions.size < 8 && <EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} button={<Icon id='plus' icon={AddIcon} />} />}
</div> </div>
)} )}
</TransitionMotion> </TransitionMotion>
@ -440,9 +443,9 @@ class Announcements extends ImmutablePureComponent {
{announcements.size > 1 && ( {announcements.size > 1 && (
<div className='announcements__pagination'> <div className='announcements__pagination'>
<IconButton disabled={announcements.size === 1} title={intl.formatMessage(messages.previous)} icon='chevron-left' onClick={this.handlePrevClick} size={13} /> <IconButton disabled={announcements.size === 1} title={intl.formatMessage(messages.previous)} icon='chevron-left' iconComponent={ChevronLeftIcon} onClick={this.handlePrevClick} size={13} />
<span>{index + 1} / {announcements.size}</span> <span>{index + 1} / {announcements.size}</span>
<IconButton disabled={announcements.size === 1} title={intl.formatMessage(messages.next)} icon='chevron-right' onClick={this.handleNextClick} size={13} /> <IconButton disabled={announcements.size === 1} title={intl.formatMessage(messages.next)} icon='chevron-right' iconComponent={ChevronRightIcon} onClick={this.handleNextClick} size={13} />
</div> </div>
)} )}
</div> </div>

View file

@ -10,6 +10,20 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as BookmarksIcon } from '@material-symbols/svg-600/outlined/bookmarks-fill.svg';
import { ReactComponent as PeopleIcon } from '@material-symbols/svg-600/outlined/group.svg';
import { ReactComponent as HomeIcon } from '@material-symbols/svg-600/outlined/home-fill.svg';
import { ReactComponent as ListAltIcon } from '@material-symbols/svg-600/outlined/list_alt.svg';
import { ReactComponent as MailIcon } from '@material-symbols/svg-600/outlined/mail.svg';
import { ReactComponent as ManufacturingIcon } from '@material-symbols/svg-600/outlined/manufacturing.svg';
import { ReactComponent as MenuIcon } from '@material-symbols/svg-600/outlined/menu.svg';
import { ReactComponent as MoreHorizIcon } from '@material-symbols/svg-600/outlined/more_horiz.svg';
import { ReactComponent as NotificationsIcon } from '@material-symbols/svg-600/outlined/notifications.svg';
import { ReactComponent as PersonAddIcon } from '@material-symbols/svg-600/outlined/person_add.svg';
import { ReactComponent as PublicIcon } from '@material-symbols/svg-600/outlined/public.svg';
import { ReactComponent as SettingsIcon } from '@material-symbols/svg-600/outlined/settings-fill.svg';
import { ReactComponent as TagIcon } from '@material-symbols/svg-600/outlined/tag.svg';
import { fetchFollowRequests } from 'flavours/glitch/actions/accounts'; import { fetchFollowRequests } from 'flavours/glitch/actions/accounts';
import { fetchLists } from 'flavours/glitch/actions/lists'; import { fetchLists } from 'flavours/glitch/actions/lists';
import { openModal } from 'flavours/glitch/actions/modal'; import { openModal } from 'flavours/glitch/actions/modal';
@ -127,53 +141,53 @@ class GettingStarted extends ImmutablePureComponent {
if (multiColumn) { if (multiColumn) {
if (signedIn && !columns.find(item => item.get('id') === 'HOME')) { if (signedIn && !columns.find(item => item.get('id') === 'HOME')) {
navItems.push(<ColumnLink key='home' icon='home' text={intl.formatMessage(messages.home_timeline)} to='/home' />); navItems.push(<ColumnLink key='home' icon='home' iconComponent={HomeIcon} text={intl.formatMessage(messages.home_timeline)} to='/home' />);
} }
if (!columns.find(item => item.get('id') === 'NOTIFICATIONS')) { if (!columns.find(item => item.get('id') === 'NOTIFICATIONS')) {
navItems.push(<ColumnLink key='notifications' icon='bell' text={intl.formatMessage(messages.notifications)} badge={badgeDisplay(unreadNotifications)} to='/notifications' />); navItems.push(<ColumnLink key='notifications' icon='bell' iconComponent={NotificationsIcon} text={intl.formatMessage(messages.notifications)} badge={badgeDisplay(unreadNotifications)} to='/notifications' />);
} }
if (!columns.find(item => item.get('id') === 'COMMUNITY')) { if (!columns.find(item => item.get('id') === 'COMMUNITY')) {
navItems.push(<ColumnLink key='community_timeline' icon='users' text={intl.formatMessage(messages.community_timeline)} to='/public/local' />); navItems.push(<ColumnLink key='community_timeline' icon='users' iconComponent={PeopleIcon} text={intl.formatMessage(messages.community_timeline)} to='/public/local' />);
} }
if (!columns.find(item => item.get('id') === 'PUBLIC')) { if (!columns.find(item => item.get('id') === 'PUBLIC')) {
navItems.push(<ColumnLink key='public_timeline' icon='globe' text={intl.formatMessage(messages.public_timeline)} to='/public' />); navItems.push(<ColumnLink key='public_timeline' icon='globe' iconComponent={PublicIcon} text={intl.formatMessage(messages.public_timeline)} to='/public' />);
} }
} }
if (showTrends) { if (showTrends) {
navItems.push(<ColumnLink key='explore' icon='hashtag' text={intl.formatMessage(messages.explore)} to='/explore' />); navItems.push(<ColumnLink key='explore' icon='hashtag' iconComponent={TagIcon} text={intl.formatMessage(messages.explore)} to='/explore' />);
} }
if (signedIn) { if (signedIn) {
if (!multiColumn || !columns.find(item => item.get('id') === 'DIRECT')) { if (!multiColumn || !columns.find(item => item.get('id') === 'DIRECT')) {
navItems.push(<ColumnLink key='conversations' icon='envelope' text={intl.formatMessage(messages.direct)} to='/conversations' />); navItems.push(<ColumnLink key='conversations' icon='envelope' iconComponent={MailIcon} text={intl.formatMessage(messages.direct)} to='/conversations' />);
} }
if (!multiColumn || !columns.find(item => item.get('id') === 'BOOKMARKS')) { if (!multiColumn || !columns.find(item => item.get('id') === 'BOOKMARKS')) {
navItems.push(<ColumnLink key='bookmarks' icon='bookmark' text={intl.formatMessage(messages.bookmarks)} to='/bookmarks' />); navItems.push(<ColumnLink key='bookmarks' icon='bookmark' iconComponent={BookmarksIcon} text={intl.formatMessage(messages.bookmarks)} to='/bookmarks' />);
} }
if (myAccount.get('locked') || unreadFollowRequests > 0) { if (myAccount.get('locked') || unreadFollowRequests > 0) {
navItems.push(<ColumnLink key='follow_requests' icon='user-plus' text={intl.formatMessage(messages.follow_requests)} badge={badgeDisplay(unreadFollowRequests, 40)} to='/follow_requests' />); navItems.push(<ColumnLink key='follow_requests' icon='user-plus' iconComponent={PersonAddIcon} text={intl.formatMessage(messages.follow_requests)} badge={badgeDisplay(unreadFollowRequests, 40)} to='/follow_requests' />);
} }
navItems.push(<ColumnLink key='getting_started' icon='ellipsis-h' text={intl.formatMessage(messages.misc)} to='/getting-started-misc' />); navItems.push(<ColumnLink key='getting_started' icon='ellipsis-h' iconComponent={MoreHorizIcon} text={intl.formatMessage(messages.misc)} to='/getting-started-misc' />);
listItems = listItems.concat([ listItems = listItems.concat([
<div key='9'> <div key='9'>
<ColumnLink key='lists' icon='bars' text={intl.formatMessage(messages.lists)} to='/lists' /> <ColumnLink key='lists' icon='bars' iconComponent={ListAltIcon} text={intl.formatMessage(messages.lists)} to='/lists' />
{lists.filter(list => !columns.find(item => item.get('id') === 'LIST' && item.getIn(['params', 'id']) === list.get('id'))).map(list => {lists.filter(list => !columns.find(item => item.get('id') === 'LIST' && item.getIn(['params', 'id']) === list.get('id'))).map(list =>
<ColumnLink key={`list-${list.get('id')}`} to={`/lists/${list.get('id')}`} icon='list-ul' text={list.get('title')} />, <ColumnLink key={`list-${list.get('id')}`} to={`/lists/${list.get('id')}`} icon='list-ul' iconComponent={ListAltIcon} text={list.get('title')} />,
)} )}
</div>, </div>,
]); ]);
} }
return ( return (
<Column bindToDocument={!multiColumn} icon='asterisk' heading={intl.formatMessage(messages.heading)} label={intl.formatMessage(messages.menu)} hideHeadingOnMobile> <Column bindToDocument={!multiColumn} icon='bars' iconComponent={MenuIcon} heading={intl.formatMessage(messages.heading)} label={intl.formatMessage(messages.menu)} hideHeadingOnMobile>
<div className='scrollable optionally-scrollable'> <div className='scrollable optionally-scrollable'>
<div className='getting-started__wrapper'> <div className='getting-started__wrapper'>
{!multiColumn && signedIn && <NavigationBar account={myAccount} />} {!multiColumn && signedIn && <NavigationBar account={myAccount} />}
@ -184,8 +198,8 @@ class GettingStarted extends ImmutablePureComponent {
<ColumnSubheading text={intl.formatMessage(messages.lists_subheading)} /> <ColumnSubheading text={intl.formatMessage(messages.lists_subheading)} />
{listItems} {listItems}
<ColumnSubheading text={intl.formatMessage(messages.settings_subheading)} /> <ColumnSubheading text={intl.formatMessage(messages.settings_subheading)} />
{ preferencesLink !== undefined && <ColumnLink icon='cog' text={intl.formatMessage(messages.preferences)} href={preferencesLink} /> } { preferencesLink !== undefined && <ColumnLink icon='cog' iconComponent={SettingsIcon} text={intl.formatMessage(messages.preferences)} href={preferencesLink} /> }
<ColumnLink icon='cogs' text={intl.formatMessage(messages.settings)} onClick={openSettings} /> <ColumnLink icon='cogs' iconComponent={ManufacturingIcon} text={intl.formatMessage(messages.settings)} onClick={openSettings} />
</> </>
)} )}
</div> </div>

View file

@ -5,13 +5,18 @@ import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as BlockIcon } from '@material-symbols/svg-600/outlined/block.svg';
import { ReactComponent as InfoIcon } from '@material-symbols/svg-600/outlined/info.svg';
import { ReactComponent as PersonCheckIcon } from '@material-symbols/svg-600/outlined/person_check.svg';
import { ReactComponent as PushPinIcon } from '@material-symbols/svg-600/outlined/push_pin.svg';
import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star-fill.svg';
import { ReactComponent as VolumeOffIcon } from '@material-symbols/svg-600/outlined/volume_off.svg';
import { openModal } from 'flavours/glitch/actions/modal'; import { openModal } from 'flavours/glitch/actions/modal';
import ColumnBackButtonSlim from 'flavours/glitch/components/column_back_button_slim';
import Column from 'flavours/glitch/features/ui/components/column'; import Column from 'flavours/glitch/features/ui/components/column';
import ColumnLink from 'flavours/glitch/features/ui/components/column_link'; import ColumnLink from 'flavours/glitch/features/ui/components/column_link';
import ColumnSubheading from 'flavours/glitch/features/ui/components/column_subheading'; import ColumnSubheading from 'flavours/glitch/features/ui/components/column_subheading';
const messages = defineMessages({ const messages = defineMessages({
heading: { id: 'column.heading', defaultMessage: 'Misc' }, heading: { id: 'column.heading', defaultMessage: 'Misc' },
subheading: { id: 'column.subheading', defaultMessage: 'Miscellaneous options' }, subheading: { id: 'column.subheading', defaultMessage: 'Miscellaneous options' },
@ -46,18 +51,16 @@ class GettingStartedMisc extends ImmutablePureComponent {
const { signedIn } = this.context.identity; const { signedIn } = this.context.identity;
return ( return (
<Column icon='ellipsis-h' heading={intl.formatMessage(messages.heading)}> <Column icon='ellipsis-h' heading={intl.formatMessage(messages.heading)} alwaysShowBackButton>
<ColumnBackButtonSlim />
<div className='scrollable'> <div className='scrollable'>
<ColumnSubheading text={intl.formatMessage(messages.subheading)} /> <ColumnSubheading text={intl.formatMessage(messages.subheading)} />
{signedIn && (<ColumnLink key='favourites' icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />)} {signedIn && (<ColumnLink key='favourites' icon='star' iconComponent={StarIcon} text={intl.formatMessage(messages.favourites)} to='/favourites' />)}
{signedIn && (<ColumnLink key='pinned' icon='thumb-tack' text={intl.formatMessage(messages.pins)} to='/pinned' />)} {signedIn && (<ColumnLink key='pinned' icon='thumb-tack' iconComponent={PushPinIcon} text={intl.formatMessage(messages.pins)} to='/pinned' />)}
{signedIn && (<ColumnLink key='featured_users' icon='users' text={intl.formatMessage(messages.featured_users)} onClick={this.openFeaturedAccountsModal} />)} {signedIn && (<ColumnLink key='featured_users' icon='users' iconComponent={PersonCheckIcon} text={intl.formatMessage(messages.featured_users)} onClick={this.openFeaturedAccountsModal} />)}
{signedIn && (<ColumnLink key='mutes' icon='volume-off' text={intl.formatMessage(messages.mutes)} to='/mutes' />)} {signedIn && (<ColumnLink key='mutes' icon='volume-off' iconComponent={VolumeOffIcon} text={intl.formatMessage(messages.mutes)} to='/mutes' />)}
{signedIn && (<ColumnLink key='blocks' icon='ban' text={intl.formatMessage(messages.blocks)} to='/blocks' />)} {signedIn && (<ColumnLink key='blocks' icon='ban' iconComponent={BlockIcon} text={intl.formatMessage(messages.blocks)} to='/blocks' />)}
{signedIn && (<ColumnLink key='domain_blocks' icon='minus-circle' text={intl.formatMessage(messages.domain_blocks)} to='/domain_blocks' />)} {signedIn && (<ColumnLink key='domain_blocks' icon='minus-circle' iconComponent={BlockIcon} text={intl.formatMessage(messages.domain_blocks)} to='/domain_blocks' />)}
<ColumnLink key='shortcuts' icon='question' text={intl.formatMessage(messages.keyboard_shortcuts)} to='/keyboard-shortcuts' /> <ColumnLink key='shortcuts' icon='question' iconComponent={InfoIcon} text={intl.formatMessage(messages.keyboard_shortcuts)} to='/keyboard-shortcuts' />
</div> </div>
</Column> </Column>
); );

View file

@ -8,6 +8,7 @@ import { Helmet } from 'react-helmet';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as TagIcon } from '@material-symbols/svg-600/outlined/tag.svg';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash';
import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns'; import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns';
@ -190,6 +191,7 @@ class HashtagTimeline extends PureComponent {
<Column bindToDocument={!multiColumn} ref={this.setRef} label={`#${id}`}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={`#${id}`}>
<ColumnHeader <ColumnHeader
icon='hashtag' icon='hashtag'
iconComponent={TagIcon}
active={hasUnread} active={hasUnread}
title={this.title()} title={this.title()}
onPin={this.handlePin} onPin={this.handlePin}

View file

@ -10,6 +10,9 @@ import { createSelector } from '@reduxjs/toolkit';
import { List as ImmutableList } from 'immutable'; import { List as ImmutableList } from 'immutable';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as CampaignIcon } from '@material-symbols/svg-600/outlined/campaign.svg';
import { ReactComponent as HomeIcon } from '@material-symbols/svg-600/outlined/home-fill.svg';
import { fetchAnnouncements, toggleShowAnnouncements } from 'flavours/glitch/actions/announcements'; import { fetchAnnouncements, toggleShowAnnouncements } from 'flavours/glitch/actions/announcements';
import { IconWithBadge } from 'flavours/glitch/components/icon_with_badge'; import { IconWithBadge } from 'flavours/glitch/components/icon_with_badge';
import { NotSignedInIndicator } from 'flavours/glitch/components/not_signed_in_indicator'; import { NotSignedInIndicator } from 'flavours/glitch/components/not_signed_in_indicator';
@ -182,7 +185,7 @@ class HomeTimeline extends PureComponent {
aria-label={intl.formatMessage(showAnnouncements ? messages.hide_announcements : messages.show_announcements)} aria-label={intl.formatMessage(showAnnouncements ? messages.hide_announcements : messages.show_announcements)}
onClick={this.handleToggleAnnouncementsClick} onClick={this.handleToggleAnnouncementsClick}
> >
<IconWithBadge id='bullhorn' count={unreadAnnouncements} /> <IconWithBadge id='bullhorn' icon={CampaignIcon} count={unreadAnnouncements} />
</button> </button>
); );
} }
@ -199,6 +202,7 @@ class HomeTimeline extends PureComponent {
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
<ColumnHeader <ColumnHeader
icon='home' icon='home'
iconComponent={HomeIcon}
active={hasUnread} active={hasUnread}
title={intl.formatMessage(messages.title)} title={intl.formatMessage(messages.title)}
onPin={this.handlePin} onPin={this.handlePin}

View file

@ -7,6 +7,10 @@ import classNames from 'classnames';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as PersonAddIcon } from '@material-symbols/svg-600/outlined/person_add.svg';
import { ReactComponent as RepeatIcon } from '@material-symbols/svg-600/outlined/repeat.svg';
import { ReactComponent as ReplyIcon } from '@material-symbols/svg-600/outlined/reply.svg';
import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star.svg';
import { throttle, escapeRegExp } from 'lodash'; import { throttle, escapeRegExp } from 'lodash';
import { openModal, closeModal } from 'flavours/glitch/actions/modal'; import { openModal, closeModal } from 'flavours/glitch/actions/modal';
@ -354,22 +358,22 @@ class InteractionModal extends React.PureComponent {
switch(type) { switch(type) {
case 'reply': case 'reply':
icon = <Icon id='reply' />; icon = <Icon id='reply' icon={ReplyIcon} />;
title = <FormattedMessage id='interaction_modal.title.reply' defaultMessage="Reply to {name}'s post" values={{ name }} />; title = <FormattedMessage id='interaction_modal.title.reply' defaultMessage="Reply to {name}'s post" values={{ name }} />;
actionDescription = <FormattedMessage id='interaction_modal.description.reply' defaultMessage='With an account on Mastodon, you can respond to this post.' />; actionDescription = <FormattedMessage id='interaction_modal.description.reply' defaultMessage='With an account on Mastodon, you can respond to this post.' />;
break; break;
case 'reblog': case 'reblog':
icon = <Icon id='retweet' />; icon = <Icon id='retweet' icon={RepeatIcon} />;
title = <FormattedMessage id='interaction_modal.title.reblog' defaultMessage="Boost {name}'s post" values={{ name }} />; title = <FormattedMessage id='interaction_modal.title.reblog' defaultMessage="Boost {name}'s post" values={{ name }} />;
actionDescription = <FormattedMessage id='interaction_modal.description.reblog' defaultMessage='With an account on Mastodon, you can boost this post to share it with your own followers.' />; actionDescription = <FormattedMessage id='interaction_modal.description.reblog' defaultMessage='With an account on Mastodon, you can boost this post to share it with your own followers.' />;
break; break;
case 'favourite': case 'favourite':
icon = <Icon id='star' />; icon = <Icon id='star' icon={StarIcon} />;
title = <FormattedMessage id='interaction_modal.title.favourite' defaultMessage="Favorite {name}'s post" values={{ name }} />; title = <FormattedMessage id='interaction_modal.title.favourite' defaultMessage="Favorite {name}'s post" values={{ name }} />;
actionDescription = <FormattedMessage id='interaction_modal.description.favourite' defaultMessage='With an account on Mastodon, you can favorite this post to let the author know you appreciate it and save it for later.' />; actionDescription = <FormattedMessage id='interaction_modal.description.favourite' defaultMessage='With an account on Mastodon, you can favorite this post to let the author know you appreciate it and save it for later.' />;
break; break;
case 'follow': case 'follow':
icon = <Icon id='user-plus' />; icon = <Icon id='user-plus' icon={PersonAddIcon} />;
title = <FormattedMessage id='interaction_modal.title.follow' defaultMessage='Follow {name}' values={{ name }} />; title = <FormattedMessage id='interaction_modal.title.follow' defaultMessage='Follow {name}' values={{ name }} />;
actionDescription = <FormattedMessage id='interaction_modal.description.follow' defaultMessage='With an account on Mastodon, you can follow {name} to receive their posts in your home feed.' values={{ name }} />; actionDescription = <FormattedMessage id='interaction_modal.description.follow' defaultMessage='With an account on Mastodon, you can follow {name} to receive their posts in your home feed.' values={{ name }} />;
break; break;

View file

@ -7,6 +7,8 @@ import { Helmet } from 'react-helmet';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as InfoIcon } from '@material-symbols/svg-600/outlined/info.svg';
import Column from 'flavours/glitch/components/column'; import Column from 'flavours/glitch/components/column';
import ColumnHeader from 'flavours/glitch/components/column_header'; import ColumnHeader from 'flavours/glitch/components/column_header';
@ -33,7 +35,8 @@ class KeyboardShortcuts extends ImmutablePureComponent {
<Column> <Column>
<ColumnHeader <ColumnHeader
title={intl.formatMessage(messages.heading)} title={intl.formatMessage(messages.heading)}
icon='question' icon='info-circle'
iconComponent={InfoIcon}
multiColumn={multiColumn} multiColumn={multiColumn}
/> />

View file

@ -6,6 +6,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as AddIcon } from '@material-symbols/svg-600/outlined/add.svg';
import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg';
import { ReactComponent as ListAltIcon } from '@material-symbols/svg-600/outlined/list_alt.svg';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { removeFromListAdder, addToListAdder } from '../../../actions/lists'; import { removeFromListAdder, addToListAdder } from '../../../actions/lists';
@ -46,16 +50,16 @@ class List extends ImmutablePureComponent {
let button; let button;
if (added) { if (added) {
button = <IconButton icon='times' title={intl.formatMessage(messages.remove)} onClick={onRemove} />; button = <IconButton icon='times' iconComponent={CloseIcon} title={intl.formatMessage(messages.remove)} onClick={onRemove} />;
} else { } else {
button = <IconButton icon='plus' title={intl.formatMessage(messages.add)} onClick={onAdd} />; button = <IconButton icon='plus' iconComponent={AddIcon} title={intl.formatMessage(messages.add)} onClick={onAdd} />;
} }
return ( return (
<div className='list'> <div className='list'>
<div className='list__wrapper'> <div className='list__wrapper'>
<div className='list__display-name'> <div className='list__display-name'>
<Icon id='list-ul' className='column-link__icon' fixedWidth /> <Icon id='list-ul' icon={ListAltIcon} className='column-link__icon' />
{list.get('title')} {list.get('title')}
</div> </div>

View file

@ -6,6 +6,9 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as AddIcon } from '@material-symbols/svg-600/outlined/add.svg';
import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg';
import { removeFromListEditor, addToListEditor } from '../../../actions/lists'; import { removeFromListEditor, addToListEditor } from '../../../actions/lists';
import { Avatar } from '../../../components/avatar'; import { Avatar } from '../../../components/avatar';
import { DisplayName } from '../../../components/display_name'; import { DisplayName } from '../../../components/display_name';
@ -53,9 +56,9 @@ class Account extends ImmutablePureComponent {
let button; let button;
if (added) { if (added) {
button = <IconButton icon='times' title={intl.formatMessage(messages.remove)} onClick={onRemove} />; button = <IconButton icon='times' iconComponent={CloseIcon} title={intl.formatMessage(messages.remove)} onClick={onRemove} />;
} else { } else {
button = <IconButton icon='plus' title={intl.formatMessage(messages.add)} onClick={onAdd} />; button = <IconButton icon='plus' iconComponent={AddIcon} title={intl.formatMessage(messages.add)} onClick={onAdd} />;
} }
return ( return (

View file

@ -5,6 +5,8 @@ import { defineMessages, injectIntl } from 'react-intl';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/check.svg';
import { changeListEditorTitle, submitListEditor } from '../../../actions/lists'; import { changeListEditorTitle, submitListEditor } from '../../../actions/lists';
import { IconButton } from '../../../components/icon_button'; import { IconButton } from '../../../components/icon_button';
@ -61,6 +63,7 @@ class ListForm extends PureComponent {
<IconButton <IconButton
disabled={disabled} disabled={disabled}
icon='check' icon='check'
iconComponent={CheckIcon}
title={title} title={title}
onClick={this.handleClick} onClick={this.handleClick}
/> />

View file

@ -7,6 +7,9 @@ import classNames from 'classnames';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as CancelIcon } from '@material-symbols/svg-600/outlined/cancel.svg';
import { ReactComponent as SearchIcon } from '@material-symbols/svg-600/outlined/search.svg';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { fetchListSuggestions, clearListSuggestions, changeListSuggestions } from '../../../actions/lists'; import { fetchListSuggestions, clearListSuggestions, changeListSuggestions } from '../../../actions/lists';
@ -69,8 +72,8 @@ class Search extends PureComponent {
</label> </label>
<div role='button' tabIndex={0} className='search__icon' onClick={this.handleClear}> <div role='button' tabIndex={0} className='search__icon' onClick={this.handleClear}>
<Icon id='search' className={classNames({ active: !hasValue })} /> <Icon id='search' icon={SearchIcon} className={classNames({ active: !hasValue })} />
<Icon id='times-circle' aria-label={intl.formatMessage(messages.search)} className={classNames({ active: hasValue })} /> <Icon id='times-circle' icon={CancelIcon} aria-label={intl.formatMessage(messages.search)} className={classNames({ active: hasValue })} />
</div> </div>
</div> </div>
); );

View file

@ -9,6 +9,9 @@ import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as DeleteIcon } from '@material-symbols/svg-600/outlined/delete.svg';
import { ReactComponent as EditIcon } from '@material-symbols/svg-600/outlined/edit.svg';
import { ReactComponent as ListAltIcon } from '@material-symbols/svg-600/outlined/list_alt.svg';
import Toggle from 'react-toggle'; import Toggle from 'react-toggle';
import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns'; import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns';
@ -181,6 +184,7 @@ class ListTimeline extends PureComponent {
<Column bindToDocument={!multiColumn} ref={this.setRef} label={title}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={title}>
<ColumnHeader <ColumnHeader
icon='list-ul' icon='list-ul'
iconComponent={ListAltIcon}
active={hasUnread} active={hasUnread}
title={title} title={title}
onPin={this.handlePin} onPin={this.handlePin}
@ -191,11 +195,11 @@ class ListTimeline extends PureComponent {
> >
<div className='column-settings__row column-header__links'> <div className='column-settings__row column-header__links'>
<button type='button' className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.handleEditClick}> <button type='button' className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.handleEditClick}>
<Icon id='pencil' /> <FormattedMessage id='lists.edit' defaultMessage='Edit list' /> <Icon id='pencil' icon={EditIcon} /> <FormattedMessage id='lists.edit' defaultMessage='Edit list' />
</button> </button>
<button type='button' className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.handleDeleteClick}> <button type='button' className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.handleDeleteClick}>
<Icon id='trash' /> <FormattedMessage id='lists.delete' defaultMessage='Delete list' /> <Icon id='trash' icon={DeleteIcon} /> <FormattedMessage id='lists.delete' defaultMessage='Delete list' />
</button> </button>
</div> </div>

View file

@ -6,7 +6,7 @@ import { defineMessages, injectIntl } from 'react-intl';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { changeListEditorTitle, submitListEditor } from 'flavours/glitch/actions/lists'; import { changeListEditorTitle, submitListEditor } from 'flavours/glitch/actions/lists';
import { IconButton } from 'flavours/glitch/components/icon_button'; import { Button } from 'flavours/glitch/components/button';
const messages = defineMessages({ const messages = defineMessages({
label: { id: 'lists.new.title_placeholder', defaultMessage: 'New list title' }, label: { id: 'lists.new.title_placeholder', defaultMessage: 'New list title' },
@ -66,9 +66,8 @@ class NewListForm extends PureComponent {
/> />
</label> </label>
<IconButton <Button
disabled={disabled || !value} disabled={disabled || !value}
icon='plus'
title={title} title={title}
onClick={this.handleClick} onClick={this.handleClick}
/> />

View file

@ -9,8 +9,9 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as ListAltIcon } from '@material-symbols/svg-600/outlined/list_alt.svg';
import { fetchLists } from 'flavours/glitch/actions/lists'; import { fetchLists } from 'flavours/glitch/actions/lists';
import ColumnBackButtonSlim from 'flavours/glitch/components/column_back_button_slim';
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator'; import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
import ScrollableList from 'flavours/glitch/components/scrollable_list'; import ScrollableList from 'flavours/glitch/components/scrollable_list';
import Column from 'flavours/glitch/features/ui/components/column'; import Column from 'flavours/glitch/features/ui/components/column';
@ -64,9 +65,7 @@ class Lists extends ImmutablePureComponent {
const emptyMessage = <FormattedMessage id='empty_column.lists' defaultMessage="You don't have any lists yet. When you create one, it will show up here." />; const emptyMessage = <FormattedMessage id='empty_column.lists' defaultMessage="You don't have any lists yet. When you create one, it will show up here." />;
return ( return (
<Column bindToDocument={!multiColumn} icon='bars' heading={intl.formatMessage(messages.heading)}> <Column bindToDocument={!multiColumn} icon='bars' iconComponent={ListAltIcon} heading={intl.formatMessage(messages.heading)} alwaysShowBackButton>
<ColumnBackButtonSlim />
<NewListForm /> <NewListForm />
<ColumnSubheading text={intl.formatMessage(messages.subheading)} /> <ColumnSubheading text={intl.formatMessage(messages.subheading)} />
@ -76,7 +75,7 @@ class Lists extends ImmutablePureComponent {
bindToDocument={!multiColumn} bindToDocument={!multiColumn}
> >
{lists.map(list => {lists.map(list =>
<ColumnLink key={list.get('id')} to={`/lists/${list.get('id')}`} icon='list-ul' text={list.get('title')} />, <ColumnLink key={list.get('id')} to={`/lists/${list.get('id')}`} icon='list-ul' iconComponent={ListAltIcon} text={list.get('title')} />,
)} )}
</ScrollableList> </ScrollableList>

View file

@ -4,13 +4,17 @@ import { PureComponent } from 'react';
import { injectIntl, defineMessages } from 'react-intl'; import { injectIntl, defineMessages } from 'react-intl';
// Our imports import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg';
import { ReactComponent as EditIcon } from '@material-symbols/svg-600/outlined/edit.svg';
import { ReactComponent as ExpandLessIcon } from '@material-symbols/svg-600/outlined/expand_less.svg';
import { ReactComponent as ImageIcon } from '@material-symbols/svg-600/outlined/image.svg';
import { ReactComponent as ManufacturingIcon } from '@material-symbols/svg-600/outlined/manufacturing.svg';
import { ReactComponent as SettingsIcon } from '@material-symbols/svg-600/outlined/settings-fill.svg';
import { preferencesLink } from 'flavours/glitch/utils/backend_links'; import { preferencesLink } from 'flavours/glitch/utils/backend_links';
import LocalSettingsNavigationItem from './item'; import LocalSettingsNavigationItem from './item';
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
const messages = defineMessages({ const messages = defineMessages({
general: { id: 'settings.general', defaultMessage: 'General' }, general: { id: 'settings.general', defaultMessage: 'General' },
compose: { id: 'settings.compose_box_opts', defaultMessage: 'Compose box' }, compose: { id: 'settings.compose_box_opts', defaultMessage: 'Compose box' },
@ -41,6 +45,7 @@ class LocalSettingsNavigation extends PureComponent {
index={0} index={0}
onNavigate={onNavigate} onNavigate={onNavigate}
icon='cogs' icon='cogs'
iconComponent={ManufacturingIcon}
title={intl.formatMessage(messages.general)} title={intl.formatMessage(messages.general)}
/> />
<LocalSettingsNavigationItem <LocalSettingsNavigationItem
@ -48,6 +53,7 @@ class LocalSettingsNavigation extends PureComponent {
index={1} index={1}
onNavigate={onNavigate} onNavigate={onNavigate}
icon='pencil' icon='pencil'
iconComponent={EditIcon}
title={intl.formatMessage(messages.compose)} title={intl.formatMessage(messages.compose)}
/> />
<LocalSettingsNavigationItem <LocalSettingsNavigationItem
@ -62,6 +68,7 @@ class LocalSettingsNavigation extends PureComponent {
index={3} index={3}
onNavigate={onNavigate} onNavigate={onNavigate}
icon='angle-double-up' icon='angle-double-up'
iconComponent={ExpandLessIcon}
title={intl.formatMessage(messages.collapsed)} title={intl.formatMessage(messages.collapsed)}
/> />
<LocalSettingsNavigationItem <LocalSettingsNavigationItem
@ -69,6 +76,7 @@ class LocalSettingsNavigation extends PureComponent {
index={4} index={4}
onNavigate={onNavigate} onNavigate={onNavigate}
icon='image' icon='image'
iconComponent={ImageIcon}
title={intl.formatMessage(messages.media)} title={intl.formatMessage(messages.media)}
/> />
<LocalSettingsNavigationItem <LocalSettingsNavigationItem
@ -76,6 +84,7 @@ class LocalSettingsNavigation extends PureComponent {
href={preferencesLink} href={preferencesLink}
index={5} index={5}
icon='cog' icon='cog'
iconComponent={SettingsIcon}
title={intl.formatMessage(messages.preferences)} title={intl.formatMessage(messages.preferences)}
/> />
<LocalSettingsNavigationItem <LocalSettingsNavigationItem
@ -84,6 +93,7 @@ class LocalSettingsNavigation extends PureComponent {
index={6} index={6}
onNavigate={onClose} onNavigate={onClose}
icon='times' icon='times'
iconComponent={CloseIcon}
title={intl.formatMessage(messages.close)} title={intl.formatMessage(messages.close)}
/> />
</nav> </nav>

View file

@ -6,8 +6,6 @@ import classNames from 'classnames';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
export default class LocalSettingsPage extends PureComponent { export default class LocalSettingsPage extends PureComponent {
static propTypes = { static propTypes = {
@ -16,6 +14,7 @@ export default class LocalSettingsPage extends PureComponent {
href: PropTypes.string, href: PropTypes.string,
icon: PropTypes.string, icon: PropTypes.string,
textIcon: PropTypes.string, textIcon: PropTypes.string,
iconComponent: PropTypes.func,
index: PropTypes.number.isRequired, index: PropTypes.number.isRequired,
onNavigate: PropTypes.func, onNavigate: PropTypes.func,
title: PropTypes.string, title: PropTypes.string,
@ -36,6 +35,7 @@ export default class LocalSettingsPage extends PureComponent {
className, className,
href, href,
icon, icon,
iconComponent,
textIcon, textIcon,
onNavigate, onNavigate,
title, title,
@ -45,7 +45,7 @@ export default class LocalSettingsPage extends PureComponent {
active, active,
}, className); }, className);
const iconElem = icon ? <Icon fixedWidth id={icon} /> : (textIcon ? <span className='text-icon-button'>{textIcon}</span> : null); const iconElem = icon ? <Icon id={icon} icon={iconComponent} /> : (textIcon ? <span className='text-icon-button'>{textIcon}</span> : null);
if (href) return ( if (href) return (
<a <a

View file

@ -8,10 +8,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as VolumeOffIcon } from '@material-symbols/svg-600/outlined/volume_off.svg';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { fetchMutes, expandMutes } from '../../actions/mutes'; import { fetchMutes, expandMutes } from '../../actions/mutes';
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import { LoadingIndicator } from '../../components/loading_indicator'; import { LoadingIndicator } from '../../components/loading_indicator';
import ScrollableList from '../../components/scrollable_list'; import ScrollableList from '../../components/scrollable_list';
import AccountContainer from '../../containers/account_container'; import AccountContainer from '../../containers/account_container';
@ -61,8 +61,7 @@ class Mutes extends ImmutablePureComponent {
const emptyMessage = <FormattedMessage id='empty_column.mutes' defaultMessage="You haven't muted any users yet." />; const emptyMessage = <FormattedMessage id='empty_column.mutes' defaultMessage="You haven't muted any users yet." />;
return ( return (
<Column bindToDocument={!multiColumn} icon='volume-off' heading={intl.formatMessage(messages.heading)}> <Column bindToDocument={!multiColumn} icon='volume-off' iconComponent={VolumeOffIcon} heading={intl.formatMessage(messages.heading)} alwaysShowBackButton>
<ColumnBackButtonSlim />
<ScrollableList <ScrollableList
scrollKey='mutes' scrollKey='mutes'
onLoadMore={this.handleLoadMore} onLoadMore={this.handleLoadMore}

View file

@ -8,6 +8,7 @@ import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { ReactComponent as FlagIcon } from '@material-symbols/svg-600/outlined/flag-fill.svg';
import { HotKeys } from 'react-hotkeys'; import { HotKeys } from 'react-hotkeys';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
@ -95,7 +96,7 @@ class AdminReport extends ImmutablePureComponent {
<div className={classNames('notification notification-admin-report focusable', { unread })} tabIndex={0}> <div className={classNames('notification notification-admin-report focusable', { unread })} tabIndex={0}>
<div className='notification__message'> <div className='notification__message'>
<div className='notification__favourite-icon-wrapper'> <div className='notification__favourite-icon-wrapper'>
<Icon id='flag' fixedWidth /> <Icon id='flag' icon={FlagIcon} />
</div> </div>
<span title={notification.get('created_at')}> <span title={notification.get('created_at')}>

View file

@ -8,6 +8,7 @@ import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { ReactComponent as PersonAddIcon } from '@material-symbols/svg-600/outlined/person_add-fill.svg';
import { HotKeys } from 'react-hotkeys'; import { HotKeys } from 'react-hotkeys';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
@ -86,7 +87,7 @@ class NotificationAdminSignup extends ImmutablePureComponent {
<div className={classNames('notification notification-admin-sign-up focusable', { unread })} tabIndex={0}> <div className={classNames('notification notification-admin-sign-up focusable', { unread })} tabIndex={0}>
<div className='notification__message'> <div className='notification__message'>
<div className='notification__favourite-icon-wrapper'> <div className='notification__favourite-icon-wrapper'>
<Icon fixedWidth id='user-plus' /> <Icon id='user-plus' icon={PersonAddIcon} />
</div> </div>
<FormattedMessage <FormattedMessage

View file

@ -3,6 +3,8 @@ import { PureComponent } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { ReactComponent as DeleteForeverIcon } from '@material-symbols/svg-600/outlined/delete_forever.svg';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
export default class ClearColumnButton extends PureComponent { export default class ClearColumnButton extends PureComponent {
@ -13,7 +15,7 @@ export default class ClearColumnButton extends PureComponent {
render () { render () {
return ( return (
<button className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.props.onClick}><Icon id='eraser' /> <FormattedMessage id='notifications.clear' defaultMessage='Clear notifications' /></button> <button className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.props.onClick}><Icon id='eraser' icon={DeleteForeverIcon} /> <FormattedMessage id='notifications.clear' defaultMessage='Clear notifications' /></button>
); );
} }

View file

@ -3,6 +3,14 @@ import { PureComponent } from 'react';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { ReactComponent as AddReactionIcon } from '@material-symbols/svg-600/outlined/add_reaction.svg';
import { ReactComponent as HomeIcon } from '@material-symbols/svg-600/outlined/home-fill.svg';
import { ReactComponent as InsertChartIcon } from '@material-symbols/svg-600/outlined/insert_chart.svg';
import { ReactComponent as PersonAddIcon } from '@material-symbols/svg-600/outlined/person_add.svg';
import { ReactComponent as RepeatIcon } from '@material-symbols/svg-600/outlined/repeat.svg';
import { ReactComponent as ReplyAllIcon } from '@material-symbols/svg-600/outlined/reply_all.svg';
import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star.svg';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
const tooltips = defineMessages({ const tooltips = defineMessages({
@ -67,49 +75,49 @@ class FilterBar extends PureComponent {
onClick={this.onClick('mention')} onClick={this.onClick('mention')}
title={intl.formatMessage(tooltips.mentions)} title={intl.formatMessage(tooltips.mentions)}
> >
<Icon id='reply-all' fixedWidth /> <Icon id='reply-all' icon={ReplyAllIcon} />
</button> </button>
<button <button
className={selectedFilter === 'favourite' ? 'active' : ''} className={selectedFilter === 'favourite' ? 'active' : ''}
onClick={this.onClick('favourite')} onClick={this.onClick('favourite')}
title={intl.formatMessage(tooltips.favourites)} title={intl.formatMessage(tooltips.favourites)}
> >
<Icon id='star' fixedWidth /> <Icon id='star' icon={StarIcon} />
</button> </button>
<button <button
className={selectedFilter === 'reaction' ? 'active' : ''} className={selectedFilter === 'reaction' ? 'active' : ''}
onClick={this.onClick('reaction')} onClick={this.onClick('reaction')}
title={intl.formatMessage(tooltips.reactions)} title={intl.formatMessage(tooltips.reactions)}
> >
<Icon id='plus' fixedWidth /> <Icon id='add_reaction' icon={AddReactionIcon} />
</button> </button>
<button <button
className={selectedFilter === 'reblog' ? 'active' : ''} className={selectedFilter === 'reblog' ? 'active' : ''}
onClick={this.onClick('reblog')} onClick={this.onClick('reblog')}
title={intl.formatMessage(tooltips.boosts)} title={intl.formatMessage(tooltips.boosts)}
> >
<Icon id='retweet' fixedWidth /> <Icon id='retweet' icon={RepeatIcon} />
</button> </button>
<button <button
className={selectedFilter === 'poll' ? 'active' : ''} className={selectedFilter === 'poll' ? 'active' : ''}
onClick={this.onClick('poll')} onClick={this.onClick('poll')}
title={intl.formatMessage(tooltips.polls)} title={intl.formatMessage(tooltips.polls)}
> >
<Icon id='tasks' fixedWidth /> <Icon id='tasks' icon={InsertChartIcon} />
</button> </button>
<button <button
className={selectedFilter === 'status' ? 'active' : ''} className={selectedFilter === 'status' ? 'active' : ''}
onClick={this.onClick('status')} onClick={this.onClick('status')}
title={intl.formatMessage(tooltips.statuses)} title={intl.formatMessage(tooltips.statuses)}
> >
<Icon id='home' fixedWidth /> <Icon id='home' icon={HomeIcon} />
</button> </button>
<button <button
className={selectedFilter === 'follow' ? 'active' : ''} className={selectedFilter === 'follow' ? 'active' : ''}
onClick={this.onClick('follow')} onClick={this.onClick('follow')}
title={intl.formatMessage(tooltips.follows)} title={intl.formatMessage(tooltips.follows)}
> >
<Icon id='user-plus' fixedWidth /> <Icon id='user-plus' icon={PersonAddIcon} />
</button> </button>
</div> </div>
); );

View file

@ -8,6 +8,7 @@ import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { ReactComponent as PersonAddIcon } from '@material-symbols/svg-600/outlined/person_add-fill.svg';
import { HotKeys } from 'react-hotkeys'; import { HotKeys } from 'react-hotkeys';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
@ -86,7 +87,7 @@ class NotificationFollow extends ImmutablePureComponent {
<div className={classNames('notification notification-follow focusable', { unread })} tabIndex={0}> <div className={classNames('notification notification-follow focusable', { unread })} tabIndex={0}>
<div className='notification__message'> <div className='notification__message'>
<div className='notification__favourite-icon-wrapper'> <div className='notification__favourite-icon-wrapper'>
<Icon fixedWidth id='user-plus' /> <Icon id='user-plus' icon={PersonAddIcon} />
</div> </div>
<FormattedMessage <FormattedMessage

View file

@ -8,6 +8,9 @@ import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/check.svg';
import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg';
import { ReactComponent as PersonIcon } from '@material-symbols/svg-600/outlined/person-fill.svg';
import { HotKeys } from 'react-hotkeys'; import { HotKeys } from 'react-hotkeys';
import { Avatar } from 'flavours/glitch/components/avatar'; import { Avatar } from 'flavours/glitch/components/avatar';
@ -106,7 +109,7 @@ class FollowRequest extends ImmutablePureComponent {
<div className={classNames('notification notification-follow-request focusable', { unread })} tabIndex={0}> <div className={classNames('notification notification-follow-request focusable', { unread })} tabIndex={0}>
<div className='notification__message'> <div className='notification__message'>
<div className='notification__favourite-icon-wrapper'> <div className='notification__favourite-icon-wrapper'>
<Icon id='user' fixedWidth /> <Icon id='user' icon={PersonIcon} />
</div> </div>
<FormattedMessage <FormattedMessage
@ -124,8 +127,8 @@ class FollowRequest extends ImmutablePureComponent {
</Permalink> </Permalink>
<div className='account__relationship'> <div className='account__relationship'>
<IconButton title={intl.formatMessage(messages.authorize)} icon='check' onClick={onAuthorize} /> <IconButton title={intl.formatMessage(messages.authorize)} icon='check' iconComponent={CheckIcon} onClick={onAuthorize} />
<IconButton title={intl.formatMessage(messages.reject)} icon='times' onClick={onReject} /> <IconButton title={intl.formatMessage(messages.reject)} icon='times' iconComponent={CloseIcon} onClick={onReject} />
</div> </div>
</div> </div>
</div> </div>

View file

@ -5,6 +5,9 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg';
import { ReactComponent as TuneIcon } from '@material-symbols/svg-600/outlined/tune.svg';
import { requestBrowserPermission } from 'flavours/glitch/actions/notifications'; import { requestBrowserPermission } from 'flavours/glitch/actions/notifications';
import { changeSetting } from 'flavours/glitch/actions/settings'; import { changeSetting } from 'flavours/glitch/actions/settings';
import { Button } from 'flavours/glitch/components/button'; import { Button } from 'flavours/glitch/components/button';
@ -36,11 +39,11 @@ class NotificationsPermissionBanner extends PureComponent {
return ( return (
<div className='notifications-permission-banner'> <div className='notifications-permission-banner'>
<div className='notifications-permission-banner__close'> <div className='notifications-permission-banner__close'>
<IconButton icon='times' onClick={this.handleClose} title={intl.formatMessage(messages.close)} /> <IconButton icon='times' iconComponent={CloseIcon} onClick={this.handleClose} title={intl.formatMessage(messages.close)} />
</div> </div>
<h2><FormattedMessage id='notifications_permission_banner.title' defaultMessage='Never miss a thing' /></h2> <h2><FormattedMessage id='notifications_permission_banner.title' defaultMessage='Never miss a thing' /></h2>
<p><FormattedMessage id='notifications_permission_banner.how_to_control' defaultMessage="To receive notifications when Mastodon isn't open, enable desktop notifications. You can control precisely which types of interactions generate desktop notifications through the {icon} button above once they're enabled." values={{ icon: <Icon id='sliders' /> }} /></p> <p><FormattedMessage id='notifications_permission_banner.how_to_control' defaultMessage="To receive notifications when Mastodon isn't open, enable desktop notifications. You can control precisely which types of interactions generate desktop notifications through the {icon} button above once they're enabled." values={{ icon: <Icon id='sliders' icon={TuneIcon} /> }} /></p>
<Button onClick={this.handleClick}><FormattedMessage id='notifications_permission_banner.enable' defaultMessage='Enable desktop notifications' /></Button> <Button onClick={this.handleClick}><FormattedMessage id='notifications_permission_banner.enable' defaultMessage='Enable desktop notifications' /></Button>
</div> </div>
); );

View file

@ -2,8 +2,6 @@
* Notification overlay * Notification overlay
*/ */
// Package imports.
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
@ -11,6 +9,8 @@ import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/check.svg';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
const messages = defineMessages({ const messages = defineMessages({
@ -49,7 +49,7 @@ class NotificationOverlay extends ImmutablePureComponent {
> >
<div className='wrappy'> <div className='wrappy'>
<div className='ckbox' aria-hidden='true' title={label}> <div className='ckbox' aria-hidden='true' title={label}>
{active ? (<Icon id='check' />) : ''} {active ? (<Icon id='check' icon={CheckIcon} />) : ''}
</div> </div>
</div> </div>
</div> </div>

View file

@ -11,6 +11,9 @@ import { List as ImmutableList } from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReactComponent as DeleteForeverIcon } from '@material-symbols/svg-600/outlined/delete_forever.svg';
import { ReactComponent as DoneAllIcon } from '@material-symbols/svg-600/outlined/done_all.svg';
import { ReactComponent as NotificationsIcon } from '@material-symbols/svg-600/outlined/notifications-fill.svg';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { compareId } from 'flavours/glitch/compare_id'; import { compareId } from 'flavours/glitch/compare_id';
@ -293,7 +296,7 @@ class Notifications extends PureComponent {
onClick={this.handleMarkAsRead} onClick={this.handleMarkAsRead}
className='column-header__button' className='column-header__button'
> >
<Icon id='check' /> <Icon id='done-all' icon={DoneAllIcon} />
</button>, </button>,
); );
} }
@ -317,7 +320,7 @@ class Notifications extends PureComponent {
onClick={this.onEnterCleaningMode} onClick={this.onEnterCleaningMode}
className={notifCleaningButtonClassName} className={notifCleaningButtonClassName}
> >
<Icon id='eraser' /> <Icon id='eraser' icon={DeleteForeverIcon} />
</button>, </button>,
); );
@ -338,6 +341,7 @@ class Notifications extends PureComponent {
> >
<ColumnHeader <ColumnHeader
icon='bell' icon='bell'
iconComponent={NotificationsIcon}
active={isUnread} active={isUnread}
title={intl.formatMessage(messages.title)} title={intl.formatMessage(messages.title)}
onPin={this.handlePin} onPin={this.handlePin}

View file

@ -1,7 +0,0 @@
const ArrowSmallRight = () => (
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor'>
<path fillRule='evenodd' d='M5 10a.75.75 0 01.75-.75h6.638L10.23 7.29a.75.75 0 111.04-1.08l3.5 3.25a.75.75 0 010 1.08l-3.5 3.25a.75.75 0 11-1.04-1.08l2.158-1.96H5.75A.75.75 0 015 10z' clipRule='evenodd' />
</svg>
);
export default ArrowSmallRight;

View file

@ -1,28 +0,0 @@
import PropTypes from 'prop-types';
import { Fragment } from 'react';
import classNames from 'classnames';
import { Check } from 'flavours/glitch/components/check';
const ProgressIndicator = ({ steps, completed }) => (
<div className='onboarding__progress-indicator'>
{(new Array(steps)).fill().map((_, i) => (
<Fragment key={i}>
{i > 0 && <div className={classNames('onboarding__progress-indicator__line', { active: completed > i })} />}
<div className={classNames('onboarding__progress-indicator__step', { active: completed > i })}>
{completed > i && <Check />}
</div>
</Fragment>
))}
</div>
);
ProgressIndicator.propTypes = {
steps: PropTypes.number.isRequired,
completed: PropTypes.number,
};
export default ProgressIndicator;

View file

@ -1,15 +1,17 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Check } from 'flavours/glitch/components/check'; import { Link } from 'react-router-dom';
import { ReactComponent as ArrowRightAltIcon } from '@material-symbols/svg-600/outlined/arrow_right_alt.svg';
import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/done.svg';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import ArrowSmallRight from './arrow_small_right'; export const Step = ({ label, description, icon, iconComponent, completed, onClick, href, to }) => {
const Step = ({ label, description, icon, completed, onClick, href }) => {
const content = ( const content = (
<> <>
<div className='onboarding__steps__item__icon'> <div className='onboarding__steps__item__icon'>
<Icon id={icon} /> <Icon id={icon} icon={iconComponent} />
</div> </div>
<div className='onboarding__steps__item__description'> <div className='onboarding__steps__item__description'>
@ -18,7 +20,7 @@ const Step = ({ label, description, icon, completed, onClick, href }) => {
</div> </div>
<div className={completed ? 'onboarding__steps__item__progress' : 'onboarding__steps__item__go'}> <div className={completed ? 'onboarding__steps__item__progress' : 'onboarding__steps__item__go'}>
{completed ? <Check /> : <ArrowSmallRight />} {completed ? <Icon icon={CheckIcon} /> : <Icon icon={ArrowRightAltIcon} />}
</div> </div>
</> </>
); );
@ -29,6 +31,12 @@ const Step = ({ label, description, icon, completed, onClick, href }) => {
{content} {content}
</a> </a>
); );
} else if (to) {
return (
<Link to={to} className='onboarding__steps__item'>
{content}
</Link>
);
} }
return ( return (
@ -42,9 +50,9 @@ Step.propTypes = {
label: PropTypes.node, label: PropTypes.node,
description: PropTypes.node, description: PropTypes.node,
icon: PropTypes.string, icon: PropTypes.string,
iconComponent: PropTypes.func,
completed: PropTypes.bool, completed: PropTypes.bool,
href: PropTypes.string, href: PropTypes.string,
to: PropTypes.string,
onClick: PropTypes.func, onClick: PropTypes.func,
}; };
export default Step;

View file

@ -1,45 +1,31 @@
import PropTypes from 'prop-types'; import { useEffect } from 'react';
import { PureComponent } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { useDispatch } from 'react-redux';
import { fetchSuggestions } from 'flavours/glitch/actions/suggestions'; import { fetchSuggestions } from 'flavours/glitch/actions/suggestions';
import { markAsPartial } from 'flavours/glitch/actions/timelines'; import { markAsPartial } from 'flavours/glitch/actions/timelines';
import Column from 'flavours/glitch/components/column'; import { ColumnBackButton } from 'flavours/glitch/components/column_back_button';
import ColumnBackButton from 'flavours/glitch/components/column_back_button';
import { EmptyAccount } from 'flavours/glitch/components/empty_account'; import { EmptyAccount } from 'flavours/glitch/components/empty_account';
import Account from 'flavours/glitch/containers/account_container'; import Account from 'flavours/glitch/containers/account_container';
import { useAppSelector } from 'flavours/glitch/store';
const mapStateToProps = state => ({ export const Follows = () => {
suggestions: state.getIn(['suggestions', 'items']), const dispatch = useDispatch();
isLoading: state.getIn(['suggestions', 'isLoading']), const isLoading = useAppSelector(state => state.getIn(['suggestions', 'isLoading']));
}); const suggestions = useAppSelector(state => state.getIn(['suggestions', 'items']));
class Follows extends PureComponent { useEffect(() => {
static propTypes = {
onBack: PropTypes.func,
dispatch: PropTypes.func.isRequired,
suggestions: ImmutablePropTypes.list,
isLoading: PropTypes.bool,
multiColumn: PropTypes.bool,
};
componentDidMount () {
const { dispatch } = this.props;
dispatch(fetchSuggestions(true)); dispatch(fetchSuggestions(true));
}
componentWillUnmount () { return () => {
const { dispatch } = this.props;
dispatch(markAsPartial('home')); dispatch(markAsPartial('home'));
} };
}, [dispatch]);
render () {
const { onBack, isLoading, suggestions, multiColumn } = this.props;
let loadedContent; let loadedContent;
@ -52,8 +38,8 @@ class Follows extends PureComponent {
} }
return ( return (
<Column> <>
<ColumnBackButton multiColumn={multiColumn} onClick={onBack} /> <ColumnBackButton />
<div className='scrollable privacy-policy'> <div className='scrollable privacy-policy'>
<div className='column-title'> <div className='column-title'>
@ -68,13 +54,9 @@ class Follows extends PureComponent {
<p className='onboarding__lead'><FormattedMessage id='onboarding.tips.accounts_from_other_servers' defaultMessage='<strong>Did you know?</strong> Since Mastodon is decentralized, some profiles you come across will be hosted on servers other than yours. And yet you can interact with them seamlessly! Their server is in the second half of their username!' values={{ strong: chunks => <strong>{chunks}</strong> }} /></p> <p className='onboarding__lead'><FormattedMessage id='onboarding.tips.accounts_from_other_servers' defaultMessage='<strong>Did you know?</strong> Since Mastodon is decentralized, some profiles you come across will be hosted on servers other than yours. And yet you can interact with them seamlessly! Their server is in the second half of their username!' values={{ strong: chunks => <strong>{chunks}</strong> }} /></p>
<div className='onboarding__footer'> <div className='onboarding__footer'>
<button className='link-button' onClick={onBack}><FormattedMessage id='onboarding.actions.back' defaultMessage='Take me back' /></button> <Link className='link-button' to='/start'><FormattedMessage id='onboarding.actions.back' defaultMessage='Take me back' /></Link>
</div> </div>
</div> </div>
</Column> </>
); );
} };
}
export default connect(mapStateToProps)(Follows);

View file

@ -1,113 +1,50 @@
import PropTypes from 'prop-types'; import { useCallback } from 'react';
import React from 'react';
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl'; import { FormattedMessage, useIntl, defineMessages } from 'react-intl';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import { Link, withRouter } from 'react-router-dom'; import { Link, Switch, Route, useHistory } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import { useDispatch } from 'react-redux';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
import { debounce } from 'lodash';
import { fetchAccount } from 'flavours/glitch/actions/accounts'; import { ReactComponent as AccountCircleIcon } from '@material-symbols/svg-600/outlined/account_circle.svg';
import { ReactComponent as ArrowRightAltIcon } from '@material-symbols/svg-600/outlined/arrow_right_alt.svg';
import { ReactComponent as ContentCopyIcon } from '@material-symbols/svg-600/outlined/content_copy.svg';
import { ReactComponent as EditNoteIcon } from '@material-symbols/svg-600/outlined/edit_note.svg';
import { ReactComponent as PersonAddIcon } from '@material-symbols/svg-600/outlined/person_add.svg';
import { focusCompose } from 'flavours/glitch/actions/compose'; import { focusCompose } from 'flavours/glitch/actions/compose';
import { closeOnboarding } from 'flavours/glitch/actions/onboarding'; import { Icon } from 'flavours/glitch/components/icon';
import Column from 'flavours/glitch/features/ui/components/column'; import Column from 'flavours/glitch/features/ui/components/column';
import { me } from 'flavours/glitch/initial_state'; import { me } from 'flavours/glitch/initial_state';
import { makeGetAccount } from 'flavours/glitch/selectors'; import { useAppSelector } from 'flavours/glitch/store';
import { assetHost } from 'flavours/glitch/utils/config'; import { assetHost } from 'flavours/glitch/utils/config';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
import illustration from 'mastodon/../images/elephant_ui_conversation.svg'; import illustration from 'mastodon/../images/elephant_ui_conversation.svg';
import ArrowSmallRight from './components/arrow_small_right'; import { Step } from './components/step';
import Step from './components/step'; import { Follows } from './follows';
import Follows from './follows'; import { Profile } from './profile';
import Share from './share'; import { Share } from './share';
const messages = defineMessages({ const messages = defineMessages({
template: { id: 'onboarding.compose.template', defaultMessage: 'Hello #Mastodon!' }, template: { id: 'onboarding.compose.template', defaultMessage: 'Hello #Mastodon!' },
}); });
const mapStateToProps = () => { const Onboarding = () => {
const getAccount = makeGetAccount(); const account = useAppSelector(state => state.getIn(['accounts', me]));
const dispatch = useDispatch();
return state => ({ const intl = useIntl();
account: getAccount(state, me), const history = useHistory();
});
};
class Onboarding extends ImmutablePureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
account: ImmutablePropTypes.record,
multiColumn: PropTypes.bool,
...WithRouterPropTypes,
};
state = {
step: null,
profileClicked: false,
shareClicked: false,
};
handleClose = () => {
const { dispatch, history } = this.props;
dispatch(closeOnboarding());
history.push('/home');
};
handleProfileClick = () => {
this.setState({ profileClicked: true });
};
handleFollowClick = () => {
this.setState({ step: 'follows' });
};
handleComposeClick = () => {
const { dispatch, intl, history } = this.props;
const handleComposeClick = useCallback(() => {
dispatch(focusCompose(history, intl.formatMessage(messages.template))); dispatch(focusCompose(history, intl.formatMessage(messages.template)));
}; }, [dispatch, intl, history]);
handleShareClick = () => {
this.setState({ step: 'share', shareClicked: true });
};
handleBackClick = () => {
this.setState({ step: null });
};
handleWindowFocus = debounce(() => {
const { dispatch, account } = this.props;
dispatch(fetchAccount(account.get('id')));
}, 1000, { trailing: true });
componentDidMount () {
window.addEventListener('focus', this.handleWindowFocus, false);
}
componentWillUnmount () {
window.removeEventListener('focus', this.handleWindowFocus);
}
render () {
const { account, multiColumn } = this.props;
const { step, shareClicked } = this.state;
switch(step) {
case 'follows':
return <Follows onBack={this.handleBackClick} multiColumn={multiColumn} />;
case 'share':
return <Share onBack={this.handleBackClick} multiColumn={multiColumn} />;
}
return ( return (
<Column> <Column>
<Switch>
<Route path='/start' exact>
<div className='scrollable privacy-policy'> <div className='scrollable privacy-policy'>
<div className='column-title'> <div className='column-title'>
<img src={illustration} alt='' className='onboarding__illustration' /> <img src={illustration} alt='' className='onboarding__illustration' />
@ -116,10 +53,10 @@ class Onboarding extends ImmutablePureComponent {
</div> </div>
<div className='onboarding__steps'> <div className='onboarding__steps'>
<Step onClick={this.handleProfileClick} href='/settings/profile' completed={(!account.get('avatar').endsWith('missing.png')) || (account.get('display_name').length > 0 && account.get('note').length > 0)} icon='address-book-o' label={<FormattedMessage id='onboarding.steps.setup_profile.title' defaultMessage='Customize your profile' />} description={<FormattedMessage id='onboarding.steps.setup_profile.body' defaultMessage='Others are more likely to interact with you with a filled out profile.' />} /> <Step to='/start/profile' completed={(!account.get('avatar').endsWith('missing.png')) || (account.get('display_name').length > 0 && account.get('note').length > 0)} icon='address-book-o' iconComponent={AccountCircleIcon} label={<FormattedMessage id='onboarding.steps.setup_profile.title' defaultMessage='Customize your profile' />} description={<FormattedMessage id='onboarding.steps.setup_profile.body' defaultMessage='Others are more likely to interact with you with a filled out profile.' />} />
<Step onClick={this.handleFollowClick} completed={(account.get('following_count') * 1) >= 7} icon='user-plus' label={<FormattedMessage id='onboarding.steps.follow_people.title' defaultMessage='Find at least {count, plural, one {one person} other {# people}} to follow' values={{ count: 7 }} />} description={<FormattedMessage id='onboarding.steps.follow_people.body' defaultMessage="You curate your own home feed. Let's fill it with interesting people." />} /> <Step to='/start/follows' completed={(account.get('following_count') * 1) >= 1} icon='user-plus' iconComponent={PersonAddIcon} label={<FormattedMessage id='onboarding.steps.follow_people.title' defaultMessage='Find at least {count, plural, one {one person} other {# people}} to follow' values={{ count: 7 }} />} description={<FormattedMessage id='onboarding.steps.follow_people.body' defaultMessage="You curate your own home feed. Let's fill it with interesting people." />} />
<Step onClick={this.handleComposeClick} completed={(account.get('statuses_count') * 1) >= 1} icon='pencil-square-o' label={<FormattedMessage id='onboarding.steps.publish_status.title' defaultMessage='Make your first post' />} description={<FormattedMessage id='onboarding.steps.publish_status.body' defaultMessage='Say hello to the world.' values={{ emoji: <img className='emojione' alt='🐘' src={`${assetHost}/emoji/1f418.svg`} /> }} />} /> <Step onClick={handleComposeClick} completed={(account.get('statuses_count') * 1) >= 1} icon='pencil-square-o' iconComponent={EditNoteIcon} label={<FormattedMessage id='onboarding.steps.publish_status.title' defaultMessage='Make your first post' />} description={<FormattedMessage id='onboarding.steps.publish_status.body' defaultMessage='Say hello to the world.' values={{ emoji: <img className='emojione' alt='🐘' src={`${assetHost}/emoji/1f418.svg`} /> }} />} />
<Step onClick={this.handleShareClick} completed={shareClicked} icon='copy' label={<FormattedMessage id='onboarding.steps.share_profile.title' defaultMessage='Share your profile' />} description={<FormattedMessage id='onboarding.steps.share_profile.body' defaultMessage='Let your friends know how to find you on Mastodon!' />} /> <Step to='/start/share' icon='copy' iconComponent={ContentCopyIcon} label={<FormattedMessage id='onboarding.steps.share_profile.title' defaultMessage='Share your profile' />} description={<FormattedMessage id='onboarding.steps.share_profile.body' defaultMessage='Let your friends know how to find you on Mastodon!' />} />
</div> </div>
<p className='onboarding__lead'><FormattedMessage id='onboarding.start.skip' defaultMessage="Don't need help getting started?" /></p> <p className='onboarding__lead'><FormattedMessage id='onboarding.start.skip' defaultMessage="Don't need help getting started?" /></p>
@ -127,23 +64,27 @@ class Onboarding extends ImmutablePureComponent {
<div className='onboarding__links'> <div className='onboarding__links'>
<Link to='/explore' className='onboarding__link'> <Link to='/explore' className='onboarding__link'>
<FormattedMessage id='onboarding.actions.go_to_explore' defaultMessage='Take me to trending' /> <FormattedMessage id='onboarding.actions.go_to_explore' defaultMessage='Take me to trending' />
<ArrowSmallRight /> <Icon icon={ArrowRightAltIcon} />
</Link> </Link>
<Link to='/home' className='onboarding__link'> <Link to='/home' className='onboarding__link'>
<FormattedMessage id='onboarding.actions.go_to_home' defaultMessage='Take me to my home feed' /> <FormattedMessage id='onboarding.actions.go_to_home' defaultMessage='Take me to my home feed' />
<ArrowSmallRight /> <Icon icon={ArrowRightAltIcon} />
</Link> </Link>
</div> </div>
</div> </div>
</Route>
<Route path='/start/profile' component={Profile} />
<Route path='/start/follows' component={Follows} />
<Route path='/start/share' component={Share} />
</Switch>
<Helmet> <Helmet>
<meta name='robots' content='noindex' /> <meta name='robots' content='noindex' />
</Helmet> </Helmet>
</Column> </Column>
); );
} };
} export default Onboarding;
export default withRouter(connect(mapStateToProps)(injectIntl(Onboarding)));

View file

@ -0,0 +1,162 @@
import { useState, useMemo, useCallback, createRef } from 'react';
import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import { useHistory } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import { ReactComponent as AddPhotoAlternateIcon } from '@material-symbols/svg-600/outlined/add_photo_alternate.svg';
import { ReactComponent as EditIcon } from '@material-symbols/svg-600/outlined/edit.svg';
import Toggle from 'react-toggle';
import { updateAccount } from 'flavours/glitch/actions/accounts';
import { Button } from 'flavours/glitch/components/button';
import { ColumnBackButton } from 'flavours/glitch/components/column_back_button';
import { Icon } from 'flavours/glitch/components/icon';
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
import { me } from 'flavours/glitch/initial_state';
import { useAppSelector } from 'flavours/glitch/store';
import { unescapeHTML } from 'flavours/glitch/utils/html';
const messages = defineMessages({
uploadHeader: { id: 'onboarding.profile.upload_header', defaultMessage: 'Upload profile header' },
uploadAvatar: { id: 'onboarding.profile.upload_avatar', defaultMessage: 'Upload profile picture' },
});
const nullIfMissing = path => path.endsWith('missing.png') ? null : path;
export const Profile = () => {
const account = useAppSelector(state => state.getIn(['accounts', me]));
const [displayName, setDisplayName] = useState(account.get('display_name'));
const [note, setNote] = useState(unescapeHTML(account.get('note')));
const [avatar, setAvatar] = useState(null);
const [header, setHeader] = useState(null);
const [discoverable, setDiscoverable] = useState(account.get('discoverable'));
const [isSaving, setIsSaving] = useState(false);
const [errors, setErrors] = useState();
const avatarFileRef = createRef();
const headerFileRef = createRef();
const dispatch = useDispatch();
const intl = useIntl();
const history = useHistory();
const handleDisplayNameChange = useCallback(e => {
setDisplayName(e.target.value);
}, [setDisplayName]);
const handleNoteChange = useCallback(e => {
setNote(e.target.value);
}, [setNote]);
const handleDiscoverableChange = useCallback(e => {
setDiscoverable(e.target.checked);
}, [setDiscoverable]);
const handleAvatarChange = useCallback(e => {
setAvatar(e.target?.files?.[0]);
}, [setAvatar]);
const handleHeaderChange = useCallback(e => {
setHeader(e.target?.files?.[0]);
}, [setHeader]);
const avatarPreview = useMemo(() => avatar ? URL.createObjectURL(avatar) : nullIfMissing(account.get('avatar')), [avatar, account]);
const headerPreview = useMemo(() => header ? URL.createObjectURL(header) : nullIfMissing(account.get('header')), [header, account]);
const handleSubmit = useCallback(() => {
setIsSaving(true);
dispatch(updateAccount({
displayName,
note,
avatar,
header,
discoverable,
indexable: discoverable,
})).then(() => history.push('/start/follows')).catch(err => {
setIsSaving(false);
setErrors(err.response.data.details);
});
}, [dispatch, displayName, note, avatar, header, discoverable, history]);
return (
<>
<ColumnBackButton />
<div className='scrollable privacy-policy'>
<div className='column-title'>
<h3><FormattedMessage id='onboarding.profile.title' defaultMessage='Profile setup' /></h3>
<p><FormattedMessage id='onboarding.profile.lead' defaultMessage='You can always complete this later in the settings, where even more customization options are available.' /></p>
</div>
<div className='simple_form'>
<div className='onboarding__profile'>
<label className={classNames('app-form__header-input', { selected: !!headerPreview, invalid: !!errors?.header })} title={intl.formatMessage(messages.uploadHeader)}>
<input
type='file'
hidden
ref={headerFileRef}
accept='image/*'
onChange={handleHeaderChange}
/>
{headerPreview && <img src={headerPreview} alt='' />}
<Icon icon={headerPreview ? EditIcon : AddPhotoAlternateIcon} />
</label>
<label className={classNames('app-form__avatar-input', { selected: !!avatarPreview, invalid: !!errors?.avatar })} title={intl.formatMessage(messages.uploadAvatar)}>
<input
type='file'
hidden
ref={avatarFileRef}
accept='image/*'
onChange={handleAvatarChange}
/>
{avatarPreview && <img src={avatarPreview} alt='' />}
<Icon icon={avatarPreview ? EditIcon : AddPhotoAlternateIcon} />
</label>
</div>
<div className={classNames('input with_block_label', { field_with_errors: !!errors?.display_name })}>
<label htmlFor='display_name'><FormattedMessage id='onboarding.profile.display_name' defaultMessage='Display name' /></label>
<span className='hint'><FormattedMessage id='onboarding.profile.display_name_hint' defaultMessage='Your full name or your fun name…' /></span>
<div className='label_input'>
<input id='display_name' type='text' value={displayName} onChange={handleDisplayNameChange} maxLength={30} />
</div>
</div>
<div className={classNames('input with_block_label', { field_with_errors: !!errors?.note })}>
<label htmlFor='note'><FormattedMessage id='onboarding.profile.note' defaultMessage='Bio' /></label>
<span className='hint'><FormattedMessage id='onboarding.profile.note_hint' defaultMessage='You can @mention other people or #hashtags…' /></span>
<div className='label_input'>
<textarea id='note' value={note} onChange={handleNoteChange} maxLength={500} />
</div>
</div>
<label className='app-form__toggle'>
<div className='app-form__toggle__label'>
<strong><FormattedMessage id='onboarding.profile.discoverable' defaultMessage='Make my profile discoverable' /></strong> <span className='recommended'><FormattedMessage id='recommended' defaultMessage='Recommended' /></span>
<span className='hint'><FormattedMessage id='onboarding.profile.discoverable_hint' defaultMessage='When you opt in to discoverability on Mastodon, your posts may appear in search results and trending, and your profile may be suggested to people with similar interests to you.' /></span>
</div>
<div className='app-form__toggle__toggle'>
<div>
<Toggle checked={discoverable} onChange={handleDiscoverableChange} />
</div>
</div>
</label>
</div>
<div className='onboarding__footer'>
<Button block onClick={handleSubmit} disabled={isSaving}>{isSaving ? <LoadingIndicator /> : <FormattedMessage id='onboarding.profile.save_and_continue' defaultMessage='Save and continue' />}</Button>
</div>
</div>
</>
);
};

Some files were not shown because too many files have changed in this diff Show more