UNPKG

@state-less/leap-frontend

Version:

A collection of open source fullstack services powered by React Server

189 lines (188 loc) 11.1 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { Container, Button, Typography, Box, Card, CardHeader, CardContent, Chip, Link, Pagination, CardActions, Grid, MenuItem, Select, Accordion, AccordionSummary, AccordionDetails, } from '@mui/material'; import { useComponent, useLocalStorage } from '@state-less/react-client'; import { Link as RouterLink } from 'react-router-dom'; import { useEffect, useMemo, useRef, useState } from 'react'; import { Markdown } from '../components/Markdown.js'; import { FlexBox } from '../components/FlexBox.js'; import { calc } from '../server-components/examples/VotingApp.js'; import { DEF_PAGE_SIZE } from '../lib/const.js'; import { ViewCounter } from '../server-components/examples/ViewCounter.js'; import { GoogleLoginButton } from '../components/LoggedInGoogleButton.js'; export const ForumPage = ({ pageSize: configuredPageSize = DEF_PAGE_SIZE, startPage = 1, forumKey, basePath = '', clientId, ghSrc = {}, ssr, }) => { const [page, setPage] = useState(startPage); const [pageSize, setPageSize] = useLocalStorage('forum-page-size', configuredPageSize); const [component, { loading }] = useComponent(forumKey, { suspend: true, ssr, props: { page, pageSize, compound: false, }, }); useEffect(() => { document .getElementById('root-container') ?.scrollTo({ top: 0, behavior: 'smooth' }); }, [page]); return (_jsx(Container, { maxWidth: "lg", disableGutters: true, children: _jsxs(Card, { sx: { boxShadow: '0 2px 2px -2px gray;', px: { xs: 0, sm: 2, md: 4, }, mb: '1px', }, children: [_jsx(Header, {}), _jsx(CardContent, { children: _jsxs(Grid, { container: true, spacing: 2, children: [_jsx(Grid, { item: true, xs: 12, md: 9, order: { xs: 1, md: 0, }, children: _jsx(Posts, { component: component, basePath: basePath, ssr: ssr }) }), _jsx(Grid, { item: true, xs: 12, md: 3, order: { xs: 0, md: 1, }, children: _jsx(ForumRules, { ghSrc: ghSrc, basePath: basePath, clientId: clientId }) })] }) }), _jsxs(CardActions, { sx: { boxShadow: '0px 0px 2px 0px black', display: 'flex', mt: 4, }, children: [_jsx(Pagination, { count: Math.ceil(component?.props?.totalCount / pageSize) || 0, page: page, onChange: (_, p) => setPage(p) }), _jsx(PageSize, { pageSize: pageSize, setPageSize: setPageSize })] })] }) })); }; export const ForumRules = ({ ghSrc, basePath, clientId }) => { const [expanded, setExpanded] = useState(0); return (_jsxs(StickyCard, { top: 64, children: [_jsxs(Accordion, { defaultExpanded: true, expanded: expanded === 0, onChange: () => setExpanded(expanded === 0 ? -1 : 0), children: [_jsx(AccordionSummary, { sx: { minHeight: '48px !important', }, children: "Forum Rules" }), _jsx(AccordionDetails, { children: _jsx(Markdown, { center: false, src: ghSrc.rules, children: "Error Loading Rules" }) })] }), _jsxs(Accordion, { expanded: expanded === 1, onChange: () => setExpanded(expanded === 1 ? -1 : 1), children: [_jsx(AccordionSummary, { sx: { minHeight: '48px !important', }, children: "Q & A" }), _jsx(AccordionDetails, { children: _jsx(Markdown, { center: false, src: ghSrc.qa, children: "Loading from GitHub" }) })] }), _jsxs(CardActions, { children: [_jsx(NewPostButton, { basePath: basePath }), clientId && _jsx(GoogleLoginButton, { clientId: clientId })] })] })); }; export const StickyCard = (props) => { const ref = useRef(null); useEffect(() => { if (!ref?.current) return; const onScroll = (e) => { const scrollTop = e?.target?.scrollTop || 0; const max = (ref?.current?.parentElement?.getBoundingClientRect?.()?.height || 0) - (ref?.current?.getBoundingClientRect()?.height || 0) - 16; const top = Math.min(max, Math.max(0, scrollTop - props.top)) + 'px'; if (ref.current) { ref.current.style.transform = `translateY(${top})`; } }; document.getElementById('scroll')?.addEventListener('scroll', onScroll); return () => { document .getElementById('scroll') ?.removeEventListener('scroll', onScroll); }; }, [ref?.current]); return _jsx(Card, { ref: ref, children: props.children }); }; const Post = (post) => { const { basePath, ssr } = post; const [votes] = useComponent(post.children?.[0]?.component, { data: post.children?.[0], suspend: true, ssr, }); const { score, upvotes, downvotes } = votes?.props || {}; const wilson = true, random = true; const randomUp = useMemo(() => Math.random(), []); const randomDown = useMemo(() => Math.random(), []); const sum = useMemo(() => calc(upvotes, { lb: score?.upvote[0], ub: score?.upvote[1], wilson, random, r: randomUp, }) - calc(downvotes, { lb: score?.downvote[0], ub: score?.downvote[1], wilson, random, r: randomDown, }), [upvotes, downvotes, score, wilson, random]); const nAnswers = post.children?.filter?.((c) => c?.props?.body && !c?.props?.deleted) ?.length || 0; return (_jsx(Card, { square: true, sx: { opacity: post.props.deleted ? 0.9 : 1, }, children: _jsxs(Box, { sx: { display: 'flex', flexDirection: { xs: 'column', md: 'row', }, }, children: [post?.props?.sticky && (_jsx(Box, { sx: { position: 'absolute', left: 0, top: 0, width: { xs: '100%', md: '2px', }, height: { xs: '2px', md: 'unset', }, // borderTop: '4px dashed', backgroundColor: 'info.main', } })), _jsxs(Grid, { container: true, children: [_jsx(Grid, { item: true, order: { xs: 2, md: 0 }, xs: 12, md: 3, children: _jsx(FlexBox, { sx: { flexDirection: 'column', gap: 1, minWidth: 200 }, children: _jsx(PostOverviewMeta, { plainText: false, nAnswers: nAnswers, nVotes: sum, post: post, ssr: ssr }) }) }), _jsx(Grid, { item: true, xs: 12, md: 9, children: _jsxs(Box, { children: [_jsx(CardHeader, { title: _jsx(Link, { sx: { color: 'secondary.main' }, to: `${basePath}/${post.component}`, component: RouterLink, children: post.props.title }), sx: { pb: 0 } }), _jsx(CardContent, { sx: { pt: 0, pb: '0rem !important', maxHeight: '5rem', mb: 2, overflow: 'hidden', }, children: _jsx(Markdown, { preview: true, disablePadding: true, center: false, children: post.props.body || '' }) }), (post.props.tags?.length || 0) > 0 && (_jsx(CardContent, { sx: { display: 'flex', gap: 1 }, children: post.props.tags?.map((tag) => (_jsx(Chip, { size: "small", label: tag }))) }))] }) })] })] }) })); }; const PostOverviewMeta = ({ nVotes, nAnswers, post, plainText, ssr }) => { const votesStr = `${nVotes} votes`; const answersStr = `${nAnswers} answers`; return (_jsxs(CardContent, { sx: { ml: 'auto', mr: { xs: 'auto', md: 'unset', }, display: 'flex', flexWrap: 'wrap', flexDirection: { xs: 'row', md: 'column', }, gap: 1, textAlign: 'right', }, children: [plainText ? _jsx("span", { children: votesStr }) : _jsx(Chip, { label: votesStr }), plainText ? _jsx("span", { children: answersStr }) : _jsx(Chip, { label: answersStr }), post?.props?.canDelete && (_jsx(Chip, { variant: "outlined", color: post.props.deleted ? 'error' : post.props.locked ? 'warning' : post.props.approved ? 'success' : undefined, label: ['deleted', 'locked', 'approved'] .filter((k) => !!post.props[k]) .join('. ') })), _jsx(ViewCounter, { clientOnly: true, variant: plainText ? 'plaintext' : 'listitem', componentKey: post?.props?.viewCounter?.component, data: post?.props?.viewCounter, ssr: ssr })] })); }; const Posts = ({ component, basePath, ssr }) => { const sticky = component?.children?.filter((post) => post.props.sticky) || []; const nonSticky = component?.children?.filter((post) => !post.props.sticky) || []; return (_jsxs(FlexBox, { sx: { display: 'flex', flexDirection: 'column', gap: 4 }, children: [sticky?.length > 0 && (_jsx(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 0 }, children: sticky.map((post) => { return _jsx(Post, { ...post, basePath: basePath, ssr: ssr }); }) })), _jsx(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 0 }, children: nonSticky.map((post) => { return _jsx(Post, { ...post, basePath: basePath, ssr: ssr }); }) })] })); }; const Header = () => { return (_jsx(CardHeader, { title: _jsx(FlexBox, { sx: { alignItems: 'center' }, children: _jsx(Typography, { variant: "h5", children: "All Questions" }) }), sx: { alignItems: 'center', flexWrap: 'wrap-reverse', justifyContent: 'center', alignContent: 'center', } })); }; export const PageSize = ({ pageSize, setPageSize }) => { return (_jsxs(Select, { size: "small", value: pageSize, onChange: (e) => setPageSize(e.target.value), sx: { ml: 'auto', mr: 2 }, children: [_jsx(MenuItem, { value: 5, children: "5" }), _jsx(MenuItem, { value: 15, children: "15" }), _jsx(MenuItem, { value: 25, children: "25" }), _jsx(MenuItem, { value: 50, children: "50" })] })); }; export const NewPostButton = ({ basePath = '', sx, }) => { return (_jsx(Link, { to: `${basePath}/new`, component: RouterLink, color: '#EEEEEE', children: _jsx(Button, { variant: "contained", color: "secondary", sx: sx, children: "Ask Question" }) })); }; export default ForumPage;