115 lines
4.1 KiB
TypeScript
115 lines
4.1 KiB
TypeScript
import { useQuery } from 'convex/react';
|
|
import { useRef, type ReactNode } from 'react';
|
|
import { useLocation } from 'react-router-dom';
|
|
import { api } from '../../../../../convex/_generated/api';
|
|
import { useIsMobile } from '../../hooks/useIsMobile';
|
|
import { useMobileSwipeNav } from '../../hooks/useMobileSwipeNav';
|
|
import { FileUploadDropZone } from '../channel/FileUploadDropZone';
|
|
import { ChannelView } from '../channel/ChannelView';
|
|
import { DMLayout } from '../dm/DMLayout';
|
|
import { GuildList } from './GuildList';
|
|
import { GuildNavbar } from './GuildNavbar';
|
|
import { MobileBottomNav } from './MobileBottomNav';
|
|
import { UserArea } from './UserArea';
|
|
import styles from './GuildsLayout.module.css';
|
|
|
|
interface GuildsLayoutProps {
|
|
children: ReactNode;
|
|
}
|
|
|
|
/**
|
|
* Parse the current pathname to decide which mobile view to show. At
|
|
* ≤768px exactly one of the three columns is visible at a time.
|
|
* /channels/@me → nav (DM list)
|
|
* /channels/@me/:channelId → chat
|
|
* /channels/:serverId → nav (channel list)
|
|
* /channels/:serverId/:channelId → chat
|
|
* /channels/you → you (mobile profile tab)
|
|
*/
|
|
function detectMobileMode(pathname: string): 'nav' | 'chat' | 'you' {
|
|
const segments = pathname.split('/').filter(Boolean);
|
|
if (segments[0] !== 'channels') return 'nav';
|
|
if (segments[1] === 'you') return 'you';
|
|
return segments.length >= 3 && !!segments[2] ? 'chat' : 'nav';
|
|
}
|
|
|
|
export function GuildsLayout({ children }: GuildsLayoutProps) {
|
|
const location = useLocation();
|
|
const segments = location.pathname.split('/').filter(Boolean);
|
|
// /channels/:serverId/... — serverId is segments[1], '@me' for DMs
|
|
const serverId = segments[0] === 'channels' ? segments[1] : undefined;
|
|
const channelId = segments[0] === 'channels' ? segments[2] : undefined;
|
|
const hasServer = !!serverId && serverId !== '@me' && serverId !== 'you';
|
|
const mobileMode = detectMobileMode(location.pathname);
|
|
|
|
const isMobile = useIsMobile();
|
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
useMobileSwipeNav(containerRef, wrapperRef);
|
|
|
|
// On mobile nav mode, pre-render the last-viewed channel so the
|
|
// swipe preview shows actual chat content instead of the "Select a
|
|
// channel" placeholder. The remembered channelId comes from the
|
|
// same localStorage key the swipe hook writes.
|
|
const rememberedChannelId =
|
|
isMobile && mobileMode === 'nav' && serverId
|
|
? (() => {
|
|
try {
|
|
return localStorage.getItem(`brycord:lastChannel:${serverId}`);
|
|
} catch {
|
|
return null;
|
|
}
|
|
})()
|
|
: null;
|
|
|
|
// Look up the current channel's name + type so the drop overlay
|
|
// reads "Upload to #channel-name". Drops are disabled when there's
|
|
// no channel in the URL (DM home, `/you` profile) and on voice
|
|
// channels — they have no composer to attach to.
|
|
const channel = useQuery(
|
|
api.channels.get,
|
|
channelId ? { id: channelId as any } : 'skip',
|
|
);
|
|
const channelName = (channel && (channel as any).name) || 'channel';
|
|
const channelType = (channel && (channel as any).type) || undefined;
|
|
const dropDisabled = !channelId || channelType === 'voice' || channelType === 'category';
|
|
|
|
return (
|
|
<FileUploadDropZone
|
|
channelName={channelName}
|
|
disabled={dropDisabled}
|
|
onDropFiles={(files) => {
|
|
if (!channelId) return;
|
|
window.dispatchEvent(
|
|
new CustomEvent('brycord:drop-files', {
|
|
detail: { channelId, files },
|
|
}),
|
|
);
|
|
}}
|
|
>
|
|
<div className={styles.wrapper} data-mobile-mode={mobileMode} ref={wrapperRef}>
|
|
<div className={styles.container} ref={containerRef}>
|
|
<div className={styles.guildList}>
|
|
<GuildList />
|
|
</div>
|
|
<div className={styles.sidebar}>
|
|
{hasServer ? <GuildNavbar /> : <DMLayout />}
|
|
</div>
|
|
<div className={styles.sidebarDivider} />
|
|
<div className={styles.content}>
|
|
{rememberedChannelId ? (
|
|
<ChannelView channelId={rememberedChannelId} />
|
|
) : (
|
|
children
|
|
)}
|
|
</div>
|
|
<div className={styles.userAreaWrapper}>
|
|
<UserArea />
|
|
</div>
|
|
</div>
|
|
<MobileBottomNav />
|
|
</div>
|
|
</FileUploadDropZone>
|
|
);
|
|
}
|