updata:icon替换
This commit is contained in:
@@ -0,0 +1,24 @@
|
|||||||
|
/** Product logo mark used in the sidebar header. */
|
||||||
|
export function BrandMark({ size = 31 }: { size?: number }) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
viewBox="0 0 32 32"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
aria-hidden
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M16 3.5 27 9.8v12.4L16 28.5 5 22.2V9.8L16 3.5Z"
|
||||||
|
fill="currentColor"
|
||||||
|
opacity=".2"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M16 7.7 23.4 12v8L16 24.3 8.6 20v-8L16 7.7Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<circle cx="16" cy="16" r="3.3" fill="white" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
type IconName =
|
|
||||||
| "brand"
|
|
||||||
| "home"
|
|
||||||
| "script"
|
|
||||||
| "schedule"
|
|
||||||
| "experiment"
|
|
||||||
| "database"
|
|
||||||
| "settings"
|
|
||||||
| "chevron"
|
|
||||||
| "search"
|
|
||||||
| "plus"
|
|
||||||
| "upload"
|
|
||||||
| "folder"
|
|
||||||
| "notebook"
|
|
||||||
| "python"
|
|
||||||
| "refresh"
|
|
||||||
| "close"
|
|
||||||
| "check"
|
|
||||||
| "info"
|
|
||||||
| "lock"
|
|
||||||
| "unlock"
|
|
||||||
| "workspace"
|
|
||||||
| "menu"
|
|
||||||
| "external"
|
|
||||||
| "release"
|
|
||||||
| "play"
|
|
||||||
| "grip-vertical";
|
|
||||||
|
|
||||||
type IconProps = {
|
|
||||||
name: IconName;
|
|
||||||
size?: number;
|
|
||||||
strokeWidth?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function Icon({
|
|
||||||
name,
|
|
||||||
size = 18,
|
|
||||||
strokeWidth = 1.8,
|
|
||||||
}: IconProps) {
|
|
||||||
const common = {
|
|
||||||
width: size,
|
|
||||||
height: size,
|
|
||||||
viewBox: "0 0 24 24",
|
|
||||||
fill: "none",
|
|
||||||
xmlns: "http://www.w3.org/2000/svg",
|
|
||||||
"aria-hidden": true,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (name === "brand") {
|
|
||||||
return (
|
|
||||||
<svg {...common} viewBox="0 0 32 32">
|
|
||||||
<path
|
|
||||||
d="M16 3.5 27 9.8v12.4L16 28.5 5 22.2V9.8L16 3.5Z"
|
|
||||||
fill="currentColor"
|
|
||||||
opacity=".2"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M16 7.7 23.4 12v8L16 24.3 8.6 20v-8L16 7.7Z"
|
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
<circle cx="16" cy="16" r="3.3" fill="white" />
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const paths: Record<Exclude<IconName, "brand">, React.ReactNode> = {
|
|
||||||
home: (
|
|
||||||
<>
|
|
||||||
<path d="m3.5 11 8.5-7 8.5 7" />
|
|
||||||
<path d="M5.5 10v10h13V10M9.5 20v-6h5v6" />
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
script: (
|
|
||||||
<>
|
|
||||||
<rect x="5" y="3.5" width="14" height="17" rx="2" />
|
|
||||||
<path d="M8.5 8h7M8.5 12h7M8.5 16h4.5" />
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
schedule: (
|
|
||||||
<>
|
|
||||||
<circle cx="12" cy="12" r="8.5" />
|
|
||||||
<path d="M12 7.5V12l3.2 2" />
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
experiment: (
|
|
||||||
<>
|
|
||||||
<path d="M9 3.5h6M10 3.5v5l-5.2 9a2 2 0 0 0 1.7 3h11a2 2 0 0 0 1.7-3L14 8.5v-5" />
|
|
||||||
<path d="M7.3 15h9.4" />
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
database: (
|
|
||||||
<>
|
|
||||||
<ellipse cx="12" cy="5.5" rx="7.5" ry="3" />
|
|
||||||
<path d="M4.5 5.5v6c0 1.7 3.4 3 7.5 3s7.5-1.3 7.5-3v-6" />
|
|
||||||
<path d="M4.5 11.5v6c0 1.7 3.4 3 7.5 3s7.5-1.3 7.5-3v-6" />
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
settings: (
|
|
||||||
<>
|
|
||||||
<circle cx="12" cy="12" r="3" />
|
|
||||||
<path d="M19.4 15a1.7 1.7 0 0 0 .3 1.9l.1.1-2.8 2.8-.1-.1a1.7 1.7 0 0 0-1.9-.3 1.7 1.7 0 0 0-1 1.6v.2h-4V21a1.7 1.7 0 0 0-1-1.6 1.7 1.7 0 0 0-1.9.3l-.1.1L4.2 17l.1-.1a1.7 1.7 0 0 0 .3-1.9A1.7 1.7 0 0 0 3 14H2.8v-4H3a1.7 1.7 0 0 0 1.6-1 1.7 1.7 0 0 0-.3-1.9L4.2 7 7 4.2l.1.1a1.7 1.7 0 0 0 1.9.3A1.7 1.7 0 0 0 10 3v-.2h4V3a1.7 1.7 0 0 0 1 1.6 1.7 1.7 0 0 0 1.9-.3l.1-.1L19.8 7l-.1.1a1.7 1.7 0 0 0-.3 1.9 1.7 1.7 0 0 0 1.6 1h.2v4H21a1.7 1.7 0 0 0-1.6 1Z" />
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
chevron: <path d="m9 7 5 5-5 5" />,
|
|
||||||
search: (
|
|
||||||
<>
|
|
||||||
<circle cx="10.5" cy="10.5" r="6.5" />
|
|
||||||
<path d="m15.5 15.5 4 4" />
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
plus: <path d="M12 5v14M5 12h14" />,
|
|
||||||
upload: (
|
|
||||||
<>
|
|
||||||
<path d="M12 16V4m0 0L7.5 8.5M12 4l4.5 4.5" />
|
|
||||||
<path d="M4 15.5V20h16v-4.5" />
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
folder: (
|
|
||||||
<path d="M3.5 6.5h6l2 2H20.5v10.5a1.5 1.5 0 0 1-1.5 1.5H5a1.5 1.5 0 0 1-1.5-1.5V6.5Z" />
|
|
||||||
),
|
|
||||||
notebook: (
|
|
||||||
<>
|
|
||||||
<rect x="5" y="3.5" width="14" height="17" rx="2" />
|
|
||||||
<path d="M8.5 3.5v17M11.5 8h4M11.5 12h4M11.5 16h2.5" />
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
python: (
|
|
||||||
<>
|
|
||||||
<path d="M8 4.5h5.5a2.5 2.5 0 0 1 2.5 2.5v3H8a3 3 0 0 0-3 3v2" />
|
|
||||||
<path d="M16 19.5h-5.5A2.5 2.5 0 0 1 8 17v-3h8a3 3 0 0 0 3-3V9" />
|
|
||||||
<circle cx="11" cy="7" r=".8" fill="currentColor" stroke="none" />
|
|
||||||
<circle cx="13" cy="17" r=".8" fill="currentColor" stroke="none" />
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
refresh: (
|
|
||||||
<>
|
|
||||||
<path d="M19 8V4l-1.7 1.7A8 8 0 1 0 20 12" />
|
|
||||||
<path d="M19 4h-4" />
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
close: <path d="m6 6 12 12M18 6 6 18" />,
|
|
||||||
check: <path d="m5 12.5 4 4L19 7" />,
|
|
||||||
info: (
|
|
||||||
<>
|
|
||||||
<circle cx="12" cy="12" r="9" />
|
|
||||||
<path d="M12 11v5M12 8h.01" />
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
lock: (
|
|
||||||
<>
|
|
||||||
<rect x="5" y="11" width="14" height="9" rx="2" />
|
|
||||||
<path d="M8 11V7a4 4 0 0 1 8 0v4" />
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
unlock: (
|
|
||||||
<>
|
|
||||||
<rect x="5" y="11" width="14" height="9" rx="2" />
|
|
||||||
<path d="M8 11V7a4 4 0 0 1 7.5-2" />
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
workspace: (
|
|
||||||
<>
|
|
||||||
<rect x="3.5" y="5" width="17" height="14" rx="2" />
|
|
||||||
<path d="M8 5V3.5h8V5M8 11h8M12 8v6" />
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
menu: (
|
|
||||||
<>
|
|
||||||
<path d="M4 6h16M4 12h16M4 18h16" />
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
external: (
|
|
||||||
<>
|
|
||||||
<path d="M14 5h5v5M19 5l-8 8" />
|
|
||||||
<path d="M18 13v6H5V6h6" />
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
release: (
|
|
||||||
<>
|
|
||||||
<path d="M12 3.5v11M7.5 8 12 3.5 16.5 8" />
|
|
||||||
<path d="M5 12.5v7h14v-7" />
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
play: <path d="m8 5 11 7-11 7V5Z" />,
|
|
||||||
"grip-vertical": (
|
|
||||||
<>
|
|
||||||
<circle cx="9" cy="6" r="1" fill="currentColor" stroke="none" />
|
|
||||||
<circle cx="9" cy="12" r="1" fill="currentColor" stroke="none" />
|
|
||||||
<circle cx="9" cy="18" r="1" fill="currentColor" stroke="none" />
|
|
||||||
<circle cx="15" cy="6" r="1" fill="currentColor" stroke="none" />
|
|
||||||
<circle cx="15" cy="12" r="1" fill="currentColor" stroke="none" />
|
|
||||||
<circle cx="15" cy="18" r="1" fill="currentColor" stroke="none" />
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<svg
|
|
||||||
{...common}
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth={strokeWidth}
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
>
|
|
||||||
{paths[name]}
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import Icon from "./Icon";
|
import { ChevronRight, LayoutGrid } from "lucide-react";
|
||||||
import type { AuthUser, AuthWorkspace } from "../../context/AuthContext";
|
import type { AuthUser, AuthWorkspace } from "../../context/AuthContext";
|
||||||
|
|
||||||
type TopbarProps = {
|
type TopbarProps = {
|
||||||
@@ -38,7 +38,7 @@ export function Topbar({
|
|||||||
className="grid h-[31px] w-[31px] rotate-180 cursor-pointer place-items-center rounded-[7px] border border-transparent bg-white text-[#7b8999] hover:border-[#b9c9da] hover:bg-[#f7faff]"
|
className="grid h-[31px] w-[31px] rotate-180 cursor-pointer place-items-center rounded-[7px] border border-transparent bg-white text-[#7b8999] hover:border-[#b9c9da] hover:bg-[#f7faff]"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<Icon name="chevron" size={19} />
|
<ChevronRight size={19} />
|
||||||
</button>
|
</button>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-[10px] font-[650] uppercase tracking-[0.08em] text-[#94a2b3]">
|
<span className="text-[10px] font-[650] uppercase tracking-[0.08em] text-[#94a2b3]">
|
||||||
@@ -67,7 +67,7 @@ export function Topbar({
|
|||||||
onClick={() => onSetWorkspaceMenuOpen(!workspaceMenuOpen)}
|
onClick={() => onSetWorkspaceMenuOpen(!workspaceMenuOpen)}
|
||||||
>
|
>
|
||||||
<span className="grid size-[30px] place-items-center rounded-[7px] bg-gradient-to-br from-[#23aa73] to-[#158d5d] text-white">
|
<span className="grid size-[30px] place-items-center rounded-[7px] bg-gradient-to-br from-[#23aa73] to-[#158d5d] text-white">
|
||||||
<Icon name="workspace" size={18} />
|
<LayoutGrid size={18} />
|
||||||
</span>
|
</span>
|
||||||
<span className="flex min-w-[155px] max-xl:min-w-[130px] flex-col items-start">
|
<span className="flex min-w-[155px] max-xl:min-w-[130px] flex-col items-start">
|
||||||
<small className="text-[10px] text-[#97a3b1]">当前 Workspace</small>
|
<small className="text-[10px] text-[#97a3b1]">当前 Workspace</small>
|
||||||
@@ -75,7 +75,7 @@ export function Topbar({
|
|||||||
{currentWorkspace.workspace_name}
|
{currentWorkspace.workspace_name}
|
||||||
</strong>
|
</strong>
|
||||||
</span>
|
</span>
|
||||||
<Icon name="chevron" size={15} />
|
<ChevronRight size={15} />
|
||||||
</button>
|
</button>
|
||||||
{workspaceMenuOpen && (
|
{workspaceMenuOpen && (
|
||||||
<div className="absolute top-[calc(100%+7px)] right-0 z-50 w-[260px] rounded-lg border border-[#dce5ed] bg-white p-1.5 shadow-[0_14px_38px_rgb(17_43_70_/_18%)]">
|
<div className="absolute top-[calc(100%+7px)] right-0 z-50 w-[260px] rounded-lg border border-[#dce5ed] bg-white p-1.5 shadow-[0_14px_38px_rgb(17_43_70_/_18%)]">
|
||||||
@@ -96,7 +96,7 @@ export function Topbar({
|
|||||||
onSetWorkspaceMenuOpen(false);
|
onSetWorkspaceMenuOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon name="workspace" size={15} />
|
<LayoutGrid size={15} />
|
||||||
<span className="flex min-w-0 flex-col gap-0.5">
|
<span className="flex min-w-0 flex-col gap-0.5">
|
||||||
<strong className="truncate text-[11px] text-[#243a50]">
|
<strong className="truncate text-[11px] text-[#243a50]">
|
||||||
{workspace.workspace_name}
|
{workspace.workspace_name}
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
import Icon from "../../components/common/Icon";
|
import {
|
||||||
|
Calendar,
|
||||||
|
Check,
|
||||||
|
FileText,
|
||||||
|
LayoutGrid,
|
||||||
|
Settings,
|
||||||
|
type LucideIcon,
|
||||||
|
} from "lucide-react";
|
||||||
import { useAuth, usePermission } from "../../context/AuthContext";
|
import { useAuth, usePermission } from "../../context/AuthContext";
|
||||||
|
|
||||||
const TREND_VALUES = [38, 55, 44, 73, 61, 86, 78] as const;
|
const TREND_VALUES = [38, 55, 44, 73, 61, 86, 78] as const;
|
||||||
@@ -50,22 +57,24 @@ export function DashboardPage({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-[18px] grid grid-cols-4 gap-3.5">
|
<div className="mt-[18px] grid grid-cols-4 gap-3.5">
|
||||||
{[
|
{(
|
||||||
{ icon: "script" as const, value: scriptCount, label: "工作副本" },
|
[
|
||||||
{ icon: "workspace" as const, value: 2, label: "Workspace" },
|
{ Icon: FileText, value: scriptCount, label: "工作副本" },
|
||||||
{ icon: "settings" as const, value: 4, label: "平台用户" },
|
{ Icon: LayoutGrid, value: 2, label: "Workspace" },
|
||||||
{
|
{ Icon: Settings, value: 4, label: "平台用户" },
|
||||||
icon: "check" as const,
|
{
|
||||||
value: online ? "正常" : "检查中",
|
Icon: Check,
|
||||||
label: "平台状态",
|
value: online ? "正常" : "检查中",
|
||||||
},
|
label: "平台状态",
|
||||||
].map((item) => (
|
},
|
||||||
|
] as Array<{ Icon: LucideIcon; value: number | string; label: string }>
|
||||||
|
).map((item) => (
|
||||||
<article
|
<article
|
||||||
key={item.label}
|
key={item.label}
|
||||||
className="flex items-center gap-3.5 rounded-[9px] border border-[#e0e8ef] bg-white p-5"
|
className="flex items-center gap-3.5 rounded-[9px] border border-[#e0e8ef] bg-white p-5"
|
||||||
>
|
>
|
||||||
<span className="text-brand">
|
<span className="text-brand">
|
||||||
<Icon name={item.icon} />
|
<item.Icon />
|
||||||
</span>
|
</span>
|
||||||
<span className="flex flex-col">
|
<span className="flex flex-col">
|
||||||
<b className="text-[21px] text-[#20364c]">{item.value}</b>
|
<b className="text-[21px] text-[#20364c]">{item.value}</b>
|
||||||
@@ -81,7 +90,7 @@ export function DashboardPage({
|
|||||||
className="flex cursor-pointer items-center gap-2 rounded-[7px] border border-[#d8e4ef] bg-white px-[18px] py-[13px] text-[#346587]"
|
className="flex cursor-pointer items-center gap-2 rounded-[7px] border border-[#d8e4ef] bg-white px-[18px] py-[13px] text-[#346587]"
|
||||||
onClick={() => onNavigate("scripts")}
|
onClick={() => onNavigate("scripts")}
|
||||||
>
|
>
|
||||||
<Icon name="script" />
|
<FileText />
|
||||||
构建脚本
|
构建脚本
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -89,7 +98,7 @@ export function DashboardPage({
|
|||||||
className="flex cursor-pointer items-center gap-2 rounded-[7px] border border-[#d8e4ef] bg-white px-[18px] py-[13px] text-[#346587]"
|
className="flex cursor-pointer items-center gap-2 rounded-[7px] border border-[#d8e4ef] bg-white px-[18px] py-[13px] text-[#346587]"
|
||||||
onClick={() => onNavigate("schedules")}
|
onClick={() => onNavigate("schedules")}
|
||||||
>
|
>
|
||||||
<Icon name="schedule" />
|
<Calendar />
|
||||||
调度配置
|
调度配置
|
||||||
</button>
|
</button>
|
||||||
{hasSystemView && (
|
{hasSystemView && (
|
||||||
@@ -98,7 +107,7 @@ export function DashboardPage({
|
|||||||
className="flex cursor-pointer items-center gap-2 rounded-[7px] border border-[#d8e4ef] bg-white px-[18px] py-[13px] text-[#346587]"
|
className="flex cursor-pointer items-center gap-2 rounded-[7px] border border-[#d8e4ef] bg-white px-[18px] py-[13px] text-[#346587]"
|
||||||
onClick={() => onNavigate("system")}
|
onClick={() => onNavigate("system")}
|
||||||
>
|
>
|
||||||
<Icon name="settings" />
|
<Settings />
|
||||||
系统管理
|
系统管理
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
@@ -226,7 +235,7 @@ export function DashboardPage({
|
|||||||
>
|
>
|
||||||
<span className="flex items-center gap-[9px] text-[#344c63]">
|
<span className="flex items-center gap-[9px] text-[#344c63]">
|
||||||
<i className="grid size-[25px] place-items-center rounded-md bg-[#eaf4ff] text-[#277fcc]">
|
<i className="grid size-[25px] place-items-center rounded-md bg-[#eaf4ff] text-[#277fcc]">
|
||||||
<Icon name="check" size={13} />
|
<Check size={13} />
|
||||||
</i>
|
</i>
|
||||||
{row[0]}
|
{row[0]}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { type Workspace, type WorkspaceMember } from "../../services/api";
|
import { type Workspace, type WorkspaceMember } from "../../services/api";
|
||||||
import Icon from "../../components/common/Icon";
|
import { Plus, X } from "lucide-react";
|
||||||
|
|
||||||
export function ProjectMembersDrawer({
|
export function ProjectMembersDrawer({
|
||||||
open,
|
open,
|
||||||
@@ -60,7 +60,7 @@ export function ProjectMembersDrawer({
|
|||||||
<p style={{ margin: "4px 0 0", fontSize: 13, color: "#888" }}>{selectedProject.workspace_name}</p>
|
<p style={{ margin: "4px 0 0", fontSize: 13, color: "#888" }}>{selectedProject.workspace_name}</p>
|
||||||
</div>
|
</div>
|
||||||
<button className="icon-button" type="button" onClick={() => onClose()}>
|
<button className="icon-button" type="button" onClick={() => onClose()}>
|
||||||
<Icon name="close" />
|
<X />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ export function ProjectMembersDrawer({
|
|||||||
onClick={() => onAddMembers()}
|
onClick={() => onAddMembers()}
|
||||||
style={{ fontSize: 13, padding: "6px 12px" }}
|
style={{ fontSize: 13, padding: "6px 12px" }}
|
||||||
>
|
>
|
||||||
<Icon name="plus" size={14} /> 添加成员
|
<Plus size={14} /> 添加成员
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { type FormEvent } from "react";
|
import { type FormEvent } from "react";
|
||||||
import Icon from "../../components/common/Icon";
|
import { Folder } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
AppFormDialog,
|
AppFormDialog,
|
||||||
dialogPrimaryButtonClass,
|
dialogPrimaryButtonClass,
|
||||||
@@ -43,7 +43,7 @@ export function CreateFolderModal({
|
|||||||
>
|
>
|
||||||
<form className={modalFormClass} onSubmit={onSubmit}>
|
<form className={modalFormClass} onSubmit={onSubmit}>
|
||||||
<div className={destinationChipClass}>
|
<div className={destinationChipClass}>
|
||||||
<Icon name="folder" size={16} />
|
<Folder size={16} />
|
||||||
创建到:{parentPath || "个人根目录"}
|
创建到:{parentPath || "个人根目录"}
|
||||||
</div>
|
</div>
|
||||||
<label className={formFieldClass}>
|
<label className={formFieldClass}>
|
||||||
@@ -76,7 +76,7 @@ export function CreateFolderModal({
|
|||||||
>
|
>
|
||||||
{busy
|
{busy
|
||||||
? <span className="button-spinner" />
|
? <span className="button-spinner" />
|
||||||
: <Icon name="folder" size={16} />}
|
: <Folder size={16} />}
|
||||||
{busy ? "正在创建…" : "创建文件夹"}
|
{busy ? "正在创建…" : "创建文件夹"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { type FormEvent } from "react";
|
import { type FormEvent } from "react";
|
||||||
import Icon from "../../components/common/Icon";
|
import { BookOpen, Check, FileCode, Folder, Plus } from "lucide-react";
|
||||||
import type { ScriptType, Visibility } from "../../services/api";
|
import type { ScriptType, Visibility } from "../../services/api";
|
||||||
import {
|
import {
|
||||||
AppFormDialog,
|
AppFormDialog,
|
||||||
@@ -57,7 +57,7 @@ export function CreateScriptModal({
|
|||||||
>
|
>
|
||||||
<form className={modalFormClass} onSubmit={onSubmit}>
|
<form className={modalFormClass} onSubmit={onSubmit}>
|
||||||
<div className={destinationChipClass}>
|
<div className={destinationChipClass}>
|
||||||
<Icon name="folder" size={16} />
|
<Folder size={16} />
|
||||||
保存到:{form.parentPath || "个人根目录"}
|
保存到:{form.parentPath || "个人根目录"}
|
||||||
</div>
|
</div>
|
||||||
<label className={formFieldClass}>
|
<label className={formFieldClass}>
|
||||||
@@ -92,7 +92,7 @@ export function CreateScriptModal({
|
|||||||
type: "notebook" as const,
|
type: "notebook" as const,
|
||||||
title: "Jupyter Notebook",
|
title: "Jupyter Notebook",
|
||||||
desc: "交互式数据探索与模型实验",
|
desc: "交互式数据探索与模型实验",
|
||||||
icon: "notebook" as const,
|
Icon: BookOpen,
|
||||||
iconTone: "bg-[#fff0ed] text-[#dd5a4c]",
|
iconTone: "bg-[#fff0ed] text-[#dd5a4c]",
|
||||||
size: 22,
|
size: 22,
|
||||||
},
|
},
|
||||||
@@ -100,13 +100,14 @@ export function CreateScriptModal({
|
|||||||
type: "python" as const,
|
type: "python" as const,
|
||||||
title: "Python 脚本",
|
title: "Python 脚本",
|
||||||
desc: "批处理、训练和模型调用任务",
|
desc: "批处理、训练和模型调用任务",
|
||||||
icon: "python" as const,
|
Icon: FileCode,
|
||||||
iconTone: "bg-[#e9f3ff] text-[#2a75c5]",
|
iconTone: "bg-[#e9f3ff] text-[#2a75c5]",
|
||||||
size: 23,
|
size: 23,
|
||||||
},
|
},
|
||||||
] as const
|
] as const
|
||||||
).map((item) => {
|
).map((item) => {
|
||||||
const selected = form.scriptType === item.type;
|
const selected = form.scriptType === item.type;
|
||||||
|
const TypeIcon = item.Icon;
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={item.type}
|
key={item.type}
|
||||||
@@ -124,7 +125,7 @@ export function CreateScriptModal({
|
|||||||
<span
|
<span
|
||||||
className={`grid size-[37px] shrink-0 place-items-center rounded-lg ${item.iconTone}`}
|
className={`grid size-[37px] shrink-0 place-items-center rounded-lg ${item.iconTone}`}
|
||||||
>
|
>
|
||||||
<Icon name={item.icon} size={item.size} />
|
<TypeIcon size={item.size} />
|
||||||
</span>
|
</span>
|
||||||
<span className="flex min-w-0 flex-col">
|
<span className="flex min-w-0 flex-col">
|
||||||
<strong className="text-[11px] text-[#34475b]">
|
<strong className="text-[11px] text-[#34475b]">
|
||||||
@@ -136,7 +137,7 @@ export function CreateScriptModal({
|
|||||||
</span>
|
</span>
|
||||||
{selected && (
|
{selected && (
|
||||||
<span className="absolute top-1.5 right-1.5 grid size-[17px] place-items-center rounded-full bg-[#2e88dd] text-white">
|
<span className="absolute top-1.5 right-1.5 grid size-[17px] place-items-center rounded-full bg-[#2e88dd] text-white">
|
||||||
<Icon name="check" size={14} />
|
<Check size={14} />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
@@ -178,7 +179,7 @@ export function CreateScriptModal({
|
|||||||
disabled={creating || !form.name.trim()}
|
disabled={creating || !form.name.trim()}
|
||||||
className={dialogPrimaryButtonClass}
|
className={dialogPrimaryButtonClass}
|
||||||
>
|
>
|
||||||
{creating ? <span className="button-spinner" /> : <Icon name="plus" size={16} />}
|
{creating ? <span className="button-spinner" /> : <Plus size={16} />}
|
||||||
{creating ? "正在创建…" : "创建脚本"}
|
{creating ? "正在创建…" : "创建脚本"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { type FormEvent } from "react";
|
import { type FormEvent } from "react";
|
||||||
import Icon from "../../components/common/Icon";
|
import { Upload } from "lucide-react";
|
||||||
import type { Visibility } from "../../services/api";
|
import type { Visibility } from "../../services/api";
|
||||||
import {
|
import {
|
||||||
AppFormDialog,
|
AppFormDialog,
|
||||||
@@ -142,7 +142,7 @@ export function DataResourceUploadModal({
|
|||||||
{uploading ? (
|
{uploading ? (
|
||||||
<span className="button-spinner" />
|
<span className="button-spinner" />
|
||||||
) : (
|
) : (
|
||||||
<Icon name="upload" size={16} />
|
<Upload size={16} />
|
||||||
)}
|
)}
|
||||||
{uploading ? "上传中…" : "上传"}
|
{uploading ? "上传中…" : "上传"}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { type FormEvent } from "react";
|
import { type FormEvent } from "react";
|
||||||
import Icon from "../../components/common/Icon";
|
import { ArrowUpFromLine } from "lucide-react";
|
||||||
import type { ScriptItem, Visibility } from "../../services/api";
|
import type { ScriptItem, Visibility } from "../../services/api";
|
||||||
import { scriptIcon } from "../../features/platform/WorkspaceTree";
|
import { scriptIcon } from "../../features/platform/WorkspaceTree";
|
||||||
import {
|
import {
|
||||||
@@ -37,6 +37,8 @@ export function PublishModal({
|
|||||||
onSubmit,
|
onSubmit,
|
||||||
onClose,
|
onClose,
|
||||||
}: PublishModalProps) {
|
}: PublishModalProps) {
|
||||||
|
const PublishTargetIcon = publishTarget ? scriptIcon(publishTarget) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppFormDialog
|
<AppFormDialog
|
||||||
open={publishTarget !== null}
|
open={publishTarget !== null}
|
||||||
@@ -48,7 +50,7 @@ export function PublishModal({
|
|||||||
size="panel"
|
size="panel"
|
||||||
>
|
>
|
||||||
<form className={modalFormClass} onSubmit={onSubmit}>
|
<form className={modalFormClass} onSubmit={onSubmit}>
|
||||||
{publishTarget ? (
|
{publishTarget && PublishTargetIcon ? (
|
||||||
<div className="mb-[17px] flex items-center gap-2.5 rounded-[7px] border border-[#dce6ef] bg-[#f8fbfd] px-3 py-[11px]">
|
<div className="mb-[17px] flex items-center gap-2.5 rounded-[7px] border border-[#dce6ef] bg-[#f8fbfd] px-3 py-[11px]">
|
||||||
<span
|
<span
|
||||||
className={`grid size-[25px] shrink-0 place-items-center rounded-[5px] ${
|
className={`grid size-[25px] shrink-0 place-items-center rounded-[5px] ${
|
||||||
@@ -57,7 +59,7 @@ export function PublishModal({
|
|||||||
: "bg-[#eaf3ff] text-[#2e73c6]"
|
: "bg-[#eaf3ff] text-[#2e73c6]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Icon name={scriptIcon(publishTarget)} size={18} />
|
<PublishTargetIcon size={18} />
|
||||||
</span>
|
</span>
|
||||||
<span className="flex min-w-0 flex-col gap-0.5">
|
<span className="flex min-w-0 flex-col gap-0.5">
|
||||||
<strong className="truncate text-xs text-[#34485d]">
|
<strong className="truncate text-xs text-[#34485d]">
|
||||||
@@ -114,7 +116,7 @@ export function PublishModal({
|
|||||||
>
|
>
|
||||||
{publishing
|
{publishing
|
||||||
? <span className="button-spinner" />
|
? <span className="button-spinner" />
|
||||||
: <Icon name="release" size={16} />}
|
: <ArrowUpFromLine size={16} />}
|
||||||
{publishing ? "正在发布…" : "确认发布"}
|
{publishing ? "正在发布…" : "确认发布"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import Editor from "@monaco-editor/react";
|
import Editor from "@monaco-editor/react";
|
||||||
|
import { Info } from "lucide-react";
|
||||||
import Icon from "../../components/common/Icon";
|
|
||||||
|
|
||||||
import type { ScriptItem } from "../../services/api";
|
import type { ScriptItem } from "../../services/api";
|
||||||
|
|
||||||
@@ -92,7 +91,7 @@ export function PythonEditor({
|
|||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
>
|
>
|
||||||
<div className="mb-4 grid size-[54px] place-items-center rounded-full bg-[#fff1f0] text-[#c84f49]">
|
<div className="mb-4 grid size-[54px] place-items-center rounded-full bg-[#fff1f0] text-[#c84f49]">
|
||||||
<Icon name="info" size={28} />
|
<Info size={28} />
|
||||||
</div>
|
</div>
|
||||||
<strong className="text-base text-[#1d344d]">Python 编辑器打开失败</strong>
|
<strong className="text-base text-[#1d344d]">Python 编辑器打开失败</strong>
|
||||||
<p className="mt-[9px] max-w-[430px] text-xs leading-[1.7] text-[#7a8a9b] [overflow-wrap:anywhere]">
|
<p className="mt-[9px] max-w-[430px] text-xs leading-[1.7] text-[#7a8a9b] [overflow-wrap:anywhere]">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { type ChangeEvent, type MouseEvent as ReactMouseEvent, type RefObject, useMemo } from "react";
|
import { type ChangeEvent, type MouseEvent as ReactMouseEvent, type RefObject, useMemo } from "react";
|
||||||
import Icon from "../../components/common/Icon";
|
import { FileText, Plus, RefreshCw, Search, Upload } from "lucide-react";
|
||||||
import { WorkspaceTreeGroup } from "~/features/platform/WorkspaceTree";
|
import { WorkspaceTreeGroup } from "~/features/platform/WorkspaceTree";
|
||||||
import { useScriptWorkspaceStore } from "~/features/platform/state/scriptWorkspaceStore";
|
import { useScriptWorkspaceStore } from "~/features/platform/state/scriptWorkspaceStore";
|
||||||
import type { ScriptItem, WorkspaceDirectory } from "~/services/api";
|
import type { ScriptItem, WorkspaceDirectory } from "~/services/api";
|
||||||
@@ -175,7 +175,7 @@ export function ScriptExplorer({
|
|||||||
disabled={uploading}
|
disabled={uploading}
|
||||||
onClick={onUpload}
|
onClick={onUpload}
|
||||||
>
|
>
|
||||||
<Icon name="upload" size={15} />
|
<Upload size={15} />
|
||||||
{uploading ? "上传中…" : "上传"}
|
{uploading ? "上传中…" : "上传"}
|
||||||
</button>
|
</button>
|
||||||
<input
|
<input
|
||||||
@@ -191,14 +191,14 @@ export function ScriptExplorer({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => onOpenCreateDialog()}
|
onClick={() => onOpenCreateDialog()}
|
||||||
>
|
>
|
||||||
<Icon name="plus" size={16} />
|
<Plus size={16} />
|
||||||
新建
|
新建
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mx-3 mt-[11px] mb-[7px] flex h-[38px] items-center gap-[7px] rounded-md border border-[#dce5ed] bg-[#fbfcfd] px-[9px] text-[#95a2b1] focus-within:border-[#6aa9e9] focus-within:shadow-[0_0_0_3px_rgb(31_119_207/8%)]">
|
<div className="mx-3 mt-[11px] mb-[7px] flex h-[38px] items-center gap-[7px] rounded-md border border-[#dce5ed] bg-[#fbfcfd] px-[9px] text-[#95a2b1] focus-within:border-[#6aa9e9] focus-within:shadow-[0_0_0_3px_rgb(31_119_207/8%)]">
|
||||||
<Icon name="search" size={17} />
|
<Search size={17} />
|
||||||
<input
|
<input
|
||||||
className="min-w-0 flex-1 border-0 bg-transparent text-xs text-[#26384d] outline-none placeholder:text-[#a7b2bf]"
|
className="min-w-0 flex-1 border-0 bg-transparent text-xs text-[#26384d] outline-none placeholder:text-[#a7b2bf]"
|
||||||
aria-label="搜索脚本"
|
aria-label="搜索脚本"
|
||||||
@@ -213,7 +213,7 @@ export function ScriptExplorer({
|
|||||||
onClick={onRefresh}
|
onClick={onRefresh}
|
||||||
>
|
>
|
||||||
<span className={refreshing ? "inline-flex animate-spin" : "inline-flex"}>
|
<span className={refreshing ? "inline-flex animate-spin" : "inline-flex"}>
|
||||||
<Icon name="refresh" size={16} />
|
<RefreshCw size={16} />
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -257,7 +257,7 @@ export function ScriptExplorer({
|
|||||||
{filteredScripts.length === 0 && directories.length === 0 && (
|
{filteredScripts.length === 0 && directories.length === 0 && (
|
||||||
<div className="flex flex-col items-center px-[18px] py-[34px] text-center text-[#97a4b4]">
|
<div className="flex flex-col items-center px-[18px] py-[34px] text-center text-[#97a4b4]">
|
||||||
<span className="mb-3 grid size-[46px] place-items-center rounded-[13px] bg-[#eff6fd] text-[#6796c5]">
|
<span className="mb-3 grid size-[46px] place-items-center rounded-[13px] bg-[#eff6fd] text-[#6796c5]">
|
||||||
<Icon name="script" size={24} />
|
<FileText size={24} />
|
||||||
</span>
|
</span>
|
||||||
<strong className="text-xs text-[#637286]">
|
<strong className="text-xs text-[#637286]">
|
||||||
{keyword ? "没有匹配脚本" : "还没有构建脚本"}
|
{keyword ? "没有匹配脚本" : "还没有构建脚本"}
|
||||||
@@ -273,7 +273,7 @@ export function ScriptExplorer({
|
|||||||
className="inline-flex cursor-pointer items-center gap-1 rounded-md border border-[#bcd6ee] bg-white px-[11px] py-[7px] text-[11px] text-[#176cc1]"
|
className="inline-flex cursor-pointer items-center gap-1 rounded-md border border-[#bcd6ee] bg-white px-[11px] py-[7px] text-[11px] text-[#176cc1]"
|
||||||
onClick={() => onOpenCreateDialog()}
|
onClick={() => onOpenCreateDialog()}
|
||||||
>
|
>
|
||||||
<Icon name="plus" size={15} />
|
<Plus size={15} />
|
||||||
新建脚本
|
新建脚本
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,4 +1,15 @@
|
|||||||
import Icon from "../../components/common/Icon";
|
import {
|
||||||
|
BookOpen,
|
||||||
|
Check,
|
||||||
|
ChevronRight,
|
||||||
|
ExternalLink,
|
||||||
|
FileCode,
|
||||||
|
Info,
|
||||||
|
Lock,
|
||||||
|
Plus,
|
||||||
|
RefreshCw,
|
||||||
|
X,
|
||||||
|
} from "lucide-react";
|
||||||
import type {
|
import type {
|
||||||
ActiveEditSession,
|
ActiveEditSession,
|
||||||
LatestVersion,
|
LatestVersion,
|
||||||
@@ -401,6 +412,8 @@ export function ScriptWorkspace({
|
|||||||
}
|
}
|
||||||
}, [script.script_id, script.workspace_id, isReadOnlyMode, readOnlyRefreshVersion]);
|
}, [script.script_id, script.workspace_id, isReadOnlyMode, readOnlyRefreshVersion]);
|
||||||
|
|
||||||
|
const ScriptTypeIcon = scriptIcon(script);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="relative flex h-[43px] min-h-[43px] items-stretch overflow-hidden border-b border-[#e5ebf1] bg-[#f7f9fb]">
|
<div className="relative flex h-[43px] min-h-[43px] items-stretch overflow-hidden border-b border-[#e5ebf1] bg-[#f7f9fb]">
|
||||||
@@ -410,7 +423,7 @@ export function ScriptWorkspace({
|
|||||||
aria-label="向左滚动"
|
aria-label="向左滚动"
|
||||||
onClick={() => scroll("left")}
|
onClick={() => scroll("left")}
|
||||||
>
|
>
|
||||||
<Icon name="chevron" size={16} />
|
<ChevronRight size={16} />
|
||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
className="tabbar-scroll flex flex-1 overflow-x-auto overflow-y-hidden scroll-smooth"
|
className="tabbar-scroll flex flex-1 overflow-x-auto overflow-y-hidden scroll-smooth"
|
||||||
@@ -432,10 +445,9 @@ export function ScriptWorkspace({
|
|||||||
<span
|
<span
|
||||||
className={`grid size-[25px] shrink-0 place-items-center rounded-[5px] ${fileIconTone(tab.scriptType)}`}
|
className={`grid size-[25px] shrink-0 place-items-center rounded-[5px] ${fileIconTone(tab.scriptType)}`}
|
||||||
>
|
>
|
||||||
<Icon
|
{tab.scriptType === "notebook"
|
||||||
name={tab.scriptType === "notebook" ? "notebook" : "python"}
|
? <BookOpen size={16} />
|
||||||
size={16}
|
: <FileCode size={16} />}
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
<span className="min-w-0 flex-1 truncate">{tab.scriptName}</span>
|
<span className="min-w-0 flex-1 truncate">{tab.scriptName}</span>
|
||||||
<button
|
<button
|
||||||
@@ -444,7 +456,7 @@ export function ScriptWorkspace({
|
|||||||
aria-label="关闭标签"
|
aria-label="关闭标签"
|
||||||
onClick={(event) => onClose(tab.scriptId, event)}
|
onClick={(event) => onClose(tab.scriptId, event)}
|
||||||
>
|
>
|
||||||
<Icon name="close" size={14} />
|
<X size={14} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -454,7 +466,7 @@ export function ScriptWorkspace({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={onNewTab}
|
onClick={onNewTab}
|
||||||
>
|
>
|
||||||
<Icon name="plus" size={17} />
|
<Plus size={17} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@@ -463,7 +475,7 @@ export function ScriptWorkspace({
|
|||||||
aria-label="向右滚动"
|
aria-label="向右滚动"
|
||||||
onClick={() => scroll("right")}
|
onClick={() => scroll("right")}
|
||||||
>
|
>
|
||||||
<Icon name="chevron" size={16} />
|
<ChevronRight size={16} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -472,10 +484,10 @@ export function ScriptWorkspace({
|
|||||||
<span
|
<span
|
||||||
className={`grid size-[25px] shrink-0 place-items-center rounded-[5px] ${fileIconTone(script.script_type)}`}
|
className={`grid size-[25px] shrink-0 place-items-center rounded-[5px] ${fileIconTone(script.script_type)}`}
|
||||||
>
|
>
|
||||||
<Icon name={scriptIcon(script)} size={17} />
|
<ScriptTypeIcon size={17} />
|
||||||
</span>
|
</span>
|
||||||
<span>工作副本</span>
|
<span>工作副本</span>
|
||||||
<Icon name="chevron" size={13} />
|
<ChevronRight size={13} />
|
||||||
<strong className="max-w-[300px] truncate text-[#45576b]">
|
<strong className="max-w-[300px] truncate text-[#45576b]">
|
||||||
{script.script_name}
|
{script.script_name}
|
||||||
</strong>
|
</strong>
|
||||||
@@ -561,7 +573,7 @@ export function ScriptWorkspace({
|
|||||||
这个锁只在本浏览器当前 tab 内有效,不能阻止隐身模式 / 其它浏览器同时编辑。 */}
|
这个锁只在本浏览器当前 tab 内有效,不能阻止隐身模式 / 其它浏览器同时编辑。 */}
|
||||||
{isEditing && (
|
{isEditing && (
|
||||||
<div className="flex shrink-0 items-center justify-center gap-2 border-b border-[#c9def9] bg-[#e7f1ff] px-4 py-1.5 text-xs font-medium text-[#1f4e8a]">
|
<div className="flex shrink-0 items-center justify-center gap-2 border-b border-[#c9def9] bg-[#e7f1ff] px-4 py-1.5 text-xs font-medium text-[#1f4e8a]">
|
||||||
<Icon name="info" size={14} />
|
<Info size={14} />
|
||||||
<span>
|
<span>
|
||||||
本地编辑锁——关闭标签页、刷新页面或换浏览器后失效,不会阻止他人同时编辑。
|
本地编辑锁——关闭标签页、刷新页面或换浏览器后失效,不会阻止他人同时编辑。
|
||||||
</span>
|
</span>
|
||||||
@@ -574,7 +586,7 @@ export function ScriptWorkspace({
|
|||||||
<div style={{ display: isReadOnlyMode ? 'block' : 'none', height: '100%' }}>
|
<div style={{ display: isReadOnlyMode ? 'block' : 'none', height: '100%' }}>
|
||||||
<div className="flex h-full flex-col overflow-hidden bg-[#f8f9fa]">
|
<div className="flex h-full flex-col overflow-hidden bg-[#f8f9fa]">
|
||||||
<div className="mb-1.5 flex shrink-0 items-center justify-center gap-2 border-b border-[#e0e0e0] bg-[#fff3cd] px-4 py-2 text-[13px] font-medium text-[#856404]">
|
<div className="mb-1.5 flex shrink-0 items-center justify-center gap-2 border-b border-[#e0e0e0] bg-[#fff3cd] px-4 py-2 text-[13px] font-medium text-[#856404]">
|
||||||
<Icon name="lock" size={16} />
|
<Lock size={16} />
|
||||||
<span>此文件已被锁定,您当前处于只读模式</span>
|
<span>此文件已被锁定,您当前处于只读模式</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="min-h-0 flex-1 overflow-y-auto">
|
<div className="min-h-0 flex-1 overflow-y-auto">
|
||||||
@@ -585,7 +597,7 @@ export function ScriptWorkspace({
|
|||||||
</div>
|
</div>
|
||||||
) : readOnlyError ? (
|
) : readOnlyError ? (
|
||||||
<div className="flex min-h-[200px] flex-col items-center justify-center gap-3 p-5 text-center text-sm text-[#dc3545]">
|
<div className="flex min-h-[200px] flex-col items-center justify-center gap-3 p-5 text-center text-sm text-[#dc3545]">
|
||||||
<Icon name="info" size={28} />
|
<Info size={28} />
|
||||||
<strong className="text-base">加载失败</strong>
|
<strong className="text-base">加载失败</strong>
|
||||||
<p className="m-0 text-[#666]">{readOnlyError}</p>
|
<p className="m-0 text-[#666]">{readOnlyError}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -712,7 +724,7 @@ export function ScriptWorkspace({
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{openError
|
{openError
|
||||||
? <Icon name="info" size={28} />
|
? <Info size={28} />
|
||||||
: <span className="button-spinner button-spinner--blue size-6 border-[3px]" />}
|
: <span className="button-spinner button-spinner--blue size-6 border-[3px]" />}
|
||||||
</div>
|
</div>
|
||||||
<strong className="text-base text-[#1d344d]">
|
<strong className="text-base text-[#1d344d]">
|
||||||
@@ -732,7 +744,7 @@ export function ScriptWorkspace({
|
|||||||
>
|
>
|
||||||
{editBusy
|
{editBusy
|
||||||
? <span className="button-spinner button-spinner--blue" />
|
? <span className="button-spinner button-spinner--blue" />
|
||||||
: <Icon name="refresh" size={16} />}
|
: <RefreshCw size={16} />}
|
||||||
{editBusy ? "正在重试…" : "重试打开"}
|
{editBusy ? "正在重试…" : "重试打开"}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
@@ -763,7 +775,7 @@ export function ScriptWorkspace({
|
|||||||
>
|
>
|
||||||
{editBusy
|
{editBusy
|
||||||
? <span className="button-spinner button-spinner--blue" />
|
? <span className="button-spinner button-spinner--blue" />
|
||||||
: <Icon name="external" size={17} />}
|
: <ExternalLink size={17} />}
|
||||||
{editBusy
|
{editBusy
|
||||||
? "正在准备编辑器…"
|
? "正在准备编辑器…"
|
||||||
: isEditing
|
: isEditing
|
||||||
@@ -817,7 +829,7 @@ export function ScriptWorkspace({
|
|||||||
|
|
||||||
<div className="mt-3 flex flex-wrap items-center gap-x-5 gap-y-2 px-1 text-[11px] text-[#7a8b9c]">
|
<div className="mt-3 flex flex-wrap items-center gap-x-5 gap-y-2 px-1 text-[11px] text-[#7a8b9c]">
|
||||||
<span className="inline-flex items-center gap-[5px] text-[#25875e]">
|
<span className="inline-flex items-center gap-[5px] text-[#25875e]">
|
||||||
<Icon name="check" size={15} />
|
<Check size={15} />
|
||||||
</span>
|
</span>
|
||||||
<span>SHA-256 {shortHash(script.content_hash)}</span>
|
<span>SHA-256 {shortHash(script.content_hash)}</span>
|
||||||
<span>
|
<span>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { useAuth } from "../../context/AuthContext";
|
|||||||
import { CreateFolderModal } from "./CreateFolderModal";
|
import { CreateFolderModal } from "./CreateFolderModal";
|
||||||
import { CreateScriptModal } from "./CreateScriptModal";
|
import { CreateScriptModal } from "./CreateScriptModal";
|
||||||
import { DataResourceUploadModal } from "./DataResourceUploadModal";
|
import { DataResourceUploadModal } from "./DataResourceUploadModal";
|
||||||
import Icon from "../../components/common/Icon";
|
|
||||||
import { WelcomePanel } from "./WelcomePanel";
|
import { WelcomePanel } from "./WelcomePanel";
|
||||||
import { PublishModal } from "./PublishModal";
|
import { PublishModal } from "./PublishModal";
|
||||||
import { ScriptExplorer } from "./ScriptExplorer";
|
import { ScriptExplorer } from "./ScriptExplorer";
|
||||||
|
|||||||
@@ -1,4 +1,14 @@
|
|||||||
import Icon from "../../components/common/Icon";
|
import {
|
||||||
|
BookOpen,
|
||||||
|
Database,
|
||||||
|
FileCode,
|
||||||
|
FileText,
|
||||||
|
Folder,
|
||||||
|
Lock,
|
||||||
|
Unlock,
|
||||||
|
Upload,
|
||||||
|
X,
|
||||||
|
} from "lucide-react";
|
||||||
import type { ScriptItem, ScriptType } from "../../services/api";
|
import type { ScriptItem, ScriptType } from "../../services/api";
|
||||||
|
|
||||||
type ContextMenuState = {
|
type ContextMenuState = {
|
||||||
@@ -45,6 +55,7 @@ export function TreeContextMenu({
|
|||||||
if (!contextMenu) return null;
|
if (!contextMenu) return null;
|
||||||
|
|
||||||
const isDataResource = !!contextMenu.script?.script_id.startsWith("data:");
|
const isDataResource = !!contextMenu.script?.script_id.startsWith("data:");
|
||||||
|
const LockToggleIcon = contextMenu.script?.is_locked ? Unlock : Lock;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -71,7 +82,7 @@ export function TreeContextMenu({
|
|||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon name="database" size={16} />
|
<Database size={16} />
|
||||||
复制 Jupyter 路径
|
复制 Jupyter 路径
|
||||||
</button>
|
</button>
|
||||||
{onRemoveResource && (
|
{onRemoveResource && (
|
||||||
@@ -85,7 +96,7 @@ export function TreeContextMenu({
|
|||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon name="close" size={16} />
|
<X size={16} />
|
||||||
删除资源
|
删除资源
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
@@ -101,7 +112,7 @@ export function TreeContextMenu({
|
|||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon name="script" size={16} />
|
<FileText size={16} />
|
||||||
打开文件
|
打开文件
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -113,10 +124,7 @@ export function TreeContextMenu({
|
|||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon
|
<LockToggleIcon size={16} />
|
||||||
name={contextMenu.script.is_locked ? "unlock" : "lock"}
|
|
||||||
size={16}
|
|
||||||
/>
|
|
||||||
{contextMenu.script.is_locked ? "解锁文件" : "锁定文件"}
|
{contextMenu.script.is_locked ? "解锁文件" : "锁定文件"}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -125,7 +133,7 @@ export function TreeContextMenu({
|
|||||||
role="menuitem"
|
role="menuitem"
|
||||||
onClick={() => onRemoveScript(contextMenu.script!)}
|
onClick={() => onRemoveScript(contextMenu.script!)}
|
||||||
>
|
>
|
||||||
<Icon name="close" size={16} />
|
<X size={16} />
|
||||||
删除文件
|
删除文件
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
@@ -139,7 +147,7 @@ export function TreeContextMenu({
|
|||||||
className={menuItemClass}
|
className={menuItemClass}
|
||||||
onClick={() => onOpenCreateDialog(contextMenu.path, "notebook")}
|
onClick={() => onOpenCreateDialog(contextMenu.path, "notebook")}
|
||||||
>
|
>
|
||||||
<Icon name="notebook" size={16} />
|
<BookOpen size={16} />
|
||||||
新建 Notebook
|
新建 Notebook
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -148,7 +156,7 @@ export function TreeContextMenu({
|
|||||||
className={menuItemClass}
|
className={menuItemClass}
|
||||||
onClick={() => onOpenCreateDialog(contextMenu.path, "python")}
|
onClick={() => onOpenCreateDialog(contextMenu.path, "python")}
|
||||||
>
|
>
|
||||||
<Icon name="python" size={16} />
|
<FileCode size={16} />
|
||||||
新建 Python 文件
|
新建 Python 文件
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -157,7 +165,7 @@ export function TreeContextMenu({
|
|||||||
className={menuItemClass}
|
className={menuItemClass}
|
||||||
onClick={() => onOpenFolderDialog(contextMenu.path)}
|
onClick={() => onOpenFolderDialog(contextMenu.path)}
|
||||||
>
|
>
|
||||||
<Icon name="folder" size={16} />
|
<Folder size={16} />
|
||||||
新建文件夹
|
新建文件夹
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -166,7 +174,7 @@ export function TreeContextMenu({
|
|||||||
className={menuItemClass}
|
className={menuItemClass}
|
||||||
onClick={() => onChooseUpload(contextMenu.path)}
|
onClick={() => onChooseUpload(contextMenu.path)}
|
||||||
>
|
>
|
||||||
<Icon name="upload" size={16} />
|
<Upload size={16} />
|
||||||
上传到此处
|
上传到此处
|
||||||
</button>
|
</button>
|
||||||
{contextMenu.kind === "directory" && (
|
{contextMenu.kind === "directory" && (
|
||||||
@@ -178,7 +186,7 @@ export function TreeContextMenu({
|
|||||||
role="menuitem"
|
role="menuitem"
|
||||||
onClick={() => onRemoveDirectory(contextMenu.path)}
|
onClick={() => onRemoveDirectory(contextMenu.path)}
|
||||||
>
|
>
|
||||||
<Icon name="close" size={16} />
|
<X size={16} />
|
||||||
删除文件夹
|
删除文件夹
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import Icon from "../../components/common/Icon";
|
import { Check } from "lucide-react";
|
||||||
import type { StableVersion } from "../../services/api";
|
import type { StableVersion } from "../../services/api";
|
||||||
import { copyToClipboard } from "../../lib/clipboard";
|
import { copyToClipboard } from "../../lib/clipboard";
|
||||||
import {
|
import {
|
||||||
@@ -30,7 +30,7 @@ export function VersionReceiptModal({
|
|||||||
bodyClassName="overflow-visible"
|
bodyClassName="overflow-visible"
|
||||||
>
|
>
|
||||||
<div className="mx-auto mb-3.5 grid size-[58px] place-items-center rounded-full bg-gradient-to-br from-[#24b67d] to-[#149566] text-white shadow-[0_9px_24px_rgb(25_157_104/25%)]">
|
<div className="mx-auto mb-3.5 grid size-[58px] place-items-center rounded-full bg-gradient-to-br from-[#24b67d] to-[#149566] text-white shadow-[0_9px_24px_rgb(25_157_104/25%)]">
|
||||||
<Icon name="check" size={28} />
|
<Check size={28} />
|
||||||
</div>
|
</div>
|
||||||
<span className={modalEyebrowClass}>STABLE VERSION</span>
|
<span className={modalEyebrowClass}>STABLE VERSION</span>
|
||||||
<h2 className="mb-2 mt-[5px] text-xl text-[#24374a]">
|
<h2 className="mb-2 mt-[5px] text-xl text-[#24374a]">
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
import { type MouseEvent as ReactMouseEvent, useEffect, useMemo } from "react";
|
import { type MouseEvent as ReactMouseEvent, useEffect, useMemo } from "react";
|
||||||
|
|
||||||
import Icon from "../../components/common/Icon";
|
import {
|
||||||
|
BookOpen,
|
||||||
|
ChevronRight,
|
||||||
|
Database,
|
||||||
|
FileCode,
|
||||||
|
Folder,
|
||||||
|
Lock,
|
||||||
|
type LucideIcon,
|
||||||
|
} from "lucide-react";
|
||||||
import type {
|
import type {
|
||||||
ScriptItem,
|
ScriptItem,
|
||||||
WorkspaceDirectory,
|
WorkspaceDirectory,
|
||||||
@@ -52,8 +60,8 @@ function formatTime(value: string) {
|
|||||||
}).format(new Date(value));
|
}).format(new Date(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function scriptIcon(item: ScriptItem) {
|
export function scriptIcon(item: Pick<ScriptItem, "script_type">): LucideIcon {
|
||||||
return item.script_type === "notebook" ? "notebook" : "python";
|
return item.script_type === "notebook" ? BookOpen : FileCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ownedScriptPath(item: ScriptItem) {
|
function ownedScriptPath(item: ScriptItem) {
|
||||||
@@ -158,10 +166,10 @@ export function WorkspaceTreeGroup({
|
|||||||
open ? "rotate-90" : ""
|
open ? "rotate-90" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Icon name="chevron" size={14} />
|
<ChevronRight size={14} />
|
||||||
</span>
|
</span>
|
||||||
<span className="inline-flex text-[#eab434] [&_svg]:fill-[#f7c84a] [&_svg]:stroke-[#e0a828]">
|
<span className="inline-flex text-[#eab434] [&_svg]:fill-[#f7c84a] [&_svg]:stroke-[#e0a828]">
|
||||||
<Icon name="folder" size={17} />
|
<Folder size={17} />
|
||||||
</span>
|
</span>
|
||||||
<span>{title}</span>
|
<span>{title}</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -242,6 +250,7 @@ function WorkspaceTreeItems({
|
|||||||
{childScripts.map((item) => {
|
{childScripts.map((item) => {
|
||||||
const isActive = selectedId === item.script_id;
|
const isActive = selectedId === item.script_id;
|
||||||
const isData = item.script_id.startsWith("data:");
|
const isData = item.script_id.startsWith("data:");
|
||||||
|
const ItemIcon = isData ? Database : scriptIcon(item);
|
||||||
const iconTone = isData
|
const iconTone = isData
|
||||||
? "text-[#5a8f6a] bg-[#eef8f1]"
|
? "text-[#5a8f6a] bg-[#eef8f1]"
|
||||||
: item.script_type === "notebook"
|
: item.script_type === "notebook"
|
||||||
@@ -273,7 +282,7 @@ function WorkspaceTreeItems({
|
|||||||
: undefined}
|
: undefined}
|
||||||
>
|
>
|
||||||
<span className={`grid size-[25px] shrink-0 place-items-center rounded-[5px] ${iconTone}`}>
|
<span className={`grid size-[25px] shrink-0 place-items-center rounded-[5px] ${iconTone}`}>
|
||||||
<Icon name={isData ? "database" : scriptIcon(item)} size={17} />
|
<ItemIcon size={17} />
|
||||||
</span>
|
</span>
|
||||||
<span className="flex min-w-0 flex-1 flex-col">
|
<span className="flex min-w-0 flex-1 flex-col">
|
||||||
<strong
|
<strong
|
||||||
@@ -288,7 +297,7 @@ function WorkspaceTreeItems({
|
|||||||
</span>
|
</span>
|
||||||
{item.is_locked && (
|
{item.is_locked && (
|
||||||
<span className="mr-1 inline-flex items-center justify-center text-[#c79a3a]" title="已锁定">
|
<span className="mr-1 inline-flex items-center justify-center text-[#c79a3a]" title="已锁定">
|
||||||
<Icon name="lock" size={12} />
|
<Lock size={12} />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{item.visibility !== "private" && (
|
{item.visibility !== "private" && (
|
||||||
@@ -343,10 +352,10 @@ function DirectoryBranch({
|
|||||||
open ? "rotate-90" : ""
|
open ? "rotate-90" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Icon name="chevron" size={13} />
|
<ChevronRight size={13} />
|
||||||
</span>
|
</span>
|
||||||
<span className="inline-flex shrink-0 text-[#e2aa27] [&_svg]:fill-[#f7c84a]">
|
<span className="inline-flex shrink-0 text-[#e2aa27] [&_svg]:fill-[#f7c84a]">
|
||||||
<Icon name="folder" size={17} />
|
<Folder size={17} />
|
||||||
</span>
|
</span>
|
||||||
<strong
|
<strong
|
||||||
className="truncate text-[11px] font-[650]"
|
className="truncate text-[11px] font-[650]"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { Collapsible } from "@base-ui/react/collapsible";
|
|||||||
|
|
||||||
import type { Route } from "./+types/platform";
|
import type { Route } from "./+types/platform";
|
||||||
import { Topbar } from "../components/common/Topbar";
|
import { Topbar } from "../components/common/Topbar";
|
||||||
import Icon from "../components/common/Icon";
|
import { BrandMark } from "../components/common/BrandMark";
|
||||||
import {
|
import {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
SidebarProvider,
|
SidebarProvider,
|
||||||
@@ -221,7 +221,7 @@ function AuthenticatedLayout() {
|
|||||||
className={`flex h-[70px] items-center gap-2.5 border-b border-[var(--sidebar-border)] text-white ${sidebarCollapsed ? "justify-center px-0" : "px-[17px]"}`}
|
className={`flex h-[70px] items-center gap-2.5 border-b border-[var(--sidebar-border)] text-white ${sidebarCollapsed ? "justify-center px-0" : "px-[17px]"}`}
|
||||||
>
|
>
|
||||||
<span className="grid size-9 shrink-0 place-items-center text-[#5ca9ff]">
|
<span className="grid size-9 shrink-0 place-items-center text-[#5ca9ff]">
|
||||||
<Icon name="brand" size={31} />
|
<BrandMark size={31} />
|
||||||
</span>
|
</span>
|
||||||
{!sidebarCollapsed && (
|
{!sidebarCollapsed && (
|
||||||
<span className="whitespace-nowrap text-[15px] font-bold tracking-[0.01em] max-xl:text-[13px]">
|
<span className="whitespace-nowrap text-[15px] font-bold tracking-[0.01em] max-xl:text-[13px]">
|
||||||
|
|||||||
Reference in New Issue
Block a user