import { useEffect, useMemo } from 'react';
import { Link, Navigate, useParams } from 'react-router-dom';
import { Trans, useTranslation } from 'react-i18next';
import { useCommunity } from '@bitsocial/bitsocial-react-hooks';
import { shouldShowSnow } from '../../lib/snow';
import { BottomButton, BracketedCatalogButton, CatalogButton, ReturnButton, TopButton } from '../../components/board-buttons/board-buttons';
import { PageFooterDesktop, PageFooterMobile, ThreadFooterStyleRow } from '../../components/footer';
import LoadingEllipsis from '../../components/loading-ellipsis';
import Tooltip from '../../components/tooltip';
import { useDirectories } from '../../hooks/use-directories';
import { useCommunityIdentifier } from '../../hooks/use-community-identifiers';
import { useResolvedCommunityAddress } from '../../hooks/use-resolved-community-address';
import { isDirectoryRoute } from '../../lib/utils/route-utils';
import { DirectoryListBoard, sortDirectoryBoardsByRank, useDirectoryList } from '../../hooks/use-directory-list';
import { type CommunityFreshnessState, isCommunityKnownOffline } from '../../lib/utils/community-freshness-utils';
import getShortAddress from '../../lib/get-short-address';
import { get5chanDeveloperBadge } from '../../lib/utils/author-display-utils';
import useCommunityOfflineStore from '../../stores/use-community-offline-store';
import useIsCommunityOffline from '../../hooks/use-is-community-offline';
import { useNowSeconds } from '../../hooks/use-now-seconds';
import postStyles from '../post/post.module.css';
import styles from './directory.module.css';
const DIRECTORY_STATUS_CHECK_LIMIT = 5;
const DIRECTORY_STATUS_UNAVAILABLE_MARKER = '\u2014';
const computeBoardStatus = (
communityState: CommunityFreshnessState | undefined,
offlineState: CommunityFreshnessState | undefined,
nowSeconds: number,
isOffline: boolean,
isOnlineStatusLoading: boolean,
): 'online' | 'offline' | 'loading' | 'unknown' => {
const freshnessState = {
state: communityState?.state ?? offlineState?.state,
updatedAt: communityState?.updatedAt ?? offlineState?.updatedAt,
};
if (isOffline || isCommunityKnownOffline(freshnessState, nowSeconds)) return 'offline';
if (isOnlineStatusLoading) return 'loading';
if (!freshnessState.updatedAt) return 'unknown';
return 'online';
};
const PASS_LINK = '/pass';
const DirectoryDesktopTopControls = ({ communityAddress }: { communityAddress: string | undefined }) => (
[]
[]
);
const DirectoryDesktopFooterControls = ({ communityAddress }: { communityAddress: string | undefined }) => (
[]
[]
);
const DirectoryMobileTopControls = ({ communityAddress }: { communityAddress: string | undefined }) => (
);
const DirectoryMobileFooterControls = ({ communityAddress }: { communityAddress: string | undefined }) => (
);
interface DirectoryRowProps {
board: DirectoryListBoard;
nowSeconds: number;
rank: number;
onVote: () => void;
}
const DirectoryRow = ({ board, nowSeconds, rank, onVote }: DirectoryRowProps) => {
const { t } = useTranslation();
const statusUnavailableReason = t('directory_status_unavailable_reason');
const ownerAddress = board.owner;
const ownerDisplay = ownerAddress ? getShortAddress(ownerAddress) || ownerAddress : undefined;
const developerBadge = get5chanDeveloperBadge(ownerAddress);
const shouldCheckStatus = rank <= DIRECTORY_STATUS_CHECK_LIMIT;
const communityIdentifier = useCommunityIdentifier(shouldCheckStatus ? board.address : undefined);
const community = useCommunity(shouldCheckStatus && communityIdentifier ? { community: communityIdentifier } : undefined);
const { isOffline, isOnlineStatusLoading } = useIsCommunityOffline(community, shouldCheckStatus ? board.address : undefined);
const offlineState = useCommunityOfflineStore((state) => (shouldCheckStatus ? state.communityOfflineState[board.address] : undefined));
const status = shouldCheckStatus ? computeBoardStatus(community, offlineState, nowSeconds, isOffline, isOnlineStatusLoading) : 'unavailable';
const boardLink = `/${board.address}`;
return (
| {rank} |
{board.address} |
{ownerDisplay ?? t('directory_owner_anonymous')}
{developerBadge && (
<>
{' ## '}
{developerBadge.label}
>
)}
|
{status === 'unavailable' ? (
{DIRECTORY_STATUS_UNAVAILABLE_MARKER}
) : status === 'loading' ? (
) : status === 'unknown' ? (
{DIRECTORY_STATUS_UNAVAILABLE_MARKER}
) : (
{t(status === 'offline' ? 'directory_status_offline' : 'directory_status_online')}
)}
|
{board.score ?? DIRECTORY_STATUS_UNAVAILABLE_MARKER}
|
[
] [
] [
{t('view')}
]
|
);
};
const getRepoEditUrl = (directoryCode: string) => `https://github.com/bitsocialnet/lists/edit/master/5chan-directories/5chan-${directoryCode}-directory.json`;
const Directory = () => {
const { t } = useTranslation();
const params = useParams();
const boardIdentifier = params.boardIdentifier;
const directories = useDirectories();
const isValidDirectoryCode = !!boardIdentifier && isDirectoryRoute(boardIdentifier, directories);
const { list, loading } = useDirectoryList(isValidDirectoryCode ? boardIdentifier : undefined);
const communityAddress = useResolvedCommunityAddress();
const nowSeconds = useNowSeconds();
const ranked = useMemo(() => (list ? sortDirectoryBoardsByRank(list.boards) : []), [list]);
const directoryTitle = list?.title || (boardIdentifier ? `/${boardIdentifier}/ - ${t('directory')}` : t('directory'));
useEffect(() => {
if (!isValidDirectoryCode) return;
document.title = `${directoryTitle} - 5chan`;
}, [directoryTitle, isValidDirectoryCode]);
if (!isValidDirectoryCode) {
return ;
}
const handleVoteUnavailable = () => {
const values = { boardIdentifier };
window.alert(`${t('directory_voting_unavailable_intro', values)}\n\n${t('directory_voting_unavailable_outro', values)}`);
};
const isLoadingShell = loading && ranked.length === 0;
const boardCount = ranked.length;
const repoEditUrl = getRepoEditUrl(boardIdentifier!);
return (
{isLoadingShell ? (
) : ranked.length === 0 ? (
{t('directory_empty')}
) : (
{t('directory_heading', { boardIdentifier, count: boardCount })}
)}
{!isLoadingShell && ranked.length > 0 && (
<>
|
No.
|
{t('directory_board')}
|
{t('directory_owner')}
|
{t('directory_status')}
|
{t('directory_score')}
|
{t('directory_vote')}
|
{ranked.map((board, index) => (
))}
>
)}
} styleRow={
} />
);
};
export default Directory;