Skip to content

Commit

Permalink
beta: allow expanding card children's comments
Browse files Browse the repository at this point in the history
  • Loading branch information
neongreen committed Sep 7, 2024
1 parent 53feaef commit e1e8f34
Show file tree
Hide file tree
Showing 13 changed files with 245 additions and 43 deletions.
100 changes: 89 additions & 11 deletions components/card/cardsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import * as Dnd from '@dnd-kit/core'
import * as DndSort from '@dnd-kit/sortable'
import * as DndModifiers from '@dnd-kit/modifiers'
import type * as GQL from 'generated/graphql/graphql'
import { CardsListItem } from './cardsListItem'
import { CardsListItemCollapsed, CardsListItemExpanded } from './cardsListItem'
import { CSS } from '@dnd-kit/utilities'
import { deleteSync, insertPosition } from 'lib/array'
import { graphql } from 'generated/graphql'
import { useApolloClient, useMutation } from '@apollo/client'
import { useApolloClient, useMutation, useQuery } from '@apollo/client'
import { evictCardChildren } from '@lib/graphql/cache'
import { reorderCardChildren } from '@lib/reorderCardChildren'
import _ from 'lodash'
import { Query } from '@components/query'

const useReorderChild = () => {
const cache = useApolloClient().cache
Expand Down Expand Up @@ -46,10 +47,58 @@ const useReorderChild = () => {
}
}

// A list of cards, supporting drag-and-drop
const useGetComments = (variables: { cardId: string }) => {
return useQuery(
graphql(`
query getComments($cardId: UUID!) {
card(id: $cardId) {
id
comments {
id
content
createdAt
visibility
pinned
canEdit
replies {
id
content
visibility
canEdit
createdAt
canDelete
author {
id
displayName
userpicUrl
}
}
}
}
}
`),
{ variables }
)
}

/**
* A list of cards, supporting drag-and-drop
*/
export function CardsList(props: {
parentId: GQL.Card['id']
cards: Pick<GQL.Card, 'id' | 'title' | 'tagline' | 'visibility' | 'commentCount' | 'firedAt'>[]
cards: Pick<
GQL.Card,
| 'id'
| 'title'
| 'tagline'
| 'visibility'
| 'commentCount'
| 'firedAt'
| 'canEdit'
| 'reverseOrder'
>[]
/** Whether to show the cards as expanded or collapsed */
expandCards: boolean
allowEdit: boolean
}) {
const sensors = Dnd.useSensors(
Expand Down Expand Up @@ -95,7 +144,7 @@ export function CardsList(props: {
}
}

if (props.allowEdit) {
if (props.allowEdit && !props.expandCards) {
return (
<Dnd.DndContext
sensors={sensors}
Expand All @@ -106,22 +155,51 @@ export function CardsList(props: {
>
<DndSort.SortableContext items={props.cards} strategy={DndSort.verticalListSortingStrategy}>
{props.cards.map((card) => (
<Sortable key={card.id} card={card} />
<SortableCardsListItem key={card.id} card={card} />
))}
</DndSort.SortableContext>
</Dnd.DndContext>
)
} else {
return (
<>
{props.cards.map((card) => (
<CardsListItem key={card.id} card={card} />
))}
{props.cards.map((card) =>
props.expandCards ? (
<SubcardWithFetchingComments key={card.id} card={card} />
) : (
<CardsListItemCollapsed key={card.id} card={card} />
)
)}
</>
)
}
}

function SubcardWithFetchingComments(props: {
card: Pick<
GQL.Card,
| 'id'
| 'title'
| 'tagline'
| 'visibility'
| 'commentCount'
| 'firedAt'
| 'canEdit'
| 'reverseOrder'
>
}) {
const commentsQuery = useGetComments({ cardId: props.card.id })
return (
<Query queries={{ commentsQuery }}>
{({
commentsQuery: {
card: { comments },
},
}) => <CardsListItemExpanded card={props.card} comments={comments} />}
</Query>
)
}

// https://github.com/clauderic/dnd-kit/discussions/108
function animateLayoutChanges(args) {
const { isSorting, wasSorting } = args
Expand All @@ -131,7 +209,7 @@ function animateLayoutChanges(args) {
return true
}

function Sortable(props: {
function SortableCardsListItem(props: {
card: Pick<GQL.Card, 'id' | 'title' | 'tagline' | 'visibility' | 'commentCount' | 'firedAt'>
}) {
const { attributes, listeners, setNodeRef, transform, transition, isDragging } =
Expand All @@ -149,7 +227,7 @@ function Sortable(props: {

return (
<div ref={setNodeRef} style={style} {...attributes} {...listeners}>
<CardsListItem card={props.card} dragged={isDragging} />
<CardsListItemCollapsed card={props.card} dragged={isDragging} />
</div>
)
}
60 changes: 58 additions & 2 deletions components/card/cardsListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { HiFire } from 'react-icons/hi'
import { useMutation } from '@apollo/client'
import { graphql } from 'generated/graphql'
import { useCurrentUser } from '@components/currentUserContext'
import { Comments } from './comments'

const useFireCard = () => {
const [action, result] = useMutation(
Expand All @@ -31,8 +32,8 @@ const useFireCard = () => {
/**
* A card in the children list.
*/
export function CardsListItem(props: {
// Whether the card is being dragged in a list
export function CardsListItemCollapsed(props: {
/** Whether the card is being dragged in a list */
dragged?: boolean
card: Pick<GQL.Card, 'id' | 'title' | 'tagline' | 'visibility' | 'commentCount' | 'firedAt'>
}) {
Expand Down Expand Up @@ -79,3 +80,58 @@ export function CardsListItem(props: {
</div>
)
}

/**
* A card in the children list, expanded to show all comments and to allow adding new comments.
*/
export function CardsListItemExpanded(props: {
card: Pick<
GQL.Card,
| 'id'
| 'title'
| 'tagline'
| 'visibility'
| 'commentCount'
| 'firedAt'
| 'canEdit'
| 'reverseOrder'
>
comments: React.ComponentProps<typeof Comments>['comments']
}) {
const currentUser = useCurrentUser()
const { card } = props
const isPrivate = card.visibility === GQL.Visibility.Private
const fireCardMutation = useFireCard()
const recentlyFired =
card.firedAt && new Date(card.firedAt).getTime() > Date.now() - 1000 * 60 * 60 * 24
return (
// NB: .position-relative is needed for .stretched-link to work properly
<div className={`${styles.cardsListItem} woc-card `}>
<div className={styles._counter}>{card.commentCount || '−'}</div>
<div className={styles._body}>
<div className="position-relative">
<div className={styles._title}>
{/* TODO: perhaps move lock+title into a separate div so that the lock icon is "inline" */}
<span>{isPrivate ? '🔒 ' : ''}</span>
<Link href={cardRoute(card.id)} className="stretched-link">
{card.title}
</Link>
{currentUser?.betaAccess && (
<B.Button
variant="outline-warning"
size="sm"
className={`${styles._fire} ${!recentlyFired ? styles._notRecentlyFired : ''}`}
onClick={async () => fireCardMutation.do({ variables: { id: card.id } })}
// TODO spinner?
>
<HiFire />
</B.Button>
)}
</div>
{card.tagline && <div className={styles._tagline}>{card.tagline}</div>}
</div>
<Comments card={props.card} comments={props.comments} />
</div>
</div>
)
}
14 changes: 11 additions & 3 deletions components/card/subcards.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type * as GQL from 'generated/graphql/graphql'
import { sortByIdOrder } from '@lib/array'
import { cardSettings } from '@lib/model-settings'
import { LinkButton } from 'components/linkButton'
import _ from 'lodash'
import * as React from 'react'
Expand All @@ -10,10 +9,18 @@ import { BiArchive } from 'react-icons/bi'
import styles from './shared.module.scss'

export function Subcards(props: {
parent: Pick<GQL.Card, 'id' | 'childrenOrder' | 'canEdit'>
parent: Pick<GQL.Card, 'id' | 'childrenOrder' | 'canEdit' | 'expandChildren'>
cards: Pick<
GQL.Card,
'id' | 'title' | 'tagline' | 'archived' | 'visibility' | 'commentCount' | 'firedAt'
| 'id'
| 'title'
| 'tagline'
| 'archived'
| 'visibility'
| 'commentCount'
| 'firedAt'
| 'canEdit'
| 'reverseOrder'
>[]
}) {
const { parent, cards } = props
Expand Down Expand Up @@ -46,6 +53,7 @@ export function Subcards(props: {
parentId={parent.id}
cards={showArchived ? archivedCards : normalCards}
allowEdit={parent.canEdit}
expandCards={parent.expandChildren}
/>
</div>
</div>
Expand Down
34 changes: 33 additions & 1 deletion components/editCardModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import Link from 'next/link'
import { accountRoute } from 'lib/routes'
import { graphql } from 'generated/graphql'
import { useMutation } from '@apollo/client'
import { useCurrentUser } from './currentUserContext'

const _updateCard_EditCardModal = graphql(`
mutation updateCard_EditCardModal(
$id: UUID!
$title: String
$tagline: String
$reverseOrder: Boolean
$expandChildren: Boolean
$beeminderGoal: String
) {
updateCard(
Expand All @@ -21,6 +23,7 @@ const _updateCard_EditCardModal = graphql(`
title: $title
tagline: $tagline
reverseOrder: $reverseOrder
expandChildren: $expandChildren
beeminderGoal: $beeminderGoal
}
) {
Expand All @@ -29,14 +32,18 @@ const _updateCard_EditCardModal = graphql(`
title
tagline
reverseOrder
expandChildren
beeminderGoal
}
}
}
`)

export function EditCardModal(props: {
card: Pick<GQL.Card, 'id' | 'title' | 'tagline' | 'reverseOrder' | 'beeminderGoal'>
card: Pick<
GQL.Card,
'id' | 'title' | 'tagline' | 'reverseOrder' | 'beeminderGoal' | 'expandChildren'
>
show: boolean
onHide: () => void
afterSave?: () => void
Expand All @@ -45,6 +52,7 @@ export function EditCardModal(props: {
// See https://github.com/react-bootstrap/react-bootstrap/issues/5102
const titleInputRef: React.RefObject<HTMLInputElement> = React.useRef(null)
const [updateCard, updateCardMutation] = useMutation(_updateCard_EditCardModal)
const currentUser = useCurrentUser()
const { card } = props

const formId = React.useId()
Expand All @@ -67,6 +75,7 @@ export function EditCardModal(props: {
title: card.title,
tagline: card.tagline,
reverseOrder: card.reverseOrder,
expandChildren: card.expandChildren,
beeminderGoal: card.beeminderGoal || '',
}}
onSubmit={async (values, formik) => {
Expand Down Expand Up @@ -121,6 +130,29 @@ export function EditCardModal(props: {
/>
</B.Form.Group>

{currentUser?.betaAccess && (
<B.Form.Check
name="expandChildren"
id={`expandChildren-${formId}`}
className="mb-3"
>
<B.Form.Check.Input
name="expandChildren"
id={`expandChildren-${formId}`}
checked={formik.values.expandChildren}
onChange={formik.handleChange}
type="checkbox"
/>
<B.Form.Check.Label>
Show child cards{"'"} comments
<br />
<span className="text-muted small">
Good if you want to see everything at once.
</span>
</B.Form.Check.Label>
</B.Form.Check>
)}

<B.Form.Check name="reverseOrder" id={`reverse-${formId}`} className="mb-3">
<B.Form.Check.Input
name="reverseOrder"
Expand Down
Loading

0 comments on commit e1e8f34

Please sign in to comment.