/**
* 顶栏:logo / 新建 / 导入导出 / 环境 / Mock / 账号 / 主题 / 文档 / 设置。
*/
import { useEffect, useState } from "react";
import { useWorkspace } from "../workspaceStore";
import { ContextMenu, cx, type MenuItem } from "./common";
import { EnvBadge } from "./EnvBadge";
import { Icon, type IconName } from "./icons";
import { Logo } from "./Logo";
const NEW_ITEMS: { kind: string; label: string; icon: IconName }[] = [
{ kind: "http", label: "HTTP 请求", icon: "globe" },
{ kind: "graphql", label: "GraphQL", icon: "hexagon" },
{ kind: "websocket", label: "WebSocket", icon: "zap" },
{ kind: "sse", label: "SSE", icon: "radio" },
{ kind: "socketio", label: "Socket.IO", icon: "plug-zap" },
{ kind: "mqtt", label: "MQTT", icon: "mailbox" },
];
export function TopBar() {
const collections = useWorkspace((s) => s.collections);
const environments = useWorkspace((s) => s.environments);
const activeEnvironmentId = useWorkspace((s) => s.activeEnvironmentId);
const settings = useWorkspace((s) => s.settings);
const setActiveEnvironment = useWorkspace((s) => s.setActiveEnvironment);
const setTheme = useWorkspace((s) => s.setTheme);
const setDialog = useWorkspace((s) => s.setDialog);
const addRequest = useWorkspace((s) => s.addRequest);
const addFolder = useWorkspace((s) => s.addFolder);
const [newMenu, setNewMenu] = useState<{ x: number; y: number } | null>(null);
const [exportMenu, setExportMenu] = useState<{ x: number; y: number } | null>(null);
const [spaceMenu, setSpaceMenu] = useState<{ x: number; y: number } | null>(null);
const [accountMenu, setAccountMenu] = useState<{ x: number; y: number } | null>(null);
const account = useWorkspace((s) => s.account);
const switchSpace = useWorkspace((s) => s.switchSpace);
const syncNow = useWorkspace((s) => s.syncNow);
useEffect(() => {
document.title = "OpsxAPI · 接口工作台";
}, []);
const firstCollection = collections[0];
const quickCreate = (kind: string) => {
if (!firstCollection) return;
if (kind === "folder") addFolder(firstCollection.id, null);
else addRequest(firstCollection.id, null, kind as never);
};
const newItems: MenuItem[] = [
...NEW_ITEMS.map((i) => ({
label: i.label,
icon: ,
onClick: () => quickCreate(i.kind),
})),
{ type: "sep" as const },
{ label: "新建目录", icon: , onClick: () => quickCreate("folder") },
{ type: "sep" as const },
{ label: "选择位置新建…", icon: , onClick: () => setDialog({ type: "new", collectionId: firstCollection?.id ?? "", parentId: null }) },
];
const activeEnv = environments.find((e) => e.id === activeEnvironmentId);
const [envMenu, setEnvMenu] = useState<{ x: number; y: number } | null>(null);
const envItems: MenuItem[] = [
...environments.map((env) => ({
label: (
{env.name}
{env.id === activeEnvironmentId ? (
● 当前
) : (
{env.variables.filter((v) => v.enabled && v.key).length} 变量
)}
),
onClick: () => setActiveEnvironment(env.id),
})),
{ type: "sep" as const },
{
label: "不使用环境",
icon: ,
onClick: () => setActiveEnvironment(null),
},
{
label: "管理环境…",
icon: ,
onClick: () => setDialog({ type: "environments" }),
},
];
const activeSpace =
account.activeSpaceId === null ? null : account.spaces.find((sp) => sp.id === account.activeSpaceId) ?? null;
const spaceName = activeSpace
? `${activeSpace.teamId !== null ? "团队 · " : ""}${activeSpace.name}`
: "本地空间";
const spaceItems: MenuItem[] = [
{
label: (
本地空间
{account.activeSpaceId === null && ● 当前}
),
onClick: () => void switchSpace(null),
},
...account.spaces.map((sp) => ({
label: (
{sp.teamId !== null ? `团队 · ${sp.name}` : sp.name}
{sp.id === account.activeSpaceId ? (
● 当前
) : (
{new Date(sp.updatedAt * 1000).toLocaleDateString("zh-CN")}
)}
),
onClick: () => void switchSpace(sp.id),
})),
];
const accountItems: MenuItem[] = account.user
? [
...(account.activeSpaceId !== null
? [
{
label: account.syncing ? "同步中…" : account.lastSyncAt ? `已同步 ${new Date(account.lastSyncAt).toLocaleTimeString("zh-CN")}` : "立即同步到云端",
icon: ,
onClick: () => void syncNow(),
},
]
: []),
{ type: "sep" as const },
{
label: "团队管理…",
icon: ,
onClick: () => setDialog({ type: "team" }),
},
{
label: "退出登录",
icon: ,
onClick: () => void useWorkspace.getState().authLogout(),
},
]
: [];
return (
OpsxAPI
HTTP · GraphQL · WS · SSE · Socket.IO · MQTT
{account.user ? (
<>
>
) : (
)}
文档
setSpaceMenu(null)}
/>
setAccountMenu(null)}
/>
setNewMenu(null)}
/>
setEnvMenu(null)}
/>
({
label: c.name,
onClick: () => setDialog({ type: "export", collectionId: c.id }),
})),
}
: null
}
onClose={() => setExportMenu(null)}
/>
);
}