@state-less/leap-frontend
Version:
A collection of open source fullstack services powered by React Server
189 lines (188 loc) • 11.1 kB
JavaScript
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;