Fix hotkey navigation in media modal
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
b1a775c2a9
commit
b0bdb78543
6 changed files with 265 additions and 230 deletions
|
@ -178,8 +178,15 @@ const StatusList: React.FC<IStatusList> = ({
|
|||
));
|
||||
};
|
||||
|
||||
const renderFeedSuggestions = (): React.ReactNode => {
|
||||
return <FeedSuggestions key='suggestions' />;
|
||||
const renderFeedSuggestions = (statusId: string): React.ReactNode => {
|
||||
return (
|
||||
<FeedSuggestions
|
||||
key='suggestions'
|
||||
statusId={statusId}
|
||||
onMoveUp={handleMoveUp}
|
||||
onMoveDown={handleMoveDown}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const renderStatuses = (): React.ReactNode[] => {
|
||||
|
@ -201,7 +208,7 @@ const StatusList: React.FC<IStatusList> = ({
|
|||
}
|
||||
} else if (statusId.startsWith('末suggestions-')) {
|
||||
if (soapboxConfig.feedInjection) {
|
||||
acc.push(renderFeedSuggestions());
|
||||
acc.push(renderFeedSuggestions(statusId));
|
||||
}
|
||||
} else if (statusId.startsWith('末pending-')) {
|
||||
acc.push(renderPendingStatus(statusId));
|
||||
|
|
|
@ -27,6 +27,7 @@ interface ICard {
|
|||
className?: string
|
||||
/** Elements inside the card. */
|
||||
children: React.ReactNode
|
||||
tabIndex?: number
|
||||
}
|
||||
|
||||
/** An opaque backdrop to hold a collection of related elements. */
|
||||
|
|
|
@ -2,18 +2,21 @@
|
|||
|
||||
import { QueryClientProvider } from '@tanstack/react-query';
|
||||
import clsx from 'clsx';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { Toaster } from 'react-hot-toast';
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { Provider } from 'react-redux';
|
||||
import { BrowserRouter, Switch, Redirect, Route } from 'react-router-dom';
|
||||
import { BrowserRouter, Switch, Redirect, Route, useHistory } from 'react-router-dom';
|
||||
import { CompatRouter } from 'react-router-dom-v5-compat';
|
||||
// @ts-ignore: it doesn't have types
|
||||
import { ScrollContext } from 'react-router-scroll-4';
|
||||
|
||||
|
||||
import { resetCompose } from 'soapbox/actions/compose';
|
||||
import { loadInstance } from 'soapbox/actions/instance';
|
||||
import { fetchMe } from 'soapbox/actions/me';
|
||||
import { openModal } from 'soapbox/actions/modals';
|
||||
import { loadSoapboxConfig, getSoapboxConfig } from 'soapbox/actions/soapbox';
|
||||
import { fetchVerificationConfig } from 'soapbox/actions/verification';
|
||||
import * as BuildConfig from 'soapbox/build-config';
|
||||
|
@ -64,6 +67,34 @@ store.dispatch(preload() as any);
|
|||
// This happens synchronously
|
||||
store.dispatch(checkOnboardingStatus() as any);
|
||||
|
||||
const keyMap = {
|
||||
help: '?',
|
||||
new: 'n',
|
||||
search: ['s', '/'],
|
||||
forceNew: 'option+n',
|
||||
reply: 'r',
|
||||
favourite: 'f',
|
||||
react: 'e',
|
||||
boost: 'b',
|
||||
mention: 'm',
|
||||
open: ['enter', 'o'],
|
||||
openProfile: 'p',
|
||||
moveDown: ['down', 'j'],
|
||||
moveUp: ['up', 'k'],
|
||||
back: 'backspace',
|
||||
goToHome: 'g h',
|
||||
goToNotifications: 'g n',
|
||||
goToFavourites: 'g f',
|
||||
goToPinned: 'g p',
|
||||
goToProfile: 'g u',
|
||||
goToBlocked: 'g b',
|
||||
goToMuted: 'g m',
|
||||
goToRequests: 'g r',
|
||||
toggleHidden: 'x',
|
||||
toggleSensitive: 'h',
|
||||
openMedia: 'a',
|
||||
};
|
||||
|
||||
/** Load initial data from the backend */
|
||||
const loadInitial = () => {
|
||||
// @ts-ignore
|
||||
|
@ -89,6 +120,10 @@ const loadInitial = () => {
|
|||
const SoapboxMount = () => {
|
||||
useCachedLocationHandler();
|
||||
|
||||
const hotkeys = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const history = useHistory();
|
||||
const dispatch = useAppDispatch();
|
||||
const me = useAppSelector(state => state.me);
|
||||
const instance = useInstance();
|
||||
const { account } = useOwnAccount();
|
||||
|
@ -106,6 +141,109 @@ const SoapboxMount = () => {
|
|||
return !(location.state?.soapboxModalKey && location.state?.soapboxModalKey !== prevRouterProps?.location?.state?.soapboxModalKey);
|
||||
};
|
||||
|
||||
const handleHotkeyNew = (e?: KeyboardEvent) => {
|
||||
e?.preventDefault();
|
||||
if (!hotkeys.current) return;
|
||||
|
||||
const element = hotkeys.current.querySelector('textarea#compose-textarea') as HTMLTextAreaElement;
|
||||
|
||||
if (element) {
|
||||
element.focus();
|
||||
}
|
||||
};
|
||||
|
||||
const handleHotkeySearch = (e?: KeyboardEvent) => {
|
||||
e?.preventDefault();
|
||||
if (!hotkeys.current) return;
|
||||
|
||||
const element = hotkeys.current.querySelector('input#search') as HTMLInputElement;
|
||||
|
||||
if (element) {
|
||||
element.focus();
|
||||
}
|
||||
};
|
||||
|
||||
const handleHotkeyForceNew = (e?: KeyboardEvent) => {
|
||||
handleHotkeyNew(e);
|
||||
dispatch(resetCompose());
|
||||
};
|
||||
|
||||
const handleHotkeyBack = () => {
|
||||
if (window.history && window.history.length === 1) {
|
||||
history.push('/');
|
||||
} else {
|
||||
history.goBack();
|
||||
}
|
||||
};
|
||||
|
||||
const setHotkeysRef: React.LegacyRef<HotKeys> = (c: any) => {
|
||||
hotkeys.current = c;
|
||||
|
||||
if (!me || !hotkeys.current) return;
|
||||
|
||||
// @ts-ignore
|
||||
hotkeys.current.__mousetrap__.stopCallback = (_e, element) => {
|
||||
return ['TEXTAREA', 'SELECT', 'INPUT', 'EM-EMOJI-PICKER'].includes(element.tagName);
|
||||
};
|
||||
};
|
||||
|
||||
const handleHotkeyToggleHelp = () => {
|
||||
dispatch(openModal('HOTKEYS'));
|
||||
};
|
||||
|
||||
const handleHotkeyGoToHome = () => {
|
||||
history.push('/');
|
||||
};
|
||||
|
||||
const handleHotkeyGoToNotifications = () => {
|
||||
history.push('/notifications');
|
||||
};
|
||||
|
||||
const handleHotkeyGoToFavourites = () => {
|
||||
if (!account) return;
|
||||
history.push(`/@${account.username}/favorites`);
|
||||
};
|
||||
|
||||
const handleHotkeyGoToPinned = () => {
|
||||
if (!account) return;
|
||||
history.push(`/@${account.username}/pins`);
|
||||
};
|
||||
|
||||
const handleHotkeyGoToProfile = () => {
|
||||
if (!account) return;
|
||||
history.push(`/@${account.username}`);
|
||||
};
|
||||
|
||||
const handleHotkeyGoToBlocked = () => {
|
||||
history.push('/blocks');
|
||||
};
|
||||
|
||||
const handleHotkeyGoToMuted = () => {
|
||||
history.push('/mutes');
|
||||
};
|
||||
|
||||
const handleHotkeyGoToRequests = () => {
|
||||
history.push('/follow_requests');
|
||||
};
|
||||
|
||||
type HotkeyHandlers = { [key: string]: (keyEvent?: KeyboardEvent) => void };
|
||||
|
||||
const handlers: HotkeyHandlers = {
|
||||
help: handleHotkeyToggleHelp,
|
||||
new: handleHotkeyNew,
|
||||
search: handleHotkeySearch,
|
||||
forceNew: handleHotkeyForceNew,
|
||||
back: handleHotkeyBack,
|
||||
goToHome: handleHotkeyGoToHome,
|
||||
goToNotifications: handleHotkeyGoToNotifications,
|
||||
goToFavourites: handleHotkeyGoToFavourites,
|
||||
goToPinned: handleHotkeyGoToPinned,
|
||||
goToProfile: handleHotkeyGoToProfile,
|
||||
goToBlocked: handleHotkeyGoToBlocked,
|
||||
goToMuted: handleHotkeyGoToMuted,
|
||||
goToRequests: handleHotkeyGoToRequests,
|
||||
};
|
||||
|
||||
/** Render the onboarding flow. */
|
||||
const renderOnboarding = () => (
|
||||
<BundleContainer fetchComponent={OnboardingWizard} loading={LoadingScreen}>
|
||||
|
@ -176,31 +314,33 @@ const SoapboxMount = () => {
|
|||
<BrowserRouter basename={BuildConfig.FE_SUBDIRECTORY}>
|
||||
<CompatRouter>
|
||||
<ScrollContext shouldUpdateScroll={shouldUpdateScroll}>
|
||||
<Switch>
|
||||
<Route
|
||||
path='/embed/:statusId'
|
||||
render={(props) => <EmbeddedStatus params={props.match.params} />}
|
||||
/>
|
||||
<Redirect from='/@:username/:statusId/embed' to='/embed/:statusId' />
|
||||
<HotKeys keyMap={keyMap} handlers={me ? handlers : undefined} ref={setHotkeysRef} attach={window} focused>
|
||||
<Switch>
|
||||
<Route
|
||||
path='/embed/:statusId'
|
||||
render={(props) => <EmbeddedStatus params={props.match.params} />}
|
||||
/>
|
||||
<Redirect from='/@:username/:statusId/embed' to='/embed/:statusId' />
|
||||
|
||||
<Route>
|
||||
{renderBody()}
|
||||
<Route>
|
||||
{renderBody()}
|
||||
|
||||
<BundleContainer fetchComponent={ModalContainer}>
|
||||
{Component => <Component />}
|
||||
</BundleContainer>
|
||||
<BundleContainer fetchComponent={ModalContainer}>
|
||||
{Component => <Component />}
|
||||
</BundleContainer>
|
||||
|
||||
<GdprBanner />
|
||||
<GdprBanner />
|
||||
|
||||
<div id='toaster'>
|
||||
<Toaster
|
||||
position='top-right'
|
||||
containerClassName='top-10'
|
||||
containerStyle={{ top: 75 }}
|
||||
/>
|
||||
</div>
|
||||
</Route>
|
||||
</Switch>
|
||||
<div id='toaster'>
|
||||
<Toaster
|
||||
position='top-right'
|
||||
containerClassName='top-10'
|
||||
containerStyle={{ top: 75 }}
|
||||
/>
|
||||
</div>
|
||||
</Route>
|
||||
</Switch>
|
||||
</HotKeys>
|
||||
</ScrollContext>
|
||||
</CompatRouter>
|
||||
</BrowserRouter>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
|
@ -61,34 +62,59 @@ const SuggestionItem: React.FC<ISuggestionItem> = ({ accountId }) => {
|
|||
);
|
||||
};
|
||||
|
||||
const FeedSuggestions = () => {
|
||||
interface IFeedSuggesetions {
|
||||
statusId: string
|
||||
onMoveUp?: (statusId: string, featured?: boolean) => void
|
||||
onMoveDown?: (statusId: string, featured?: boolean) => void
|
||||
}
|
||||
|
||||
const FeedSuggestions: React.FC<IFeedSuggesetions> = ({ statusId, onMoveUp, onMoveDown }) => {
|
||||
const intl = useIntl();
|
||||
const suggestedProfiles = useAppSelector((state) => state.suggestions.items);
|
||||
const isLoading = useAppSelector((state) => state.suggestions.isLoading);
|
||||
|
||||
if (!isLoading && suggestedProfiles.size === 0) return null;
|
||||
|
||||
const handleHotkeyMoveUp = (e?: KeyboardEvent): void => {
|
||||
if (onMoveUp) {
|
||||
onMoveUp(statusId);
|
||||
}
|
||||
};
|
||||
|
||||
const handleHotkeyMoveDown = (e?: KeyboardEvent): void => {
|
||||
if (onMoveDown) {
|
||||
onMoveDown(statusId);
|
||||
}
|
||||
};
|
||||
|
||||
const handlers = {
|
||||
moveUp: handleHotkeyMoveUp,
|
||||
moveDown: handleHotkeyMoveDown,
|
||||
};
|
||||
|
||||
return (
|
||||
<Card size='lg' variant='rounded' className='space-y-6'>
|
||||
<HStack justifyContent='between' alignItems='center'>
|
||||
<CardTitle title={intl.formatMessage(messages.heading)} />
|
||||
<HotKeys handlers={handlers}>
|
||||
<Card size='lg' variant='rounded' className='focusable space-y-6' tabIndex={0}>
|
||||
<HStack justifyContent='between' alignItems='center'>
|
||||
<CardTitle title={intl.formatMessage(messages.heading)} />
|
||||
|
||||
<Link
|
||||
to='/suggestions'
|
||||
className='text-primary-600 hover:underline dark:text-accent-blue'
|
||||
>
|
||||
{intl.formatMessage(messages.viewAll)}
|
||||
</Link>
|
||||
</HStack>
|
||||
|
||||
<CardBody>
|
||||
<HStack space={4} alignItems='center' className='overflow-x-auto md:space-x-0 lg:overflow-x-hidden'>
|
||||
{suggestedProfiles.slice(0, 4).map((suggestedProfile) => (
|
||||
<SuggestionItem key={suggestedProfile.account} accountId={suggestedProfile.account} />
|
||||
))}
|
||||
<Link
|
||||
to='/suggestions'
|
||||
className='text-primary-600 hover:underline dark:text-accent-blue'
|
||||
>
|
||||
{intl.formatMessage(messages.viewAll)}
|
||||
</Link>
|
||||
</HStack>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
<CardBody>
|
||||
<HStack space={4} alignItems='center' className='overflow-x-auto md:space-x-0 lg:overflow-x-hidden'>
|
||||
{suggestedProfiles.slice(0, 4).map((suggestedProfile) => (
|
||||
<SuggestionItem key={suggestedProfile.account} accountId={suggestedProfile.account} />
|
||||
))}
|
||||
</HStack>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</HotKeys>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -263,15 +263,12 @@ const Thread = (props: IThread) => {
|
|||
};
|
||||
|
||||
const _selectChild = (index: number) => {
|
||||
if (!useWindowScroll) index = index + 1;
|
||||
scroller.current?.scrollIntoView({
|
||||
index,
|
||||
behavior: 'smooth',
|
||||
done: () => {
|
||||
const element = document.querySelector<HTMLDivElement>(`#thread [data-index="${index}"] .focusable`);
|
||||
|
||||
if (element) {
|
||||
element.focus();
|
||||
}
|
||||
node.current?.querySelector<HTMLDivElement>(`[data-index="${index}"] .focusable`)?.focus();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -465,4 +462,4 @@ const Thread = (props: IThread) => {
|
|||
);
|
||||
};
|
||||
|
||||
export default Thread;
|
||||
export default Thread;
|
||||
|
|
|
@ -2,13 +2,11 @@
|
|||
|
||||
import clsx from 'clsx';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
import { Switch, useHistory, useLocation, Redirect } from 'react-router-dom';
|
||||
|
||||
import { fetchFollowRequests } from 'soapbox/actions/accounts';
|
||||
import { fetchReports, fetchUsers, fetchConfig } from 'soapbox/actions/admin';
|
||||
import { fetchAnnouncements } from 'soapbox/actions/announcements';
|
||||
import { resetCompose } from 'soapbox/actions/compose';
|
||||
import { fetchCustomEmojis } from 'soapbox/actions/custom-emojis';
|
||||
import { fetchFilters } from 'soapbox/actions/filters';
|
||||
import { fetchMarker } from 'soapbox/actions/markers';
|
||||
|
@ -154,34 +152,6 @@ const GroupMembershipRequestsSlug = withHoc(GroupMembershipRequests as any, Grou
|
|||
|
||||
const EmptyPage = HomePage;
|
||||
|
||||
const keyMap = {
|
||||
help: '?',
|
||||
new: 'n',
|
||||
search: ['s', '/'],
|
||||
forceNew: 'option+n',
|
||||
reply: 'r',
|
||||
favourite: 'f',
|
||||
react: 'e',
|
||||
boost: 'b',
|
||||
mention: 'm',
|
||||
open: ['enter', 'o'],
|
||||
openProfile: 'p',
|
||||
moveDown: ['down', 'j'],
|
||||
moveUp: ['up', 'k'],
|
||||
back: 'backspace',
|
||||
goToHome: 'g h',
|
||||
goToNotifications: 'g n',
|
||||
goToFavourites: 'g f',
|
||||
goToPinned: 'g p',
|
||||
goToProfile: 'g u',
|
||||
goToBlocked: 'g b',
|
||||
goToMuted: 'g m',
|
||||
goToRequests: 'g r',
|
||||
toggleHidden: 'x',
|
||||
toggleSensitive: 'h',
|
||||
openMedia: 'a',
|
||||
};
|
||||
|
||||
interface ISwitchingColumnsArea {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
@ -396,7 +366,6 @@ const UI: React.FC<IUI> = ({ children }) => {
|
|||
const userStream = useRef<any>(null);
|
||||
const nostrStream = useRef<any>(null);
|
||||
const node = useRef<HTMLDivElement | null>(null);
|
||||
const hotkeys = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const me = useAppSelector(state => state.me);
|
||||
const { account } = useOwnAccount();
|
||||
|
@ -527,91 +496,6 @@ const UI: React.FC<IUI> = ({ children }) => {
|
|||
}
|
||||
}, [pendingPolicy, !!account]);
|
||||
|
||||
const handleHotkeyNew = (e?: KeyboardEvent) => {
|
||||
e?.preventDefault();
|
||||
if (!node.current) return;
|
||||
|
||||
const element = node.current.querySelector('textarea#compose-textarea') as HTMLTextAreaElement;
|
||||
|
||||
if (element) {
|
||||
element.focus();
|
||||
}
|
||||
};
|
||||
|
||||
const handleHotkeySearch = (e?: KeyboardEvent) => {
|
||||
e?.preventDefault();
|
||||
if (!node.current) return;
|
||||
|
||||
const element = node.current.querySelector('input#search') as HTMLInputElement;
|
||||
|
||||
if (element) {
|
||||
element.focus();
|
||||
}
|
||||
};
|
||||
|
||||
const handleHotkeyForceNew = (e?: KeyboardEvent) => {
|
||||
handleHotkeyNew(e);
|
||||
dispatch(resetCompose());
|
||||
};
|
||||
|
||||
const handleHotkeyBack = () => {
|
||||
if (window.history && window.history.length === 1) {
|
||||
history.push('/');
|
||||
} else {
|
||||
history.goBack();
|
||||
}
|
||||
};
|
||||
|
||||
const setHotkeysRef: React.LegacyRef<HotKeys> = (c: any) => {
|
||||
hotkeys.current = c;
|
||||
|
||||
if (!me || !hotkeys.current) return;
|
||||
|
||||
// @ts-ignore
|
||||
hotkeys.current.__mousetrap__.stopCallback = (_e, element) => {
|
||||
return ['TEXTAREA', 'SELECT', 'INPUT', 'EM-EMOJI-PICKER'].includes(element.tagName);
|
||||
};
|
||||
};
|
||||
|
||||
const handleHotkeyToggleHelp = () => {
|
||||
dispatch(openModal('HOTKEYS'));
|
||||
};
|
||||
|
||||
const handleHotkeyGoToHome = () => {
|
||||
history.push('/');
|
||||
};
|
||||
|
||||
const handleHotkeyGoToNotifications = () => {
|
||||
history.push('/notifications');
|
||||
};
|
||||
|
||||
const handleHotkeyGoToFavourites = () => {
|
||||
if (!account) return;
|
||||
history.push(`/@${account.username}/favorites`);
|
||||
};
|
||||
|
||||
const handleHotkeyGoToPinned = () => {
|
||||
if (!account) return;
|
||||
history.push(`/@${account.username}/pins`);
|
||||
};
|
||||
|
||||
const handleHotkeyGoToProfile = () => {
|
||||
if (!account) return;
|
||||
history.push(`/@${account.username}`);
|
||||
};
|
||||
|
||||
const handleHotkeyGoToBlocked = () => {
|
||||
history.push('/blocks');
|
||||
};
|
||||
|
||||
const handleHotkeyGoToMuted = () => {
|
||||
history.push('/mutes');
|
||||
};
|
||||
|
||||
const handleHotkeyGoToRequests = () => {
|
||||
history.push('/follow_requests');
|
||||
};
|
||||
|
||||
const shouldHideFAB = (): boolean => {
|
||||
const path = location.pathname;
|
||||
return Boolean(path.match(/^\/posts\/|^\/search|^\/getting-started|^\/chats/));
|
||||
|
@ -620,85 +504,65 @@ const UI: React.FC<IUI> = ({ children }) => {
|
|||
// Wait for login to succeed or fail
|
||||
if (me === null) return null;
|
||||
|
||||
type HotkeyHandlers = { [key: string]: (keyEvent?: KeyboardEvent) => void };
|
||||
|
||||
const handlers: HotkeyHandlers = {
|
||||
help: handleHotkeyToggleHelp,
|
||||
new: handleHotkeyNew,
|
||||
search: handleHotkeySearch,
|
||||
forceNew: handleHotkeyForceNew,
|
||||
back: handleHotkeyBack,
|
||||
goToHome: handleHotkeyGoToHome,
|
||||
goToNotifications: handleHotkeyGoToNotifications,
|
||||
goToFavourites: handleHotkeyGoToFavourites,
|
||||
goToPinned: handleHotkeyGoToPinned,
|
||||
goToProfile: handleHotkeyGoToProfile,
|
||||
goToBlocked: handleHotkeyGoToBlocked,
|
||||
goToMuted: handleHotkeyGoToMuted,
|
||||
goToRequests: handleHotkeyGoToRequests,
|
||||
};
|
||||
|
||||
const style: React.CSSProperties = {
|
||||
pointerEvents: dropdownMenuIsOpen ? 'none' : undefined,
|
||||
};
|
||||
|
||||
return (
|
||||
<HotKeys keyMap={keyMap} handlers={me ? handlers : undefined} ref={setHotkeysRef} attach={window} focused>
|
||||
<div ref={node} style={style}>
|
||||
<div
|
||||
className={clsx('pointer-events-none fixed z-[90] h-screen w-screen transition', {
|
||||
'backdrop-blur': isDragging,
|
||||
})}
|
||||
/>
|
||||
<div ref={node} style={style}>
|
||||
<div
|
||||
className={clsx('pointer-events-none fixed z-[90] h-screen w-screen transition', {
|
||||
'backdrop-blur': isDragging,
|
||||
})}
|
||||
/>
|
||||
|
||||
<BackgroundShapes />
|
||||
<BackgroundShapes />
|
||||
|
||||
<div className='z-10 flex flex-col'>
|
||||
<Navbar />
|
||||
<div className='z-10 flex flex-col'>
|
||||
<Navbar />
|
||||
|
||||
<Layout>
|
||||
<Layout.Sidebar>
|
||||
{!standalone && <SidebarNavigation />}
|
||||
</Layout.Sidebar>
|
||||
<Layout>
|
||||
<Layout.Sidebar>
|
||||
{!standalone && <SidebarNavigation />}
|
||||
</Layout.Sidebar>
|
||||
|
||||
<SwitchingColumnsArea>
|
||||
{children}
|
||||
</SwitchingColumnsArea>
|
||||
</Layout>
|
||||
<SwitchingColumnsArea>
|
||||
{children}
|
||||
</SwitchingColumnsArea>
|
||||
</Layout>
|
||||
|
||||
{(me && !shouldHideFAB()) && (
|
||||
<div className='fixed bottom-24 right-4 z-40 transition-all rtl:left-4 rtl:right-auto lg:hidden'>
|
||||
<FloatingActionButton />
|
||||
</div>
|
||||
)}
|
||||
{(me && !shouldHideFAB()) && (
|
||||
<div className='fixed bottom-24 right-4 z-40 transition-all rtl:left-4 rtl:right-auto lg:hidden'>
|
||||
<FloatingActionButton />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{me && (
|
||||
<BundleContainer fetchComponent={SidebarMenu}>
|
||||
{Component => <Component />}
|
||||
</BundleContainer>
|
||||
)}
|
||||
|
||||
{me && features.chats && (
|
||||
<BundleContainer fetchComponent={ChatWidget}>
|
||||
{Component => (
|
||||
<div className='hidden xl:block'>
|
||||
<Component />
|
||||
</div>
|
||||
)}
|
||||
</BundleContainer>
|
||||
)}
|
||||
<ThumbNavigation />
|
||||
|
||||
<BundleContainer fetchComponent={ProfileHoverCard}>
|
||||
{me && (
|
||||
<BundleContainer fetchComponent={SidebarMenu}>
|
||||
{Component => <Component />}
|
||||
</BundleContainer>
|
||||
)}
|
||||
|
||||
<BundleContainer fetchComponent={StatusHoverCard}>
|
||||
{Component => <Component />}
|
||||
{me && features.chats && (
|
||||
<BundleContainer fetchComponent={ChatWidget}>
|
||||
{Component => (
|
||||
<div className='hidden xl:block'>
|
||||
<Component />
|
||||
</div>
|
||||
)}
|
||||
</BundleContainer>
|
||||
</div>
|
||||
)}
|
||||
<ThumbNavigation />
|
||||
|
||||
<BundleContainer fetchComponent={ProfileHoverCard}>
|
||||
{Component => <Component />}
|
||||
</BundleContainer>
|
||||
|
||||
<BundleContainer fetchComponent={StatusHoverCard}>
|
||||
{Component => <Component />}
|
||||
</BundleContainer>
|
||||
</div>
|
||||
</HotKeys>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue