import { RefreshIcon } from "~/components/ui/refresh-icon";
import { Spinner } from "~/components/ui/spinner";
import type { EnvironmentId } from "@t3tools/contracts";
import { ArrowLeftIcon, ChevronRightIcon } from "lucide-react";
import { useEffect, useMemo, useState } from "react";
import { PierreEntryIcon } from "~/components/chat/PierreEntryIcon";
import {
Menu,
MenuGroup,
MenuItem,
MenuPopup,
MenuSeparator,
MenuTrigger,
} from "~/components/ui/menu";
import { Tooltip, TooltipPopup, TooltipTrigger } from "~/components/ui/tooltip";
import { useTheme } from "~/hooks/useTheme";
import { useWorkspaceMutationRefresh } from "~/hooks/useWorkspaceMutationRefresh";
import { cn } from "~/lib/utils";
import { isAbsolutePath } from "~/terminal-links";
import {
type FileBreadcrumb,
fileBreadcrumbChildren,
fileBreadcrumbParent,
fileBreadcrumbs,
} from "./filePath";
import { useProjectEntriesQuery } from "./projectFilesQueryState";
interface FileBreadcrumbsProps {
readonly cwd: string;
readonly environmentId: EnvironmentId;
readonly onOpenFile: (relativePath: string) => void;
readonly projectName: string;
readonly relativePath: string;
readonly workspaceMutationId: string | null;
}
function pathLabel(path: string, projectName: string): string {
return path.slice(path.lastIndexOf("/") + 1) || projectName;
}
function BreadcrumbLabel(props: {
readonly current?: boolean;
readonly label: string;
readonly pathLabel: string;
}) {
return (
}
>
{props.label}
{props.pathLabel}
);
}
function BreadcrumbMenuContent(props: {
readonly cwd: string;
readonly currentFilePath: string;
readonly directoryPath: string;
readonly environmentId: EnvironmentId;
readonly onDirectoryChange: (path: string) => void;
readonly onOpenChange: (open: boolean) => void;
readonly onOpenFile: (path: string) => void;
readonly projectName: string;
readonly rootPath: string;
readonly workspaceMutationId: string | null;
}) {
const entriesQuery = useProjectEntriesQuery(props.environmentId, props.cwd);
useWorkspaceMutationRefresh({
mutationId: props.workspaceMutationId,
refresh: entriesQuery.refresh,
resourceKey: `files:${props.environmentId}:${props.cwd}`,
});
const { resolvedTheme } = useTheme();
const entries = entriesQuery.data?.entries ?? [];
const entriesTruncated = entriesQuery.data?.truncated ?? false;
const children = useMemo(
() => fileBreadcrumbChildren(entries, props.directoryPath),
[entries, props.directoryPath],
);
const directoryAvailable =
props.directoryPath === "" ||
entries.some((entry) => entry.kind === "directory" && entry.path === props.directoryPath);
const parentPath = fileBreadcrumbParent(props.directoryPath);
const canGoBack =
props.directoryPath !== props.rootPath &&
parentPath !== null &&
(props.rootPath === "" ||
parentPath === props.rootPath ||
parentPath.startsWith(`${props.rootPath}/`));
return (
{
if (event.key !== "ArrowLeft" || !canGoBack || parentPath === null) return;
event.preventDefault();
event.stopPropagation();
props.onDirectoryChange(parentPath);
}}
>
{canGoBack && parentPath !== null ? (
<>
>
) : null}
{entriesQuery.isPending && entriesQuery.data === null ? (
) : entriesQuery.error && entriesQuery.data === null ? (
) : !directoryAvailable && !entriesTruncated ? (
) : children.length === 0 ? (
) : (
children.map((entry) => {
const isCurrentFile = entry.kind === "file" && entry.path === props.currentFilePath;
return (
);
})
)}
{entriesQuery.error && entriesQuery.data !== null ? (
<>
>
) : null}
{entriesTruncated ? (
<>
>
) : null}
);
}
function DirectoryBreadcrumb(props: FileBreadcrumbsProps & { readonly crumb: FileBreadcrumb }) {
const [open, setOpen] = useState(false);
const [directoryPath, setDirectoryPath] = useState(props.crumb.path);
useEffect(() => {
setOpen(false);
setDirectoryPath(props.crumb.path);
}, [props.crumb.path, props.relativePath]);
const handleOpenChange = (nextOpen: boolean) => {
setOpen(nextOpen);
if (nextOpen) setDirectoryPath(props.crumb.path);
};
return (
);
}
export function FileBreadcrumbs(props: FileBreadcrumbsProps) {
const hostPath = isAbsolutePath(props.relativePath);
const breadcrumbs = useMemo(
() => fileBreadcrumbs(props.projectName, props.relativePath),
[props.projectName, props.relativePath],
);
return breadcrumbs.map((crumb, index) => (
{index > 0 ? (
) : null}
{crumb.kind === "file" ? (
) : hostPath ? (
) : (
)}
));
}