Files
NetMesh/application/app/workTabSurface.ts
zhaolei 3c72efcb7f
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

191 lines
6.0 KiB
TypeScript

import {
fromEditorTabId,
isEditorTabId,
} from '../state/activeTabStore';
import { isPluginViewTabId } from '../state/pluginViewTabStore';
import { applyCustomAccentToTerminalTheme, resolveHostTerminalThemeId } from '../../domain/terminalAppearance';
import { collectSessionIds } from '../../domain/workspace';
import type { EditorTabChrome } from '../state/editorTabStore';
import type { Host, TerminalSession, TerminalTheme, Workspace } from '../../types';
function uniqueTabIds(tabIds: readonly string[]): string[] {
const seen = new Set<string>();
const uniqueIds: string[] = [];
for (const tabId of tabIds) {
if (!tabId || seen.has(tabId)) continue;
seen.add(tabId);
uniqueIds.push(tabId);
}
return uniqueIds;
}
export function isRootPageTabId(activeTabId: string): boolean {
return activeTabId === 'vault' || activeTabId === 'sftp';
}
/**
* Host edit from overlays (Quick Switcher): use the terminal work-surface
* HostDetailsPanel when a work tab is active; otherwise deep-link into Vault.
*/
export function shouldOpenHostEditOnWorkSurface(activeTabId: string): boolean {
return !isRootPageTabId(activeTabId) && !isPluginViewTabId(activeTabId);
}
export function buildOrderedWorkTabIds(
tabOrder: readonly string[],
allTabIds: readonly string[],
): string[] {
const uniqueAllTabIds = uniqueTabIds(allTabIds);
const allTabIdSet = new Set(uniqueAllTabIds);
const orderedIds = uniqueTabIds(tabOrder.filter((id) => allTabIdSet.has(id)));
const orderedIdSet = new Set(orderedIds);
const newIds = uniqueAllTabIds.filter((id) => !orderedIdSet.has(id));
return [...orderedIds, ...newIds];
}
export function reorderWorkTabIds(
tabOrder: readonly string[],
allTabIds: readonly string[],
draggedId: string,
targetId: string,
position: 'before' | 'after' = 'before',
): string[] {
if (draggedId === targetId) return buildOrderedWorkTabIds(tabOrder, allTabIds);
const currentOrder = buildOrderedWorkTabIds(tabOrder, allTabIds);
const draggedIndex = currentOrder.indexOf(draggedId);
const targetIndex = currentOrder.indexOf(targetId);
if (draggedIndex === -1 || targetIndex === -1) return [...tabOrder];
currentOrder.splice(draggedIndex, 1);
let nextTargetIndex = targetIndex;
if (draggedIndex < targetIndex) {
nextTargetIndex -= 1;
}
if (position === 'after') {
nextTargetIndex += 1;
}
currentOrder.splice(nextTargetIndex, 0, draggedId);
return currentOrder;
}
export function isHostTreeWorkTabSurface({
enabled,
activeTabId,
logViewIds = new Set(),
orderedTabs,
sessionIds,
workspaceIds,
}: {
enabled: boolean;
activeTabId: string;
logViewIds?: ReadonlySet<string>;
orderedTabs: readonly string[];
sessionIds: ReadonlySet<string>;
workspaceIds: ReadonlySet<string>;
}): boolean {
if (!enabled) return false;
if (isRootPageTabId(activeTabId)) return false;
if (isPluginViewTabId(activeTabId)) return false;
return orderedTabs.includes(activeTabId)
|| isEditorTabId(activeTabId)
|| logViewIds.has(activeTabId)
|| sessionIds.has(activeTabId)
|| workspaceIds.has(activeTabId);
}
export function isTerminalContentTabSurface({
activeTabId,
sessionIds,
workspaceIds,
}: {
activeTabId: string;
sessionIds: ReadonlySet<string>;
workspaceIds: ReadonlySet<string>;
}): boolean {
return sessionIds.has(activeTabId) || workspaceIds.has(activeTabId);
}
export function resolveWorkspaceTargetSession(
workspace: Workspace,
sessions: readonly TerminalSession[],
): TerminalSession | undefined {
const sessionById = new Map(sessions.map((session) => [session.id, session]));
return resolveWorkspaceTargetSessionFromMap(workspace, sessionById);
}
export function resolveWorkspaceTargetSessionFromMap(
workspace: Workspace,
sessionById: ReadonlyMap<string, TerminalSession>,
): TerminalSession | undefined {
const orderedSessionIds = collectSessionIds(workspace.root);
const workspaceSessionIdSet = new Set(orderedSessionIds);
const focusedSession = workspace.focusedSessionId
? sessionById.get(workspace.focusedSessionId)
: undefined;
const validFocusedSession = focusedSession && workspaceSessionIdSet.has(focusedSession.id)
? focusedSession
: undefined;
if (validFocusedSession) return validFocusedSession;
for (const sessionId of orderedSessionIds) {
const session = sessionById.get(sessionId);
if (session) return session;
}
return undefined;
}
export function resolveWorkTabActiveHostId({
activeTabId,
editorTabs,
sessions,
workspaces,
}: {
activeTabId: string;
editorTabs: readonly EditorTabChrome[];
sessions: readonly TerminalSession[];
workspaces: readonly Workspace[];
}): string | null {
if (isEditorTabId(activeTabId)) {
const editorId = fromEditorTabId(activeTabId);
return editorTabs.find((tab) => tab.id === editorId)?.hostId ?? null;
}
const activeSession = sessions.find((session) => session.id === activeTabId);
if (activeSession) return activeSession.hostId ?? null;
const activeWorkspace = workspaces.find((workspace) => workspace.id === activeTabId);
if (!activeWorkspace) return null;
const targetSession = resolveWorkspaceTargetSession(activeWorkspace, sessions);
return targetSession?.hostId ?? null;
}
export function resolveWorkTabHostTreeTheme({
activeHostId,
accentMode,
currentTerminalTheme,
customAccent,
followAppTerminalTheme,
hostById,
themeById,
}: {
activeHostId: string | null;
accentMode: 'theme' | 'custom';
currentTerminalTheme: TerminalTheme;
customAccent: string;
followAppTerminalTheme: boolean;
hostById: ReadonlyMap<string, Host>;
themeById: ReadonlyMap<string, TerminalTheme>;
}): TerminalTheme {
if (!activeHostId || followAppTerminalTheme) {
return applyCustomAccentToTerminalTheme(currentTerminalTheme, accentMode, customAccent);
}
const host = hostById.get(activeHostId) ?? null;
const themeId = resolveHostTerminalThemeId(host, currentTerminalTheme.id);
const baseTheme = themeById.get(themeId) ?? currentTerminalTheme;
return applyCustomAccentToTerminalTheme(baseTheme, accentMode, customAccent);
}