0.1.11 ai修复与全屏
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { ChevronRight, FileText, Folder, Paperclip, Plus } from "lucide-react";
|
||||
import { useMemo, useState } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { FileTreeRow } from "@/lib/file-tree/types";
|
||||
import { getFileTreeRowLabel } from "@/lib/file-tree/types";
|
||||
@@ -38,6 +39,29 @@ export function FileTree({
|
||||
onDropFiles,
|
||||
onInternalDrop,
|
||||
}: FileTreeProps) {
|
||||
const [dragOverRowId, setDragOverRowId] = useState<string | null>(null);
|
||||
|
||||
const dragOverRange = useMemo(() => {
|
||||
if (!dragOverRowId) return null;
|
||||
const startIndex = rows.findIndex((row) => row.rowId === dragOverRowId);
|
||||
if (startIndex < 0) return null;
|
||||
|
||||
const target = rows[startIndex];
|
||||
const targetDepth = target.depth;
|
||||
|
||||
// VS Code 的树在拖拽悬停到“展开的文件夹”时,会把该节点的可渲染范围都
|
||||
// 标记为 drop feedback(包含它的所有可见子节点)。我们用“扁平化 rows +
|
||||
// depth”来近似计算该范围。
|
||||
let endIndex = startIndex + 1;
|
||||
if (target.kind === "doc" && target.isExpanded && target.hasChildren) {
|
||||
while (endIndex < rows.length && rows[endIndex].depth > targetDepth) {
|
||||
endIndex += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return { startIndex, endIndex };
|
||||
}, [dragOverRowId, rows]);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return <div className="px-4 py-6 text-sm text-gray-400">暂无页面,点击下方按钮创建</div>;
|
||||
}
|
||||
@@ -47,6 +71,12 @@ export function FileTree({
|
||||
className="w-full min-w-0 max-w-full space-y-0.5 overflow-x-hidden"
|
||||
onDragOver={(event) => {
|
||||
if (!onDropFiles) return;
|
||||
if (event.target === event.currentTarget) {
|
||||
setDragOverRowId(null);
|
||||
}
|
||||
const types = Array.from(event.dataTransfer.types ?? []);
|
||||
const hasFiles = types.includes("Files") || Boolean(event.dataTransfer.files?.length);
|
||||
if (!hasFiles) return;
|
||||
event.preventDefault();
|
||||
if (event.dataTransfer.files?.length) {
|
||||
event.dataTransfer.dropEffect = "copy";
|
||||
@@ -55,6 +85,7 @@ export function FileTree({
|
||||
onDrop={(event) => {
|
||||
if (onDropFiles && event.dataTransfer.files?.length) {
|
||||
event.preventDefault();
|
||||
setDragOverRowId(null);
|
||||
const files = event.dataTransfer.files;
|
||||
const activeDocRow = rows.find(
|
||||
(row) => row.kind === "doc" && row.docId === activeId,
|
||||
@@ -64,18 +95,27 @@ export function FileTree({
|
||||
onDropFiles(targetDocId, files);
|
||||
}
|
||||
}}
|
||||
onDragLeave={(event) => {
|
||||
const relatedTarget = (event as unknown as { relatedTarget?: EventTarget | null }).relatedTarget;
|
||||
if (relatedTarget && event.currentTarget.contains(relatedTarget as Node)) return;
|
||||
setDragOverRowId(null);
|
||||
}}
|
||||
onMouseDown={(event) => {
|
||||
if (event.target === event.currentTarget) {
|
||||
onBlankMouseDown?.(event);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{rows.map((row) => {
|
||||
{rows.map((row, index) => {
|
||||
const label = getFileTreeRowLabel(row);
|
||||
const selected = selectedRowIds.has(row.rowId);
|
||||
const active = row.kind !== "asset" && row.docId === activeId;
|
||||
const active = row.kind !== "asset" && row.docId === activeId;
|
||||
const draggable =
|
||||
row.kind === "doc" || (row.kind === "asset" && isRealFileAsset(row.asset));
|
||||
const inDropFeedback =
|
||||
dragOverRange &&
|
||||
index >= dragOverRange.startIndex &&
|
||||
index < dragOverRange.endIndex;
|
||||
const baseClass =
|
||||
"flex w-full min-w-0 max-w-full select-none items-center gap-2 rounded-md px-2 py-1.5 text-sm text-gray-700 hover:bg-[#f5f7fb]";
|
||||
const activeClass =
|
||||
@@ -90,7 +130,12 @@ export function FileTree({
|
||||
return (
|
||||
<div
|
||||
key={row.rowId}
|
||||
className={cn(baseClass, active && activeClass, selected && "bg-[#e8f2ff] text-[#2563eb]")}
|
||||
className={cn(
|
||||
baseClass,
|
||||
active && activeClass,
|
||||
selected && "bg-[#e8f2ff] text-[#2563eb]",
|
||||
inDropFeedback && "bg-gray-200/70",
|
||||
)}
|
||||
style={{ paddingLeft }}
|
||||
onClick={(event) => onRowClick(row, event)}
|
||||
onDoubleClick={(event) => onRowDoubleClick(row, event)}
|
||||
@@ -118,21 +163,29 @@ export function FileTree({
|
||||
event.dataTransfer.setData("text/plain", payload);
|
||||
event.dataTransfer.effectAllowed = event.altKey ? "copyMove" : "move";
|
||||
}}
|
||||
onDragEnd={() => {
|
||||
setDragOverRowId(null);
|
||||
}}
|
||||
onDragOver={(event) => {
|
||||
const types = Array.from(event.dataTransfer.types ?? []);
|
||||
const hasInternal = types.includes("application/x-mnote-file-tree");
|
||||
if (hasInternal && onInternalDrop) {
|
||||
event.preventDefault();
|
||||
event.dataTransfer.dropEffect = event.altKey ? "copy" : "move";
|
||||
setDragOverRowId((prev) => (prev === row.rowId ? prev : row.rowId));
|
||||
return;
|
||||
}
|
||||
if (!onDropFiles) return;
|
||||
const hasFiles = types.includes("Files") || Boolean(event.dataTransfer.files?.length);
|
||||
if (!hasFiles) return;
|
||||
event.preventDefault();
|
||||
if (event.dataTransfer.files?.length) {
|
||||
event.dataTransfer.dropEffect = "copy";
|
||||
}
|
||||
setDragOverRowId((prev) => (prev === row.rowId ? prev : row.rowId));
|
||||
}}
|
||||
onDrop={(event) => {
|
||||
setDragOverRowId(null);
|
||||
const types = Array.from(event.dataTransfer.types ?? []);
|
||||
const isInternal = types.includes("application/x-mnote-file-tree");
|
||||
if (isInternal && onInternalDrop) {
|
||||
|
||||
Reference in New Issue
Block a user