mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 16:43:18 +08:00
feat(web): NetX workbench, UME-only UI, i18n, and header status
- Replace sidebar app with workbench home and module tabs (openOrFocusModule) - Remove legacy pages; add module registry, shared tab channel, WEB.md - Add zh/en i18n, global ToastProvider, restore oclaw bridge pill in header Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
664775927b
commit
282dbe75af
33 changed files with 1631 additions and 1677 deletions
14
web/src/hooks/useAppWindowRegistration.ts
Normal file
14
web/src/hooks/useAppWindowRegistration.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { useEffect } from "react";
|
||||
import { isWorkbenchPath, moduleIdFromPath } from "../config/modules";
|
||||
import { registerModuleWindow } from "../utils/moduleWindows";
|
||||
import { registerWorkbenchWindow } from "../utils/workbench";
|
||||
|
||||
/** Register workbench or module tab for cross-tab focus (see utils/tabChannel). */
|
||||
export function useAppWindowRegistration(pathname: string): void {
|
||||
useEffect(() => {
|
||||
if (isWorkbenchPath(pathname)) return registerWorkbenchWindow();
|
||||
const moduleId = moduleIdFromPath(pathname);
|
||||
if (moduleId) return registerModuleWindow(moduleId);
|
||||
return undefined;
|
||||
}, [pathname]);
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
export type ToastState = { type: "ok" | "error"; text: string } | null;
|
||||
|
||||
export function useToast(autoHideMs = 2600) {
|
||||
const [toast, setToast] = useState<ToastState>(null);
|
||||
|
||||
const showOk = useCallback((text: string) => setToast({ type: "ok", text }), []);
|
||||
const showError = useCallback((text: string) => setToast({ type: "error", text }), []);
|
||||
const clear = useCallback(() => setToast(null), []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!toast) return;
|
||||
const t = setTimeout(() => setToast(null), autoHideMs);
|
||||
return () => clearTimeout(t);
|
||||
}, [toast, autoHideMs]);
|
||||
|
||||
return { toast, showOk, showError, clear };
|
||||
}
|
||||
|
||||
44
web/src/hooks/useToast.tsx
Normal file
44
web/src/hooks/useToast.tsx
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from "react";
|
||||
|
||||
export type ToastState = { type: "ok" | "error"; text: string } | null;
|
||||
|
||||
type ToastApi = {
|
||||
toast: ToastState;
|
||||
showOk: (text: string) => void;
|
||||
showError: (text: string) => void;
|
||||
clear: () => void;
|
||||
};
|
||||
|
||||
const ToastContext = createContext<ToastApi | null>(null);
|
||||
|
||||
function useToastState(autoHideMs: number): ToastApi {
|
||||
const [toast, setToast] = useState<ToastState>(null);
|
||||
|
||||
const showOk = useCallback((text: string) => setToast({ type: "ok", text }), []);
|
||||
const showError = useCallback((text: string) => setToast({ type: "error", text }), []);
|
||||
const clear = useCallback(() => setToast(null), []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!toast) return;
|
||||
const timer = window.setTimeout(() => setToast(null), autoHideMs);
|
||||
return () => window.clearTimeout(timer);
|
||||
}, [toast, autoHideMs]);
|
||||
|
||||
return useMemo(() => ({ toast, showOk, showError, clear }), [toast, showOk, showError, clear]);
|
||||
}
|
||||
|
||||
export function ToastProvider({ children, autoHideMs = 2600 }: { children: ReactNode; autoHideMs?: number }) {
|
||||
const api = useToastState(autoHideMs);
|
||||
return (
|
||||
<ToastContext.Provider value={api}>
|
||||
{children}
|
||||
{api.toast ? <div className={`toast toast--${api.toast.type}`}>{api.toast.text}</div> : null}
|
||||
</ToastContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useToast(): ToastApi {
|
||||
const ctx = useContext(ToastContext);
|
||||
if (!ctx) throw new Error("useToast must be used within ToastProvider");
|
||||
return ctx;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue