/** * 顶栏: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)} />
); }