diff --git a/app/soapbox/components/dropdown_menu.tsx b/app/soapbox/components/dropdown_menu.tsx index 01a60948d..e28ea0f9e 100644 --- a/app/soapbox/components/dropdown_menu.tsx +++ b/app/soapbox/components/dropdown_menu.tsx @@ -18,7 +18,7 @@ let id = 0; export interface MenuItem { action?: React.EventHandler, middleClick?: React.EventHandler, - text: string, + text: string | JSX.Element, href?: string, to?: string, newTab?: boolean, diff --git a/app/soapbox/components/sidebar-navigation-link.tsx b/app/soapbox/components/sidebar-navigation-link.tsx index bd0567c03..fc63372e7 100644 --- a/app/soapbox/components/sidebar-navigation-link.tsx +++ b/app/soapbox/components/sidebar-navigation-link.tsx @@ -8,17 +8,29 @@ interface ISidebarNavigationLink { count?: number, icon: string, text: string | React.ReactElement, - to: string, + to?: string, + onClick?: React.EventHandler, } -const SidebarNavigationLink = ({ icon, text, to, count }: ISidebarNavigationLink) => { +const SidebarNavigationLink = React.forwardRef((props: ISidebarNavigationLink, ref: React.ForwardedRef): JSX.Element => { + const { icon, text, to = '', count, onClick } = props; const isActive = location.pathname === to; const withCounter = typeof count !== 'undefined'; + const handleClick: React.EventHandler = (e) => { + if (onClick) { + onClick(e); + e.preventDefault(); + e.stopPropagation(); + } + }; + return ( {text} ); -}; +}); export default SidebarNavigationLink; diff --git a/app/soapbox/components/sidebar-navigation.tsx b/app/soapbox/components/sidebar-navigation.tsx index 96411d6c3..3f33e6733 100644 --- a/app/soapbox/components/sidebar-navigation.tsx +++ b/app/soapbox/components/sidebar-navigation.tsx @@ -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: , + 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: , + icon: require('@tabler/icons/icons/bookmark.svg'), + }); + } + + if (features.lists) { + menu.push({ + to: '/lists', + text: , + 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: , + }); + } + + if (settings.get('isDeveloper')) { + menu.push({ + to: '/developers', + icon: require('@tabler/icons/icons/code.svg'), + text: , + }); + } + + if (account && account.staff) { + menu.push({ + to: '/admin', + icon: require('@tabler/icons/icons/dashboard.svg'), + text: , + // 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 : , + }); + } + + if (features.localTimeline && features.federating) { + menu.push({ + to: '/timeline/fediverse', + icon: require('icons/fediverse.svg'), + text: , + }); + } + + return menu; + }; + + const menu = makeMenu(); + return (
@@ -71,49 +154,13 @@ const SidebarNavigation = () => { ) )} - {(account && account.staff) && ( - } - count={dashboardCount} - /> - )} - - {(account && instance.invites_enabled) && ( - } - /> - )} - - {(settings.get('isDeveloper')) && ( - } - /> - )} - - {(features.localTimeline || features.publicTimeline) && ( -
- )} - - {features.localTimeline && ( - } - /> - )} - - {(features.publicTimeline && features.federating) && ( - } - /> + {menu.length > 0 && ( + + } + /> + )}
diff --git a/app/soapbox/features/ui/components/features_panel.js b/app/soapbox/features/ui/components/features_panel.js deleted file mode 100644 index f54ba36b5..000000000 --- a/app/soapbox/features/ui/components/features_panel.js +++ /dev/null @@ -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 ( -
-
- {(isLocked || followRequestsCount > 0) && - - {intl.formatMessage(messages.follow_requests)} - } - - {features.bookmarks && ( - - - {intl.formatMessage(messages.bookmarks)} - - )} - - {features.lists && ( - - - {intl.formatMessage(messages.lists)} - - )} -
-
- ); - } - -} diff --git a/app/soapbox/features/ui/util/async-components.js b/app/soapbox/features/ui/util/async-components.js index 69d3d401d..15ed6cadf 100644 --- a/app/soapbox/features/ui/util/async-components.js +++ b/app/soapbox/features/ui/util/async-components.js @@ -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'); } diff --git a/app/soapbox/pages/remote_instance_page.js b/app/soapbox/pages/remote_instance_page.js index c922be6a5..2ff5e7c4f 100644 --- a/app/soapbox/pages/remote_instance_page.js +++ b/app/soapbox/pages/remote_instance_page.js @@ -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 ( @@ -43,11 +42,6 @@ class RemoteInstancePage extends ImmutablePureComponent { - {me && ( - - {Component => } - - )} {Component => }