chapter re-ordering

This commit is contained in:
2026-02-21 10:42:01 +10:00
parent 740faae8c8
commit ca49a407da
8 changed files with 162 additions and 9 deletions

View File

@@ -2,14 +2,21 @@ import { useState } from 'react'
import { useEditorStore } from '../../store/editorStore'
import type { FileNode } from '../../types/editor'
interface DndPayload {
name: string
dirPath: string
}
interface Props {
node: FileNode
depth: number
dirPath: string
siblings: FileNode[]
}
export function FileTreeNode({ node, depth }: Props): JSX.Element {
export function FileTreeNode({ node, depth, dirPath, siblings }: Props): JSX.Element {
const [expanded, setExpanded] = useState(true)
const { activeFilePath, setActiveFile } = useEditorStore()
const { activeFilePath, setActiveFile, moveNode } = useEditorStore()
const openFile = async (): Promise<void> => {
if (node.type === 'file') {
@@ -18,6 +25,40 @@ export function FileTreeNode({ node, depth }: Props): JSX.Element {
}
}
function handleDragStart(e: React.DragEvent<HTMLElement>): void {
e.dataTransfer.effectAllowed = 'move'
e.dataTransfer.setData('dnd', JSON.stringify({ name: node.name, dirPath } satisfies DndPayload))
e.currentTarget.classList.add('dragging')
}
function handleDragEnd(e: React.DragEvent<HTMLElement>): void {
e.currentTarget.classList.remove('dragging')
}
function handleDragOver(e: React.DragEvent<HTMLElement>): void {
e.preventDefault()
e.dataTransfer.dropEffect = 'move'
e.currentTarget.classList.add('drag-over')
}
function handleDragLeave(e: React.DragEvent<HTMLElement>): void {
e.currentTarget.classList.remove('drag-over')
}
function handleDrop(e: React.DragEvent<HTMLElement>): void {
e.preventDefault()
e.currentTarget.classList.remove('drag-over')
const raw = e.dataTransfer.getData('dnd')
if (!raw) return
const payload: DndPayload = JSON.parse(raw)
if (payload.dirPath !== dirPath) return
const fromIdx = siblings.findIndex((n) => n.name === payload.name)
const toIdx = siblings.findIndex((n) => n.name === node.name)
if (fromIdx !== -1 && toIdx !== -1 && fromIdx !== toIdx) {
moveNode(dirPath, fromIdx, toIdx)
}
}
if (node.type === 'directory') {
return (
<div className="tree-dir">
@@ -25,12 +66,24 @@ export function FileTreeNode({ node, depth }: Props): JSX.Element {
className="tree-dir-header"
style={{ paddingLeft: `${depth * 12 + 8}px` }}
onClick={() => setExpanded(!expanded)}
draggable={true}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
>
<span className="tree-arrow">{expanded ? '▾' : '▸'}</span>
{node.name}
</button>
{expanded && node.children?.map((child) => (
<FileTreeNode key={child.path} node={child} depth={depth + 1} />
<FileTreeNode
key={child.path}
node={child}
depth={depth + 1}
dirPath={node.path}
siblings={node.children!}
/>
))}
</div>
)
@@ -44,6 +97,12 @@ export function FileTreeNode({ node, depth }: Props): JSX.Element {
style={{ paddingLeft: `${depth * 12 + 20}px` }}
onClick={openFile}
title={node.name}
draggable={true}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
>
{node.name}
</button>