diff --git a/packages/web-ui/src/sections/Document/Sidebar/Files/index.tsx b/packages/web-ui/src/sections/Document/Sidebar/Files/index.tsx index 75119379f..d2f2b6984 100644 --- a/packages/web-ui/src/sections/Document/Sidebar/Files/index.tsx +++ b/packages/web-ui/src/sections/Document/Sidebar/Files/index.tsx @@ -31,9 +31,9 @@ type IndentType = { isLast: boolean } function IndentationBar({ indentation, - open, + hasChildren, }: { - open: boolean + hasChildren: boolean indentation: IndentType[] }) { return indentation.map((indent, index) => { @@ -45,13 +45,13 @@ function IndentationBar({
{index > 0 ? (
- {!open && showBorder ? ( + {hasChildren || !showBorder ? ( +
+ ) : (
- ) : ( -
)}
) : null} @@ -62,13 +62,14 @@ function IndentationBar({ type WrapperProps = { open: boolean + node: Node selected?: boolean onClick?: () => void children: ReactNode indentation: IndentType[] } const NodeHeaderWrapper = forwardRef(function Foo( - { open, selected = false, onClick, children, indentation }, + { node, open, selected = false, onClick, children, indentation }, ref, ) { return ( @@ -80,7 +81,10 @@ const NodeHeaderWrapper = forwardRef(function Foo( 'bg-accent': selected, })} > - + 0} + /> {children}
) @@ -133,7 +137,7 @@ function FolderHeader({ if (!open) { togglePath(node.path) } - addFolder({ parentPath: node.path }) + addFolder({ parentPath: node.path, parentId: node.id }) }, }, { @@ -163,7 +167,12 @@ function FolderHeader({ ], ) return ( - +
({ openPaths: state.openPaths, togglePath: state.togglePath, @@ -341,14 +352,16 @@ function FileNode({ hidden: !open && !node.isRoot, })} > - {[...tmpNodes, ...node.children].map((node, idx) => ( -
  • - -
  • - ))} + {allNodes.map((node, idx) => { + return ( +
  • + +
  • + ) + })} )}
    diff --git a/packages/web-ui/src/sections/Document/Sidebar/Files/useNodeValidator/index.ts b/packages/web-ui/src/sections/Document/Sidebar/Files/useNodeValidator/index.ts index 53223eb2a..590b21af4 100644 --- a/packages/web-ui/src/sections/Document/Sidebar/Files/useNodeValidator/index.ts +++ b/packages/web-ui/src/sections/Document/Sidebar/Files/useNodeValidator/index.ts @@ -78,6 +78,7 @@ export function useNodeValidator({ const onClickOutside = useCallback(async () => { const val = inputRef.current?.value ?? '' const value = val.trim() + setValue(value) if (!value) { leaveWithoutSave?.() @@ -117,6 +118,6 @@ export function useNodeValidator({ onInputChange, onInputKeyDown, error: validationError, - keepFocused: !value.trim(), + keepFocused: !value, } } diff --git a/packages/web-ui/src/sections/Document/Sidebar/Files/useTempNodes/index.test.ts b/packages/web-ui/src/sections/Document/Sidebar/Files/useTempNodes/index.test.ts index ea609dd54..f0a9c4b0f 100644 --- a/packages/web-ui/src/sections/Document/Sidebar/Files/useTempNodes/index.test.ts +++ b/packages/web-ui/src/sections/Document/Sidebar/Files/useTempNodes/index.test.ts @@ -11,7 +11,12 @@ describe('useTempNodes', () => { it('should add a folder', async () => { const { result } = renderHook(() => useTempNodes((state) => state)) - act(() => result.current.addFolder({ parentPath: 'some-folder' })) + act(() => + result.current.addFolder({ + parentPath: 'some-folder', + parentId: 'fake-id', + }), + ) expect(result.current.tmpFolders).toEqual({ 'some-folder': [ @@ -27,7 +32,12 @@ describe('useTempNodes', () => { it('should update a folder', async () => { const { result } = renderHook(() => useTempNodes((state) => state)) - act(() => result.current.addFolder({ parentPath: 'some-folder' })) + act(() => + result.current.addFolder({ + parentPath: 'some-folder', + parentId: 'fake-id', + }), + ) const id = result?.current?.tmpFolders?.['some-folder']?.[0]?.id act(() => result.current.updateFolder({ id: id!, path: 'new-name' })) @@ -45,10 +55,62 @@ describe('useTempNodes', () => { it('should delete a folder', async () => { const { result } = renderHook(() => useTempNodes((state) => state)) - act(() => result.current.addFolder({ parentPath: 'some-folder' })) + act(() => + result.current.addFolder({ + parentPath: 'some-folder', + parentId: 'fake-id', + }), + ) const id = result?.current?.tmpFolders?.['some-folder']?.[0]?.id act(() => result.current.deleteTmpFolder({ id: id! })) expect(result.current.tmpFolders).toEqual({ 'some-folder': [] }) }) + + it.only('should update the parent folder children', async () => { + const { result } = renderHook(() => useTempNodes((state) => state)) + act(() => + result.current.addFolder({ + parentPath: 'some-folder', + parentId: 'fake-id', + }), + ) + const id = result?.current?.tmpFolders?.['some-folder']?.[0]?.id + act(() => + result.current.updateFolder({ id: id!, path: 'parent-tmp-folder' }), + ) + + act(() => + result.current.addFolder({ + parentPath: 'some-folder/parent-tmp-folder', + parentId: id!, + }), + ) + + const childId = + result?.current?.tmpFolders?.['some-folder']?.[0]?.children?.[0]?.id + + act(() => + result.current.updateFolder({ id: childId!, path: 'child-tmp-folder' }), + ) + + expect(result.current.tmpFolders).toEqual({ + 'some-folder': [ + new Node({ + id: expect.any(String), + path: 'some-folder/parent-tmp-folder', + name: 'parent-tmp-folder', + isPersisted: false, + children: [ + new Node({ + id: expect.any(String), + path: 'some-folder/parent-tmp-folder/child-tmp-folder', + name: 'child-tmp-folder', + isPersisted: false, + }), + ], + }), + ], + }) + }) }) diff --git a/packages/web-ui/src/sections/Document/Sidebar/Files/useTempNodes/index.ts b/packages/web-ui/src/sections/Document/Sidebar/Files/useTempNodes/index.ts index a9d0b70d7..2fe178bcc 100644 --- a/packages/web-ui/src/sections/Document/Sidebar/Files/useTempNodes/index.ts +++ b/packages/web-ui/src/sections/Document/Sidebar/Files/useTempNodes/index.ts @@ -4,14 +4,30 @@ import { defaultGenerateNodeUuid, Node } from '../useTree' type OpenPathsState = { tmpFolders: Record - addFolder: (args: { parentPath: string }) => void + addFolder: (args: { parentPath: string; parentId: string }) => void updateFolder: (args: { id: string; path: string }) => void deleteTmpFolder: (args: { id: string }) => void reset: () => void } +function nodeAndChildren(node: Node): Node[] { + return [node, ...node.children.flatMap(nodeAndChildren)] +} + function allTmpNodes(tmpFolders: OpenPathsState['tmpFolders']) { - return Object.values(tmpFolders).flatMap((nodes) => nodes) + return Object.values(tmpFolders) + .flatMap((nodes) => nodes.map(nodeAndChildren)) + .flat() +} + +function createEmptyNode({ parentPath }: { parentPath: string }) { + const emptyName = ' ' + return new Node({ + id: defaultGenerateNodeUuid(), + name: emptyName, + path: `${parentPath}/${emptyName}`, + isPersisted: false, + }) } export const useTempNodes = create((set) => ({ @@ -21,16 +37,37 @@ export const useTempNodes = create((set) => ({ tmpFolders: {}, }) }, - addFolder: ({ parentPath }) => { + addFolder: ({ parentPath, parentId }) => { set((state) => { - const prevNodes = state.tmpFolders[parentPath] - const emptyName = ' ' - const node = new Node({ - id: defaultGenerateNodeUuid(), - name: emptyName, - path: `${parentPath}/${emptyName}`, - isPersisted: false, + const allNodes = allTmpNodes(state.tmpFolders) + const parentNode = allNodes.find((node) => node.id === parentId) + + const node = createEmptyNode({ + parentPath: parentNode ? parentNode.path : parentPath, }) + + if (parentNode) { + parentNode.children = [node, ...parentNode.children] + const grandParentPath = parentNode.path + .split('/') + .slice(0, -1) + .join('/') + const grandParent = state.tmpFolders[grandParentPath] + if (!grandParent) return state + const idx = grandParent.findIndex((n) => n.id === parentId) + return { + tmpFolders: { + ...state.tmpFolders, + [grandParentPath]: [ + ...grandParent.slice(0, idx), + parentNode, + ...grandParent.slice(idx + 1), + ], + }, + } + } + + const prevNodes = state.tmpFolders[parentPath] return { tmpFolders: { ...state.tmpFolders, @@ -46,26 +83,11 @@ export const useTempNodes = create((set) => ({ if (!node) return state const parentPath = node.path.split('/').slice(0, -1).join('/') - const parent = state.tmpFolders[parentPath] - if (!parent) return state - - const idx = parent.findIndex((n) => n.id === id) - const existingNode = parent[idx] - if (!existingNode) return state + node.name = path + node.path = `${parentPath}/${path}` - existingNode.name = path - existingNode.path = `${parentPath}/${path}` - return { - tmpFolders: { - ...state.tmpFolders, - [parentPath]: [ - ...parent.slice(0, idx), - existingNode, - ...parent.slice(idx + 1), - ], - }, - } + return state }) }, deleteTmpFolder: ({ id }) => {