Merge branch 'next-more' into 'next'
Next: Nuke FeaturesPanel, add "More" menu to left sidebar See merge request soapbox-pub/soapbox-fe!1218
This commit is contained in:
commit
8083b4b6e3
|
@ -18,7 +18,7 @@ let id = 0;
|
|||
export interface MenuItem {
|
||||
action?: React.EventHandler<React.KeyboardEvent | React.MouseEvent>,
|
||||
middleClick?: React.EventHandler<React.MouseEvent>,
|
||||
text: string,
|
||||
text: string | JSX.Element,
|
||||
href?: string,
|
||||
to?: string,
|
||||
newTab?: boolean,
|
||||
|
|
|
@ -8,17 +8,29 @@ interface ISidebarNavigationLink {
|
|||
count?: number,
|
||||
icon: string,
|
||||
text: string | React.ReactElement,
|
||||
to: string,
|
||||
to?: string,
|
||||
onClick?: React.EventHandler<React.MouseEvent>,
|
||||
}
|
||||
|
||||
const SidebarNavigationLink = ({ icon, text, to, count }: ISidebarNavigationLink) => {
|
||||
const SidebarNavigationLink = React.forwardRef((props: ISidebarNavigationLink, ref: React.ForwardedRef<HTMLAnchorElement>): JSX.Element => {
|
||||
const { icon, text, to = '', count, onClick } = props;
|
||||
const isActive = location.pathname === to;
|
||||
const withCounter = typeof count !== 'undefined';
|
||||
|
||||
const handleClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
if (onClick) {
|
||||
onClick(e);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<NavLink
|
||||
exact
|
||||
to={to}
|
||||
ref={ref}
|
||||
onClick={handleClick}
|
||||
className={classNames({
|
||||
'flex items-center py-2 text-sm font-semibold space-x-4': true,
|
||||
'text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200': !isActive,
|
||||
|
@ -50,6 +62,6 @@ const SidebarNavigationLink = ({ icon, text, to, count }: ISidebarNavigationLink
|
|||
<Text weight='semibold' theme='inherit'>{text}</Text>
|
||||
</NavLink>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default SidebarNavigationLink;
|
||||
|
|
|
@ -1,27 +1,110 @@
|
|||
import { Map as ImmutableMap } from 'immutable';
|
||||
import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet } from 'immutable';
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { getSettings } from 'soapbox/actions/settings';
|
||||
import DropdownMenu from 'soapbox/containers/dropdown_menu_container';
|
||||
import ComposeButton from 'soapbox/features/ui/components/compose-button';
|
||||
import { useAppSelector } from 'soapbox/hooks';
|
||||
import { useAppSelector, useOwnAccount } from 'soapbox/hooks';
|
||||
import { getBaseURL } from 'soapbox/utils/accounts';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
|
||||
import SidebarNavigationLink from './sidebar-navigation-link';
|
||||
|
||||
import type { Menu } from 'soapbox/components/dropdown_menu';
|
||||
|
||||
const SidebarNavigation = () => {
|
||||
const me = useAppSelector((state) => state.me);
|
||||
const instance = useAppSelector((state) => state.instance);
|
||||
const settings = useAppSelector((state) => getSettings(state));
|
||||
const account = useAppSelector((state) => state.accounts.get(me));
|
||||
const account = useOwnAccount();
|
||||
const notificationCount = useAppSelector((state) => state.notifications.get('unread'));
|
||||
const chatsCount = useAppSelector((state) => state.chats.get('items').reduce((acc: any, curr: any) => acc + Math.min(curr.get('unread', 0), 1), 0));
|
||||
const dashboardCount = useAppSelector((state) => state.admin.openReports.count() + state.admin.awaitingApproval.count());
|
||||
const followRequestsCount = useAppSelector((state) => state.user_lists.getIn(['follow_requests', 'items'], ImmutableOrderedSet()).count());
|
||||
// const dashboardCount = useAppSelector((state) => state.admin.openReports.count() + state.admin.awaitingApproval.count());
|
||||
|
||||
const baseURL = getBaseURL(ImmutableMap(account));
|
||||
const baseURL = account ? getBaseURL(ImmutableMap(account)) : '';
|
||||
const features = getFeatures(instance);
|
||||
|
||||
const makeMenu = (): Menu => {
|
||||
const menu: Menu = [];
|
||||
|
||||
if (account?.locked || followRequestsCount > 0) {
|
||||
menu.push({
|
||||
to: '/follow_requests',
|
||||
text: <FormattedMessage id='navigation_bar.follow_requests' defaultMessage='Follow requests' />,
|
||||
icon: require('@tabler/icons/icons/user-plus.svg'),
|
||||
// TODO: let menu items have a counter
|
||||
// count: followRequestsCount,
|
||||
});
|
||||
}
|
||||
|
||||
if (features.bookmarks) {
|
||||
menu.push({
|
||||
to: '/bookmarks',
|
||||
text: <FormattedMessage id='column.bookmarks' defaultMessage='Bookmarks' />,
|
||||
icon: require('@tabler/icons/icons/bookmark.svg'),
|
||||
});
|
||||
}
|
||||
|
||||
if (features.lists) {
|
||||
menu.push({
|
||||
to: '/lists',
|
||||
text: <FormattedMessage id='column.lists' defaultMessage='Lists' />,
|
||||
icon: require('@tabler/icons/icons/list.svg'),
|
||||
});
|
||||
}
|
||||
|
||||
if (account && instance.invites_enabled) {
|
||||
menu.push({
|
||||
to: `${baseURL}/invites`,
|
||||
icon: require('@tabler/icons/icons/mailbox.svg'),
|
||||
text: <FormattedMessage id='navigation.invites' defaultMessage='Invites' />,
|
||||
});
|
||||
}
|
||||
|
||||
if (settings.get('isDeveloper')) {
|
||||
menu.push({
|
||||
to: '/developers',
|
||||
icon: require('@tabler/icons/icons/code.svg'),
|
||||
text: <FormattedMessage id='navigation.developers' defaultMessage='Developers' />,
|
||||
});
|
||||
}
|
||||
|
||||
if (account && account.staff) {
|
||||
menu.push({
|
||||
to: '/admin',
|
||||
icon: require('@tabler/icons/icons/dashboard.svg'),
|
||||
text: <FormattedMessage id='tabs_bar.dashboard' defaultMessage='Dashboard' />,
|
||||
// TODO: let menu items have a counter
|
||||
// count: dashboardCount,
|
||||
});
|
||||
}
|
||||
|
||||
if (features.localTimeline || features.publicTimeline) {
|
||||
menu.push(null);
|
||||
}
|
||||
|
||||
if (features.localTimeline) {
|
||||
menu.push({
|
||||
to: '/timeline/local',
|
||||
icon: features.federating ? require('@tabler/icons/icons/users.svg') : require('@tabler/icons/icons/world.svg'),
|
||||
text: features.federating ? instance.title : <FormattedMessage id='tabs_bar.all' defaultMessage='All' />,
|
||||
});
|
||||
}
|
||||
|
||||
if (features.localTimeline && features.federating) {
|
||||
menu.push({
|
||||
to: '/timeline/fediverse',
|
||||
icon: require('icons/fediverse.svg'),
|
||||
text: <FormattedMessage id='tabs_bar.fediverse' defaultMessage='Fediverse' />,
|
||||
});
|
||||
}
|
||||
|
||||
return menu;
|
||||
};
|
||||
|
||||
const menu = makeMenu();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='flex flex-col space-y-2'>
|
||||
|
@ -71,49 +154,13 @@ const SidebarNavigation = () => {
|
|||
)
|
||||
)}
|
||||
|
||||
{(account && account.staff) && (
|
||||
<SidebarNavigationLink
|
||||
to='/admin'
|
||||
icon={require('@tabler/icons/icons/dashboard.svg')}
|
||||
text={<FormattedMessage id='tabs_bar.dashboard' defaultMessage='Dashboard' />}
|
||||
count={dashboardCount}
|
||||
/>
|
||||
)}
|
||||
|
||||
{(account && instance.invites_enabled) && (
|
||||
<SidebarNavigationLink
|
||||
to={`${baseURL}/invites`}
|
||||
icon={require('@tabler/icons/icons/mailbox.svg')}
|
||||
text={<FormattedMessage id='navigation.invites' defaultMessage='Invites' />}
|
||||
/>
|
||||
)}
|
||||
|
||||
{(settings.get('isDeveloper')) && (
|
||||
<SidebarNavigationLink
|
||||
to='/developers'
|
||||
icon={require('@tabler/icons/icons/code.svg')}
|
||||
text={<FormattedMessage id='navigation.developers' defaultMessage='Developers' />}
|
||||
/>
|
||||
)}
|
||||
|
||||
{(features.localTimeline || features.publicTimeline) && (
|
||||
<hr className='dark:border-slate-700' />
|
||||
)}
|
||||
|
||||
{features.localTimeline && (
|
||||
<SidebarNavigationLink
|
||||
to='/timeline/local'
|
||||
icon={features.federating ? require('@tabler/icons/icons/users.svg') : require('@tabler/icons/icons/world.svg')}
|
||||
text={features.federating ? instance.title : <FormattedMessage id='tabs_bar.all' defaultMessage='All' />}
|
||||
/>
|
||||
)}
|
||||
|
||||
{(features.publicTimeline && features.federating) && (
|
||||
<SidebarNavigationLink
|
||||
to='/timeline/fediverse'
|
||||
icon={require('icons/fediverse.svg')}
|
||||
text={<FormattedMessage id='tabs_bar.fediverse' defaultMessage='Fediverse' />}
|
||||
/>
|
||||
{menu.length > 0 && (
|
||||
<DropdownMenu items={menu}>
|
||||
<SidebarNavigationLink
|
||||
icon={require('@tabler/icons/icons/dots-circle-horizontal.svg')}
|
||||
text={<FormattedMessage id='tabs_bar.more' defaultMessage='More' />}
|
||||
/>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
import { OrderedSet as ImmutableOrderedSet } from 'immutable';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { injectIntl, defineMessages } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
|
||||
import Icon from 'soapbox/components/icon';
|
||||
import IconWithCounter from 'soapbox/components/icon_with_counter';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
|
||||
const messages = defineMessages({
|
||||
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit Profile' },
|
||||
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
|
||||
security: { id: 'navigation_bar.security', defaultMessage: 'Security' },
|
||||
lists: { id: 'column.lists', defaultMessage: 'Lists' },
|
||||
bookmarks: { id: 'column.bookmarks', defaultMessage: 'Bookmarks' },
|
||||
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
|
||||
});
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const me = state.get('me');
|
||||
|
||||
const instance = state.get('instance');
|
||||
const features = getFeatures(instance);
|
||||
|
||||
return {
|
||||
isLocked: state.getIn(['accounts', me, 'locked']),
|
||||
followRequestsCount: state.getIn(['user_lists', 'follow_requests', 'items'], ImmutableOrderedSet()).count(),
|
||||
features,
|
||||
};
|
||||
};
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@injectIntl
|
||||
class FeaturesPanel extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
isLocked: PropTypes.bool,
|
||||
followRequestsCount: PropTypes.number,
|
||||
features: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { intl, isLocked, followRequestsCount, features } = this.props;
|
||||
|
||||
return (
|
||||
<div className='wtf-panel promo-panel panel'>
|
||||
<div className='promo-panel__container'>
|
||||
{(isLocked || followRequestsCount > 0) && <NavLink className='promo-panel-item' to='/follow_requests'>
|
||||
<IconWithCounter src={require('@tabler/icons/icons/user-plus.svg')} count={followRequestsCount} className='promo-panel-item__icon' />
|
||||
{intl.formatMessage(messages.follow_requests)}
|
||||
</NavLink>}
|
||||
|
||||
{features.bookmarks && (
|
||||
<NavLink className='promo-panel-item' to='/bookmarks'>
|
||||
<Icon src={require('@tabler/icons/icons/bookmark.svg')} className='promo-panel-item__icon' />
|
||||
{intl.formatMessage(messages.bookmarks)}
|
||||
</NavLink>
|
||||
)}
|
||||
|
||||
{features.lists && (
|
||||
<NavLink className='promo-panel-item' to='/lists'>
|
||||
<Icon src={require('@tabler/icons/icons/list.svg')} className='promo-panel-item__icon' />
|
||||
{intl.formatMessage(messages.lists)}
|
||||
</NavLink>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -338,10 +338,6 @@ export function UserPanel() {
|
|||
return import(/* webpackChunkName: "features/ui" */'../components/user_panel');
|
||||
}
|
||||
|
||||
export function FeaturesPanel() {
|
||||
return import(/* webpackChunkName: "features/ui" */'../components/features_panel');
|
||||
}
|
||||
|
||||
export function PromoPanel() {
|
||||
return import(/* webpackChunkName: "features/ui" */'../components/promo_panel');
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import LinkFooter from 'soapbox/features/ui/components/link_footer';
|
|||
import BundleContainer from 'soapbox/features/ui/containers/bundle_container';
|
||||
import {
|
||||
PromoPanel,
|
||||
FeaturesPanel,
|
||||
InstanceInfoPanel,
|
||||
InstanceModerationPanel,
|
||||
} from 'soapbox/features/ui/util/async-components';
|
||||
|
@ -30,7 +29,7 @@ export default @connect(mapStateToProps)
|
|||
class RemoteInstancePage extends ImmutablePureComponent {
|
||||
|
||||
render() {
|
||||
const { me, children, params: { instance: host }, disclosed, isAdmin } = this.props;
|
||||
const { children, params: { instance: host }, disclosed, isAdmin } = this.props;
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
|
@ -43,11 +42,6 @@ class RemoteInstancePage extends ImmutablePureComponent {
|
|||
</Layout.Main>
|
||||
|
||||
<Layout.Aside>
|
||||
{me && (
|
||||
<BundleContainer fetchComponent={FeaturesPanel}>
|
||||
{Component => <Component key='features-panel' />}
|
||||
</BundleContainer>
|
||||
)}
|
||||
<BundleContainer fetchComponent={PromoPanel}>
|
||||
{Component => <Component key='promo-panel' />}
|
||||
</BundleContainer>
|
||||
|
|
Loading…
Reference in New Issue