Skip to content

Commit

Permalink
Fix open folders in sidebar (#55)
Browse files Browse the repository at this point in the history
The old approach did not work because navigating to a new document
would re-render the tree. We now try to hold that state in Zustand so it
can be persisted across navigations
  • Loading branch information
andresgutgon authored Jul 23, 2024
1 parent bc46713 commit ba7494a
Show file tree
Hide file tree
Showing 12 changed files with 244 additions and 128 deletions.
11 changes: 11 additions & 0 deletions apps/web/src/app/(private)/_data-access/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { cache } from 'react'

import {
getDocumentAtCommit,
findCommitByUuid as originalfindCommit,
findProject as originalFindProject,
getFirstProject as originalGetFirstProject,
type FindCommitByUuidProps,
type FindProjectProps,
type GetDocumentAtCommitProps,
} from '@latitude-data/core'

export const getFirstProject = cache(
Expand Down Expand Up @@ -34,3 +36,12 @@ export const findCommit = cache(
return commit
},
)

export const getDocumentByUuid = cache(
async ({ documentUuid, commitId }: GetDocumentAtCommitProps) => {
const result = await getDocumentAtCommit({ documentUuid, commitId })
const document = result.unwrap()

return document
},
)
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ import { useRouter } from 'next/navigation'

export default function ClientFilesTree({
documents,
documentUuid,
documentPath,
}: {
documents: SidebarDocument[]
documentPath: string | undefined
documentUuid: string | undefined
}) {
const router = useRouter()
Expand All @@ -35,7 +36,7 @@ export default function ClientFilesTree({
return (
<FilesTree
documents={documents}
currentDocumentUuid={documentUuid}
currentPath={documentPath}
navigateToDocument={navigateToDocument}
/>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,18 @@ import ClientFilesTree from './ClientFilesTree'
export default async function Sidebar({
commit,
documentUuid,
documentPath,
}: {
commit: Commit
documentPath?: string
documentUuid?: string
}) {
const documents = await getDocumentsAtCommit({ commitId: commit.id })
return (
<Suspense fallback={<div>Loading...</div>}>
<DocumentSidebar>
<ClientFilesTree
documentPath={documentPath}
documents={documents.unwrap()}
documentUuid={documentUuid}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react'

import { DocumentDetailWrapper } from '@latitude-data/web-ui'
import { findCommit } from '$/app/(private)/_data-access'
import { findCommit, getDocumentByUuid } from '$/app/(private)/_data-access'

import Sidebar from '../../_components/Sidebar'

Expand All @@ -16,9 +16,17 @@ export default async function DocumentLayout({
projectId: Number(params.projectId),
uuid: params.commitUuid,
})
const document = await getDocumentByUuid({
documentUuid: params.documentUuid,
commitId: commit.id,
})
return (
<DocumentDetailWrapper>
<Sidebar commit={commit} documentUuid={params.documentUuid} />
<Sidebar
commit={commit}
documentUuid={params.documentUuid}
documentPath={document.path}
/>
<div className='p-32'>{children}</div>
</DocumentDetailWrapper>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,34 @@ export async function getDocumentsAtCommit(
return Result.ok(totalDocuments)
}

export type GetDocumentAtCommitProps = {
commitId: number
documentUuid: string
}
export async function getDocumentAtCommit(
{ commitId, documentUuid }: GetDocumentAtCommitProps,
tx = database,
): Promise<TypedResult<DocumentVersion, LatitudeError>> {
const documentInCommit = await tx.query.documentVersions.findFirst({
where: and(
eq(documentVersions.commitId, commitId),
eq(documentVersions.documentUuid, documentUuid),
),
})
if (documentInCommit !== undefined) return Result.ok(documentInCommit)

const documentsAtCommit = await getDocumentsAtCommit({ commitId }, tx)
if (documentsAtCommit.error) return Result.error(documentsAtCommit.error)

const document = documentsAtCommit.value.find(
(d) => d.documentUuid === documentUuid,
)

if (!document) return Result.error(new LatitudeError('Document not found'))

return Result.ok(document)
}

export async function listCommitChanges(
{ commitId }: { commitId: number },
tx = database,
Expand Down
3 changes: 2 additions & 1 deletion packages/web-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@
"react-dom": "18.3.0",
"tailwind-merge": "^2.4.0",
"tailwindcss-animate": "^1.0.7",
"zod": "^3.23.8"
"zod": "^3.23.8",
"zustand": "^4.5.4"
},
"devDependencies": {
"@latitude-data/eslint-config": "workspace:*",
Expand Down
78 changes: 55 additions & 23 deletions packages/web-ui/src/sections/Document/Sidebar/Files/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
'use client'

import { ReactNode, useCallback, useState } from 'react'
import { ReactNode, useCallback, useEffect, useState } from 'react'

import { Icons } from '$ui/ds/atoms/Icons'
import Text from '$ui/ds/atoms/Text'
import { ReactStateDispatch } from '$ui/lib/commonTypes'
import { cn } from '$ui/lib/utils'

import { useOpenPaths } from './useOpenPaths'
import { Node, SidebarDocument, useTree } from './useTree'

const ICON_CLASS = 'w-6 h-6 text-muted-foreground'
Expand Down Expand Up @@ -45,20 +45,20 @@ function IndentationBar({

function NodeHeaderWrapper({
open,
node,
selected = false,
children,
indentation,
}: {
open: boolean
selected?: boolean
children: ReactNode
node: Node
indentation: IndentType[]
}) {
return (
<div
className={cn('flex flex-row my-0.5 cursor-pointer', {
'hover:bg-muted': !node.selected,
'bg-accent': node.selected,
'hover:bg-muted': !selected,
'bg-accent': selected,
})}
>
<IndentationBar indentation={indentation} open={open} />
Expand All @@ -70,21 +70,23 @@ function NodeHeaderWrapper({
function FolderHeader({
node,
open,
onClick,
indentation,
}: {
isLast: boolean
node: Node
open: boolean
onClick: ReactStateDispatch<boolean>
indentation: IndentType[]
}) {
const togglePath = useOpenPaths((state) => state.togglePath)
const FolderIcon = open ? Icons.folderOpen : Icons.folderClose
const ChevronIcon = open ? Icons.chevronDown : Icons.chevronRight
const onTooglePath = useCallback(() => {
togglePath(node.path)
}, [togglePath, node.path])
return (
<NodeHeaderWrapper open={open} node={node} indentation={indentation}>
<NodeHeaderWrapper open={open} indentation={indentation}>
<div
onClick={() => onClick(!open)}
onClick={onTooglePath}
className='flex flex-row items-center gap-x-1'
>
<div className='w-6 flex justify-center'>
Expand All @@ -99,32 +101,40 @@ function FolderHeader({

function FileHeader({
open,
selected,
node,
indentation,
navigateToDocument,
}: {
open: boolean
selected: boolean
node: Node
indentation: IndentType[]
navigateToDocument: (documentUuid: string) => void
}) {
const handleClick = useCallback(() => {
if (selected) return

navigateToDocument(node.doc!.documentUuid)
}, [node.doc])
}, [node.doc!.documentUuid, selected])
return (
<NodeHeaderWrapper open={open} node={node} indentation={indentation}>
<NodeHeaderWrapper
open={open}
selected={selected}
indentation={indentation}
>
<div
className='flex flex-row items-center gap-x-1 py-0.5'
onClick={handleClick}
>
<Icons.file
className={cn(ICON_CLASS, {
'text-accent-foreground': node.selected,
'text-accent-foreground': selected,
})}
/>
<Text.H5M
userSelect={false}
color={node.selected ? 'accentForeground' : 'foreground'}
color={selected ? 'accentForeground' : 'foreground'}
>
{node.name}
</Text.H5M>
Expand All @@ -135,16 +145,16 @@ function FileHeader({

function NodeHeader({
isLast,
selected,
node,
open,
onClick,
indentation,
navigateToDocument,
}: {
isLast: boolean
selected: boolean
node: Node
open: boolean
onClick: ReactStateDispatch<boolean>
indentation: IndentType[]
navigateToDocument: (documentUuid: string) => void
}) {
Expand All @@ -153,6 +163,7 @@ function NodeHeader({
return (
<FileHeader
open={open}
selected={selected}
node={node}
indentation={indentation}
navigateToDocument={navigateToDocument}
Expand All @@ -165,7 +176,6 @@ function NodeHeader({
isLast={isLast}
node={node}
open={open}
onClick={onClick}
indentation={indentation}
/>
)
Expand All @@ -174,24 +184,31 @@ function NodeHeader({
function FileNode({
isLast = false,
node,
currentPath,
indentation = [],
navigateToDocument,
}: {
node: Node
currentPath: string | undefined
isLast?: boolean
indentation?: IndentType[]
navigateToDocument: (documentUuid: string) => void
}) {
const [open, setOpen] = useState(node.containsSelected)
const [selected, setSelected] = useState(currentPath === node.path)
const openPaths = useOpenPaths((state) => state.openPaths)
const open = node.isRoot || openPaths.includes(node.path)
const lastIdx = node.children.length - 1
useEffect(() => {
setSelected(currentPath === node.path)
}, [currentPath])
return (
<div className='w-full'>
<NodeHeader
isLast={isLast}
indentation={indentation}
node={node}
selected={selected}
open={open}
onClick={setOpen}
navigateToDocument={navigateToDocument}
/>

Expand All @@ -204,6 +221,7 @@ function FileNode({
{node.children.map((node, idx) => (
<li key={node.id}>
<FileNode
currentPath={currentPath}
indentation={[...indentation, { isLast: idx === lastIdx }]}
node={node}
isLast={idx === lastIdx}
Expand All @@ -218,14 +236,28 @@ function FileNode({
}

export function FilesTree({
currentPath,
documents,
currentDocumentUuid,
navigateToDocument,
}: {
documents: SidebarDocument[]
currentDocumentUuid: string | undefined
currentPath: string | undefined
navigateToDocument: (documentUuid: string) => void
}) {
const rootNode = useTree({ documents, currentDocumentUuid })
return <FileNode node={rootNode} navigateToDocument={navigateToDocument} />
const togglePath = useOpenPaths((state) => state.togglePath)
const rootNode = useTree({ documents })

useEffect(() => {
if (currentPath) {
togglePath(currentPath)
}
}, [currentPath, togglePath])

return (
<FileNode
currentPath={currentPath}
node={rootNode}
navigateToDocument={navigateToDocument}
/>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { act, renderHook } from '@testing-library/react'
import { describe, expect, it } from 'vitest'

import { useOpenPaths } from './index'

describe('useOpenPaths', () => {
it('shout add the paths', async () => {
const { result } = renderHook(() => useOpenPaths((state) => state))
act(() => {
result.current.togglePath('some-folder/nested-folder/doc1')
})

expect(result.current.openPaths).toEqual([
'',
'some-folder',
'some-folder/nested-folder',
'some-folder/nested-folder/doc1',
])
})

it('shout remove nested paths', async () => {
const { result } = renderHook(() => useOpenPaths((state) => state))
act(() => {
result.current.togglePath('some-folder/nested-folder/doc1')
})

act(() => {
result.current.togglePath('some-folder/nested-folder')
})
expect(result.current.openPaths).toEqual(['', 'some-folder'])
})
})
Loading

0 comments on commit ba7494a

Please sign in to comment.