mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 00:50:46 +08:00
Preserve existing module tabs when focusing from the workbench.
Avoid window.open(url, namedWindow) reloads that wiped WebCRT sessions; reclaim named windows without navigating when already open. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
7effa7b2b3
commit
87018721a3
1 changed files with 46 additions and 15 deletions
|
|
@ -29,6 +29,16 @@ function moduleAckKey(moduleId: string): string {
|
|||
return `netx-mod-focus-ack-${moduleId}`;
|
||||
}
|
||||
|
||||
function resolveModulePath(moduleId: string, path: string): string {
|
||||
const mod = getModuleById(moduleId);
|
||||
const base = mod?.path ?? "/";
|
||||
// Prefer caller path so query strings like /webcrt?ne_id=... are preserved.
|
||||
if (path && (path === base || path.startsWith(`${base}?`) || path.startsWith(`${base}/`))) {
|
||||
return path;
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
export function registerModuleWindow(moduleId: string): () => void {
|
||||
const sharedName = moduleWindowName(moduleId);
|
||||
try {
|
||||
|
|
@ -50,15 +60,17 @@ export function registerModuleWindow(moduleId: string): () => void {
|
|||
}
|
||||
const targetPath = String(data.path || "").trim();
|
||||
if (targetPath) {
|
||||
// Compare pathname only for hard navigation. Same route + new query
|
||||
// must soft-navigate so React state (open tabs) survives.
|
||||
try {
|
||||
const url = new URL(targetPath, window.location.origin);
|
||||
const next = `${url.pathname}${url.search}${url.hash}`;
|
||||
const cur = `${window.location.pathname}${window.location.search}${window.location.hash}`;
|
||||
if (window.location.pathname !== url.pathname) {
|
||||
// Different module route — hard navigate.
|
||||
window.location.assign(next);
|
||||
} else if (cur !== next) {
|
||||
} else if (cur !== next && (url.search || url.hash)) {
|
||||
// Same pathname: soft-nav only when caller supplies query/hash
|
||||
// (e.g. /webcrt?ne_id=...). Bare /webcrt means focus-only — keep
|
||||
// current query and in-memory session state (no reload).
|
||||
window.history.pushState({}, "", next);
|
||||
window.dispatchEvent(new PopStateEvent("popstate"));
|
||||
}
|
||||
|
|
@ -75,12 +87,7 @@ export function registerModuleWindow(moduleId: string): () => void {
|
|||
}
|
||||
|
||||
export function openOrFocusModule({ moduleId, path }: ModuleWindowSpec): void {
|
||||
const mod = getModuleById(moduleId);
|
||||
const base = mod?.path ?? "/";
|
||||
// Prefer caller path so query strings like /webcrt?ne_id=... are preserved.
|
||||
const targetPath = path && (path === base || path.startsWith(`${base}?`) || path.startsWith(`${base}/`))
|
||||
? path
|
||||
: base;
|
||||
const targetPath = resolveModulePath(moduleId, path);
|
||||
const requestId = createFocusRequestId(`mod-${moduleId}`);
|
||||
const name = moduleWindowName(moduleId);
|
||||
|
||||
|
|
@ -91,20 +98,44 @@ export function openOrFocusModule({ moduleId, path }: ModuleWindowSpec): void {
|
|||
requestId,
|
||||
});
|
||||
|
||||
focusWindowSafe(window.open(targetPath, name));
|
||||
// Under the user-gesture: reclaim the named window WITHOUT passing a URL.
|
||||
// window.open(url, existingName) reloads that tab and kills WebCRT sessions.
|
||||
let handledByGesture = false;
|
||||
let win: Window | null = null;
|
||||
try {
|
||||
win = window.open("", name);
|
||||
} catch {
|
||||
win = null;
|
||||
}
|
||||
if (win && !win.closed) {
|
||||
handledByGesture = true;
|
||||
let needsNavigate = false;
|
||||
try {
|
||||
const href = String(win.location?.href || "");
|
||||
needsNavigate = !href || href === "about:blank";
|
||||
} catch {
|
||||
// Existing cross-context window — only focus; broadcast soft-nav handles path.
|
||||
needsNavigate = false;
|
||||
}
|
||||
if (needsNavigate) {
|
||||
try {
|
||||
win.location.href = targetPath;
|
||||
} catch {
|
||||
handledByGesture = false;
|
||||
}
|
||||
}
|
||||
focusWindowSafe(win);
|
||||
}
|
||||
|
||||
waitForFocusAck(moduleAckKey(moduleId), requestId, () => {
|
||||
if (handledByGesture) return;
|
||||
focusWindowSafe(window.open(targetPath, name));
|
||||
});
|
||||
}
|
||||
|
||||
/** Always open a fresh browser tab/window; never focus or merge with an existing module tab. */
|
||||
export function openNewModuleWindow({ moduleId, path }: ModuleWindowSpec): void {
|
||||
const mod = getModuleById(moduleId);
|
||||
const base = mod?.path ?? "/";
|
||||
const targetPath = path && (path === base || path.startsWith(`${base}?`) || path.startsWith(`${base}/`))
|
||||
? path
|
||||
: base;
|
||||
const targetPath = resolveModulePath(moduleId, path);
|
||||
const uniqueName = `${moduleWindowName(moduleId)}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
focusWindowSafe(window.open(targetPath, uniqueName));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue