mostly clientside render

This commit is contained in:
keyan 2021-04-26 19:55:48 -05:00
parent c626998952
commit c82c82bb7b
10 changed files with 119 additions and 79 deletions

View File

@ -28,14 +28,6 @@ export default {
WHERE "userId" = ${userId} AND "parentId" IS NULL WHERE "userId" = ${userId} AND "parentId" IS NULL
ORDER BY created_at`) ORDER BY created_at`)
}, },
comments: async (parent, { parentId }, { models }) => {
const flat = await models.$queryRaw(`
${SELECT}
FROM "Item"
WHERE path <@ (SELECT path FROM "Item" where id = ${parentId}) AND id != ${parentId}
ORDER BY "path"`)
return nestComments(flat, parentId)[0]
},
userComments: async (parent, { userId }, { models }) => { userComments: async (parent, { userId }, { models }) => {
return await models.$queryRaw(` return await models.$queryRaw(`
${SELECT} ${SELECT}
@ -119,6 +111,14 @@ export default {
WHERE path <@ text2ltree(${item.path}) AND id != ${item.id}` WHERE path <@ text2ltree(${item.path}) AND id != ${item.id}`
return count return count
}, },
comments: async (item, args, { models }) => {
const flat = await models.$queryRaw(`
${SELECT}
FROM "Item"
WHERE path <@ (SELECT path FROM "Item" where id = ${item.id}) AND id != ${item.id}
ORDER BY "path"`)
return nestComments(flat, item.id)[0]
},
sats: async (item, args, { models }) => { sats: async (item, args, { models }) => {
const { sum: { sats } } = await models.vote.aggregate({ const { sum: { sats } } = await models.vote.aggregate({
sum: { sum: {
@ -158,11 +158,6 @@ const createItem = async (parent, { title, text, url, parentId }, { me, models }
title, title,
url, url,
text, text,
item: {
connect: {
}
},
user: { user: {
connect: { connect: {
name: me.name name: me.name

View File

@ -6,7 +6,6 @@ export default gql`
recent: [Item!]! recent: [Item!]!
item(id: ID!): Item item(id: ID!): Item
userItems(userId: ID!): [Item!] userItems(userId: ID!): [Item!]
comments(parentId: ID!): [Item!]!
userComments(userId: ID!): [Item!] userComments(userId: ID!): [Item!]
root(id: ID!): Item root(id: ID!): Item
} }

View File

@ -42,7 +42,7 @@ function Parent ({ item }) {
) )
} }
export default function Comment ({ item, children, replyOpen, includeParent, cacheId, noReply }) { export default function Comment ({ item, children, replyOpen, includeParent, cacheId, noComments, noReply }) {
const [reply, setReply] = useState(replyOpen) const [reply, setReply] = useState(replyOpen)
return ( return (
@ -82,7 +82,7 @@ export default function Comment ({ item, children, replyOpen, includeParent, cac
{reply && <Reply parentId={item.id} onSuccess={() => setReply(replyOpen || false)} cacheId={cacheId} />} {reply && <Reply parentId={item.id} onSuccess={() => setReply(replyOpen || false)} cacheId={cacheId} />}
{children} {children}
<div className={styles.comments}> <div className={styles.comments}>
{item.comments {item.comments && !noComments
? item.comments.map((item) => ( ? item.comments.map((item) => (
<Comment key={item.id} item={item} /> <Comment key={item.id} item={item} />
)) ))

View File

@ -1,23 +1,31 @@
import { useQuery } from '@apollo/client' import { useQuery } from '@apollo/client'
import Comment, { CommentSkeleton } from './comment' import Comment, { CommentSkeleton } from './comment'
export default function Comments ({ query, ...props }) { export default function Comments ({ comments, ...props }) {
const { loading, error, data } = useQuery(query) return comments.map(item => (
if (error) return <div>Failed to load!</div>
if (loading) {
const comments = new Array(3).fill(null)
return comments.map((_, i) => (
<div key={i} className='mt-2'>
<CommentSkeleton skeletonChildren />
</div>
))
}
return data.comments.map(item => (
<div key={item.id} className='mt-2'> <div key={item.id} className='mt-2'>
<Comment item={item} {...props} /> <Comment item={item} {...props} />
</div> </div>
)) ))
} }
export function CommentsSkeleton () {
const comments = new Array(3).fill(null)
return comments.map((_, i) => (
<div key={i} className='mt-2'>
<CommentSkeleton skeletonChildren />
</div>
))
}
export function CommentsQuery ({ query, ...props }) {
const { loading, error, data } = useQuery(query)
if (error) return <div>Failed to load!</div>
if (loading) {
return <CommentsSkeleton />
}
return <Comments comments={data.comments} {...props} />
}

View File

@ -45,7 +45,7 @@ export default function Item ({ item, rank, children }) {
) )
} }
export function ItemSkeleton ({ rank }) { export function ItemSkeleton ({ rank, children }) {
return ( return (
<> <>
{rank && {rank &&
@ -66,6 +66,11 @@ export function ItemSkeleton ({ rank }) {
</div> </div>
</div> </div>
</div> </div>
{children && (
<div className={styles.children}>
{children}
</div>
)}
</> </>
) )
} }

View File

@ -8,7 +8,7 @@ export const CommentSchema = Yup.object({
text: Yup.string().required('required').trim() text: Yup.string().required('required').trim()
}) })
export default function Reply ({ parentId, onSuccess, cacheId }) { export default function Reply ({ parentId, onSuccess }) {
const [createComment] = useMutation( const [createComment] = useMutation(
gql` gql`
${COMMENTS} ${COMMENTS}
@ -22,7 +22,7 @@ export default function Reply ({ parentId, onSuccess, cacheId }) {
}`, { }`, {
update (cache, { data: { createComment } }) { update (cache, { data: { createComment } }) {
cache.modify({ cache.modify({
id: cacheId || `Item:${parentId}`, id: `Item:${parentId}`,
fields: { fields: {
comments (existingCommentRefs = []) { comments (existingCommentRefs = []) {
const newCommentRef = cache.writeFragment({ const newCommentRef = cache.writeFragment({
@ -67,3 +67,12 @@ export default function Reply ({ parentId, onSuccess, cacheId }) {
</div> </div>
) )
} }
export function ReplySkeleton () {
return (
<div className={`${styles.reply} ${styles.skeleton}`}>
<div className={`${styles.input} clouds`} />
<div className={`${styles.button} clouds`} />
</div>
)
}

View File

@ -2,3 +2,19 @@
max-width: 600px; max-width: 600px;
margin-top: 1rem; margin-top: 1rem;
} }
.skeleton .input {
background-color: grey;
width: 100%;
border-radius: .4rem;
height: 115px;
margin-bottom: 1rem;
}
.skeleton .button {
background-color: grey;
border-radius: .4rem;
margin-top: .25rem;
width: 71px;
height: 38px;
}

View File

@ -1,12 +1,12 @@
import Layout from '../../components/layout' import Layout from '../../components/layout'
import Comments from '../../components/comments' import { CommentsQuery } from '../../components/comments'
import { COMMENT_FIELDS } from '../../fragments/comments' import { COMMENT_FIELDS } from '../../fragments/comments'
import { gql } from '@apollo/client' import { gql } from '@apollo/client'
import ApolloClient from '../../api/client' import ApolloClient from '../../api/client'
import UserHeader from '../../components/user-header' import UserHeader from '../../components/user-header'
export async function getServerSideProps ({ params }) { export async function getServerSideProps ({ req, params }) {
const { error, data: { user } } = await ApolloClient.query({ const { error, data: { user } } = await (await ApolloClient(req)).query({
query: query:
gql`{ gql`{
user(name: "${params.username}") { user(name: "${params.username}") {
@ -46,7 +46,7 @@ export default function User ({ user }) {
return ( return (
<Layout> <Layout>
<UserHeader user={user} /> <UserHeader user={user} />
<Comments query={query} includeParent noReply /> <CommentsQuery query={query} includeParent noReply />
</Layout> </Layout>
) )
} }

View File

@ -1,64 +1,72 @@
import Item from '../../components/item' import Item, { ItemSkeleton } from '../../components/item'
import Layout from '../../components/layout' import Layout from '../../components/layout'
import ApolloClient from '../../api/client' import Reply, { ReplySkeleton } from '../../components/reply'
import Reply from '../../components/reply'
import Comment from '../../components/comment' import Comment from '../../components/comment'
import Text from '../../components/text' import Text from '../../components/text'
import Comments from '../../components/comments' import Comments, { CommentsSkeleton } from '../../components/comments'
import { COMMENTS } from '../../fragments/comments' import { COMMENTS } from '../../fragments/comments'
import { ITEM_FIELDS } from '../../fragments/items' import { ITEM_FIELDS } from '../../fragments/items'
import { gql } from '@apollo/client' import { gql, useQuery } from '@apollo/client'
import { useRouter } from 'next/router'
export async function getServerSideProps ({ req, params }) {
const { error, data: { item } } = await (await ApolloClient(req)).query({
query:
gql`
${ITEM_FIELDS}
{
item(id: ${params.id}) {
...ItemFields
text
}
}`
})
if (!item || error) {
return {
notFound: true
}
}
return {
props: {
item: item
}
}
}
export default function FullItem ({ item }) { export default function FullItem ({ item }) {
const commentsQuery = gql` const router = useRouter()
const { id } = router.query
const query = gql`
${ITEM_FIELDS}
${COMMENTS} ${COMMENTS}
{ {
comments(parentId: ${item.id}) { item(id: ${id}) {
...CommentsRecursive ...ItemFields
} text
comments {
...CommentsRecursive
}
}
}` }`
return ( return (
<Layout> <Layout>
<LoadItem query={query} />
</Layout>
)
}
function LoadItem ({ query }) {
const { loading, error, data } = useQuery(query)
if (error) return <div>Failed to load!</div>
if (loading) {
return (
<div>
<ItemSkeleton>
<ReplySkeleton />
</ItemSkeleton>
<div className='mt-5'>
<CommentsSkeleton />
</div>
</div>
)
}
const { item } = data
return (
<>
{item.parentId {item.parentId
? <Comment item={item} replyOpen includeParent cacheId='ROOT_QUERY' /> ? <Comment item={item} replyOpen includeParent noComments />
: ( : (
<> <>
<Item item={item}> <Item item={item}>
{item.text && <Text>{item.text}</Text>} {item.text && <Text>{item.text}</Text>}
<Reply parentId={item.id} cacheId='ROOT_QUERY' /> <Reply parentId={item.id} />
</Item> </Item>
</> </>
)} )}
<div className='mt-5'> <div className='mt-5'>
<Comments query={commentsQuery} /> <Comments comments={item.comments} />
</div> </div>
</Layout> </>
) )
} }

View File

@ -82,12 +82,12 @@ $container-max-widths: (
} }
@keyframes flash { @keyframes flash {
from { filter: brightness(1); background-position: 0 0;} from { filter: brightness(1);}
2% { filter: brightness(2.3); } 2% { filter: brightness(2.3); }
4% { filter: brightness(1.4); } 4% { filter: brightness(1.4); }
8% { filter: brightness(3); } 8% { filter: brightness(3); }
16% { filter: brightness(1); } 16% { filter: brightness(1); }
to { filter: brightness(1); background-position: 250px 0;} to { filter: brightness(1);}
} }
.clouds { .clouds {