update:文件后缀、icon添加
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
dialogSecondaryButtonClass,
|
||||
} from "~/components/common/AppFormDialog";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { fileVisualFromName } from "./WorkspaceTree";
|
||||
import {
|
||||
formFieldClass,
|
||||
formHintClass,
|
||||
@@ -52,6 +53,8 @@ export function DataResourceUploadModal({
|
||||
const finalPath = finalTarget
|
||||
? `${finalTarget}/${file?.name ?? ""}`
|
||||
: file?.name ?? "";
|
||||
const fileVisual = fileVisualFromName(file?.name ?? resourceName);
|
||||
const FileIcon = fileVisual.Icon;
|
||||
|
||||
return (
|
||||
<AppFormDialog
|
||||
@@ -65,8 +68,13 @@ export function DataResourceUploadModal({
|
||||
<form className={modalFormClass} onSubmit={onSubmit}>
|
||||
<label className={formFieldClass}>
|
||||
<span>已选文件</span>
|
||||
<div className="font-normal text-[#283a4e]">
|
||||
{file ? file.name : "未选择文件"}
|
||||
<div className="flex items-center gap-2.5 font-normal text-[#283a4e]">
|
||||
<span
|
||||
className={`grid size-[25px] shrink-0 place-items-center rounded-[5px] ${fileVisual.tone}`}
|
||||
>
|
||||
<FileIcon size={17} />
|
||||
</span>
|
||||
<span className="min-w-0 truncate">{file ? file.name : "未选择文件"}</span>
|
||||
</div>
|
||||
</label>
|
||||
<label className={formFieldClass}>
|
||||
@@ -75,7 +83,7 @@ export function DataResourceUploadModal({
|
||||
className={formInputClass}
|
||||
autoFocus
|
||||
maxLength={255}
|
||||
placeholder="例如:训练数据"
|
||||
placeholder="例如:训练数据.csv"
|
||||
value={resourceName}
|
||||
onChange={(event) => onNameChange(event.target.value)}
|
||||
required
|
||||
|
||||
@@ -5,8 +5,13 @@ import {
|
||||
ChevronRight,
|
||||
Database,
|
||||
FileCode,
|
||||
FileJson,
|
||||
FileSpreadsheet,
|
||||
FileText,
|
||||
FileType,
|
||||
Folder,
|
||||
Lock,
|
||||
Table2,
|
||||
type LucideIcon,
|
||||
} from "lucide-react";
|
||||
import type {
|
||||
@@ -50,6 +55,11 @@ type WorkspaceTreeItemsProps = Omit<WorkspaceTreeProps, "title" | "readOnly"> &
|
||||
onCopyResourcePath?: (jupyterPath: string) => void;
|
||||
};
|
||||
|
||||
export type FileVisual = {
|
||||
Icon: LucideIcon;
|
||||
tone: string;
|
||||
};
|
||||
|
||||
function formatTime(value: string) {
|
||||
return new Intl.DateTimeFormat("zh-CN", {
|
||||
month: "2-digit",
|
||||
@@ -64,6 +74,36 @@ export function scriptIcon(item: Pick<ScriptItem, "script_type">): LucideIcon {
|
||||
return item.script_type === "notebook" ? BookOpen : FileCode;
|
||||
}
|
||||
|
||||
/** 按文件名后缀返回树节点图标与配色(脚本 + 数据资源共用)。 */
|
||||
export function fileVisualFromName(
|
||||
name: string,
|
||||
scriptType?: ScriptItem["script_type"],
|
||||
): FileVisual {
|
||||
const lower = name.toLowerCase();
|
||||
if (scriptType === "notebook" || lower.endsWith(".ipynb")) {
|
||||
return { Icon: BookOpen, tone: "text-[#e15e50] bg-[#fff0ed]" };
|
||||
}
|
||||
if (scriptType === "python" || lower.endsWith(".py")) {
|
||||
return { Icon: FileCode, tone: "text-[#2e73c6] bg-[#eaf3ff]" };
|
||||
}
|
||||
if (lower.endsWith(".csv") || lower.endsWith(".tsv")) {
|
||||
return { Icon: Table2, tone: "text-[#2f8f7b] bg-[#eaf8f4]" };
|
||||
}
|
||||
if (lower.endsWith(".xlsx") || lower.endsWith(".xls")) {
|
||||
return { Icon: FileSpreadsheet, tone: "text-[#3d8f5a] bg-[#eef8f1]" };
|
||||
}
|
||||
if (lower.endsWith(".json")) {
|
||||
return { Icon: FileJson, tone: "text-[#b07a1a] bg-[#fff8e8]" };
|
||||
}
|
||||
if (lower.endsWith(".parquet")) {
|
||||
return { Icon: Database, tone: "text-[#4a6fa5] bg-[#eef3fa]" };
|
||||
}
|
||||
if (lower.endsWith(".txt")) {
|
||||
return { Icon: FileText, tone: "text-[#6b7c8f] bg-[#f2f5f8]" };
|
||||
}
|
||||
return { Icon: FileType, tone: "text-[#5a8f6a] bg-[#eef8f1]" };
|
||||
}
|
||||
|
||||
function ownedScriptPath(item: ScriptItem) {
|
||||
// 数据资源的 relative_path 已经是 jupyter 路径(无 ULID 前缀);脚本相对路径形如
|
||||
// `{ulid}/{ws_id}/{user_id}/...`,需要剥前两层。
|
||||
@@ -98,25 +138,33 @@ export function WorkspaceTreeGroup({
|
||||
const open = expandedPaths.has(groupKey);
|
||||
const dataResourceScripts = useMemo(() => {
|
||||
if (!dataResources) return [];
|
||||
return dataResources.map((r) => ({
|
||||
script_id: `data:${r.resource_id}`,
|
||||
workspace_id: r.workspace_id,
|
||||
current_object_id: r.storage_object_id,
|
||||
owner_user_id: r.owner_user_id,
|
||||
owner_display_name: null,
|
||||
script_name: r.resource_name,
|
||||
script_type: "python" as const,
|
||||
visibility: r.visibility,
|
||||
status: r.status,
|
||||
is_locked: false,
|
||||
// relative_path 直接是 jupyter 路径(与 Python/Notebook 同层渲染,不再带虚拟前缀)
|
||||
relative_path: r.jupyter_accessible_path,
|
||||
jupyter_path: r.jupyter_accessible_path,
|
||||
content_hash: r.file.content_hash ?? "",
|
||||
size_bytes: r.file.size_bytes,
|
||||
created_at: r.created_at,
|
||||
updated_at: r.updated_at,
|
||||
} as unknown as ScriptItem));
|
||||
return dataResources.map((r) => {
|
||||
const ext = r.file.file_extension;
|
||||
const name = r.resource_name;
|
||||
const displayName =
|
||||
ext && !name.toLowerCase().endsWith(ext.toLowerCase())
|
||||
? `${name}${ext}`
|
||||
: name;
|
||||
return {
|
||||
script_id: `data:${r.resource_id}`,
|
||||
workspace_id: r.workspace_id,
|
||||
current_object_id: r.storage_object_id,
|
||||
owner_user_id: r.owner_user_id,
|
||||
owner_display_name: null,
|
||||
script_name: displayName,
|
||||
script_type: "python" as const,
|
||||
visibility: r.visibility,
|
||||
status: r.status,
|
||||
is_locked: false,
|
||||
// relative_path 直接是 jupyter 路径(与 Python/Notebook 同层渲染,不再带虚拟前缀)
|
||||
relative_path: r.jupyter_accessible_path,
|
||||
jupyter_path: r.jupyter_accessible_path,
|
||||
content_hash: r.file.content_hash ?? "",
|
||||
size_bytes: r.file.size_bytes,
|
||||
created_at: r.created_at,
|
||||
updated_at: r.updated_at,
|
||||
} as unknown as ScriptItem;
|
||||
});
|
||||
}, [dataResources]);
|
||||
|
||||
const dataResourceDirectories = useMemo(() => {
|
||||
@@ -250,12 +298,10 @@ function WorkspaceTreeItems({
|
||||
{childScripts.map((item) => {
|
||||
const isActive = selectedId === item.script_id;
|
||||
const isData = item.script_id.startsWith("data:");
|
||||
const ItemIcon = isData ? Database : scriptIcon(item);
|
||||
const iconTone = isData
|
||||
? "text-[#5a8f6a] bg-[#eef8f1]"
|
||||
: item.script_type === "notebook"
|
||||
? "text-[#e15e50] bg-[#fff0ed]"
|
||||
: "text-[#2e73c6] bg-[#eaf3ff]";
|
||||
const { Icon: ItemIcon, tone: iconTone } = fileVisualFromName(
|
||||
item.script_name,
|
||||
isData ? undefined : item.script_type,
|
||||
);
|
||||
return (
|
||||
<button
|
||||
className={`flex h-[47px] w-full cursor-pointer items-center gap-2 rounded-md border pr-[9px] text-left ${
|
||||
|
||||
@@ -251,7 +251,7 @@ export const useUiStore = create<UiState>((set) => ({
|
||||
open: true,
|
||||
file,
|
||||
parentPath,
|
||||
resourceName: file.name.replace(/\.[^/.]+$/, ""),
|
||||
resourceName: file.name,
|
||||
visibility: "workspace",
|
||||
description: "",
|
||||
uploading: false,
|
||||
|
||||
Reference in New Issue
Block a user