Skip to content

Commit

Permalink
Comment Sorting and Filtering (#4682)
Browse files Browse the repository at this point in the history
* thread variables

* dummy toggle buttons

* toggle filter section

* sort by date toggle works

* unread first toggle works

* newest first, actually

* adjust header

* refactor, everything works!

* sort/filter tooltip
  • Loading branch information
lankaukk authored Jan 4, 2024
1 parent 730c6ed commit ac6ca1b
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 35 deletions.
184 changes: 150 additions & 34 deletions editor/src/components/inspector/sections/comment-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@
import { jsx } from '@emotion/react'
import '../../../../resources/editor/css/liveblocks/react-comments/styles.css'
import '../../../../resources/editor/css/liveblocks/react-comments/dark/attributes.css'
import React from 'react'
import React, { useState } from 'react'
import {
Button,
CheckboxInput,
FlexColumn,
FlexRow,
Icn,
InspectorSubsectionHeader,
Tooltip,
color,
useColorTheme,
} from '../../../uuiui'
import { stopPropagation } from '../common/inspector-utils'
import { useStorage, type ThreadMetadata } from '../../../../liveblocks.config'
import { useStorage, type ThreadMetadata, useThreads } from '../../../../liveblocks.config'
import type { ThreadData } from '@liveblocks/client'
import { useDispatch } from '../../editor/store/dispatch-context'
import { canvasRectangle, rectangleContainsRectangle } from '../../../core/shared/math-utils'
Expand All @@ -33,6 +35,7 @@ import {
useCanvasLocationOfThread,
getCollaboratorById,
useMyThreadReadStatus,
useReadThreads,
} from '../../../core/commenting/comment-hooks'
import { Substores, useEditorState, useSelectorWithCallback } from '../../editor/store/store-hook'
import { when } from '../../../utils/react-conditionals'
Expand All @@ -51,17 +54,6 @@ import { CommentRepliesCounter } from '../../canvas/controls/comment-replies-cou
export const CommentSection = React.memo(() => {
return (
<div onKeyDown={stopPropagation} onKeyUp={stopPropagation}>
<InspectorSubsectionHeader>
<FlexRow
style={{
flexGrow: 1,
gap: 8,
height: 42,
}}
>
<span>Comments</span>
</FlexRow>
</InspectorSubsectionHeader>
<MultiplayerWrapper errorFallback={null} suspenseFallback={null}>
<ThreadPreviews />
</MultiplayerWrapper>
Expand All @@ -74,8 +66,14 @@ const ThreadPreviews = React.memo(() => {
const dispatch = useDispatch()
const colorTheme = useColorTheme()

const { threads: activeThreads } = useUnresolvedThreads()
const [filtersOpen, setOpen] = React.useState(false)
const toggleOpen = React.useCallback(() => {
setOpen((prevOpen) => !prevOpen)
}, [setOpen])

const { threads: threads } = useThreads()
const { threads: resolvedThreads } = useResolvedThreads()
const { threads: readThreads } = useReadThreads()

sortThreadsByDescendingUpdateTimeInPlace(activeThreads)
sortThreadsByDescendingUpdateTimeInPlace(resolvedThreads)
Expand All @@ -93,32 +91,150 @@ const ThreadPreviews = React.memo(() => {
])
}, [showResolved, dispatch])

const [sortByDateNewestFirst, setSortByDateNewestFirst] = React.useState(true)
const [sortByUnreadFirst, setSortedByUnreadFirst] = React.useState(false)

const toggleSortByDateNewestFirst = React.useCallback(() => {
setSortByDateNewestFirst((prevValue) => !prevValue)
}, [])
const toggleSortByUnreadFirst = React.useCallback(() => {
setSortedByUnreadFirst((prevValue) => !prevValue)
}, [])

const sortedThreads = React.useMemo(() => {
let filteredThreads = threads

if (!showResolved) {
filteredThreads = filteredThreads.filter((thread) => !resolvedThreads.includes(thread))
}
if (sortByDateNewestFirst) {
filteredThreads = filteredThreads.slice().reverse()
}
if (sortByUnreadFirst) {
const unreadThreads = filteredThreads.filter((thread) => !readThreads.includes(thread))
const theReadThreads = filteredThreads.filter((thread) => readThreads.includes(thread))
filteredThreads = [...unreadThreads, ...theReadThreads]
}

return filteredThreads
}, [
threads,
resolvedThreads,
readThreads,
showResolved,
sortByUnreadFirst,
sortByDateNewestFirst,
])

return (
<FlexColumn style={{ gap: 5 }}>
{activeThreads.map((thread) => (
<ThreadPreview key={thread.id} thread={thread} />
))}
{when(
activeThreads.length === 0,
<div style={{ padding: '0px 8px', color: colorTheme.fg6.value }}>
No active comment threads.
</div>,
)}
<FlexColumn>
<FlexRow
style={{
flexGrow: 1,
justifyContent: 'space-between',
alignItems: 'center',
fontWeight: 600,
margin: 8,
}}
>
<span>Comments</span>
<Tooltip title='Sort/Filter' placement='bottom'>
<div
css={{
width: 20,
height: 20,
borderRadius: 3,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
'&:hover': {
backgroundColor: colorTheme.bg3.value,
},
}}
>
<div
css={{
height: 14,
width: 14,
borderRadius: 14,
border: `1px solid ${colorTheme.fg1.value}`,
cursor: 'pointer',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
gap: 1,
}}
onClick={toggleOpen}
>
<div
style={{ width: 8, height: 1, background: colorTheme.fg1.value, borderRadius: 2 }}
/>
<div
style={{ width: 6, height: 1, background: colorTheme.fg1.value, borderRadius: 2 }}
/>
<div
style={{ width: 4, height: 1, background: colorTheme.fg1.value, borderRadius: 2 }}
/>
</div>
</div>
</Tooltip>
</FlexRow>
{when(
resolvedThreads.length > 0,
<Button
highlight
spotlight
style={{ padding: 10, margin: '10px' }}
onClick={toggleShowResolved}
sortedThreads.length > 0,
<FlexColumn
style={{
gap: 6,
overflow: 'hidden',
padding: filtersOpen ? 8 : 0,
height: filtersOpen ? 'auto' : 0,
}}
>
{showResolved ? 'Hide' : 'Show'} resolved threads
</Button>,
<FlexRow>
<CheckboxInput
style={{ marginRight: 8 }}
id='showNewestFirst'
checked={sortByDateNewestFirst}
onChange={toggleSortByDateNewestFirst}
/>
<label htmlFor='showNewestFirst'>Newest First</label>
</FlexRow>
<FlexRow>
<CheckboxInput
style={{ marginRight: 8 }}
id='showUnreadFirst'
checked={sortByUnreadFirst}
onChange={toggleSortByUnreadFirst}
/>
<label htmlFor='showUnreadFirst'>Unread First</label>
</FlexRow>
{when(
resolvedThreads.length > 0,
<div style={{ width: '100%', background: colorTheme.bg3.value, height: 1 }} />,
)}
{when(
resolvedThreads.length > 0,
<FlexRow>
<CheckboxInput
style={{ marginRight: 8 }}
id='showResolved'
checked={showResolved}
onChange={toggleShowResolved}
/>
<label htmlFor='showResolved'>Show Resolved Comments</label>
</FlexRow>,
)}
</FlexColumn>,
)}
{when(
showResolved,
resolvedThreads.map((thread) => <ThreadPreview key={thread.id} thread={thread} />),
sortedThreads.length === 0,
<div style={{ padding: 8, color: colorTheme.fg6.value }}>
Leave comments on the canvas.
</div>,
)}
{sortedThreads.map((thread) => (
<ThreadPreview key={thread.id} thread={thread} />
))}
</FlexColumn>
)
})
Expand Down
23 changes: 22 additions & 1 deletion editor/src/core/commenting/comment-hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,28 @@ export function useUnresolvedThreads() {
const threads = useThreads()
return {
...threads,
threads: threads.threads.filter((t) => !t.metadata.resolved),
threads: threads.threads.filter((t) => t.metadata.resolved !== true),
}
}

export function useReadThreads() {
const threads = useThreads()
const self = useSelf()
const threadReadStatuses = useStorage((store) => store.userReadStatusesByThread)

const filteredThreads = threads.threads.filter((thread) => {
if (thread == null) {
return false
}
if (threadReadStatuses[thread.id] == null) {
return false
}
return threadReadStatuses[thread.id][self.id] === true
})

return {
...threads,
threads: filteredThreads,
}
}

Expand Down

0 comments on commit ac6ca1b

Please sign in to comment.