feat(send-command-panel): add support for "allWindows" target in session selection
This commit is contained in:
1 parent
147b0f7a51
commit
9ff4685c4e
4 files changed
+142
-44
No files matched your search
+53
-35
@@ -3403,45 +3403,62 @@ function App() {
|
||||
[tabsById, terminalWindows],
|
||||
);
|
||||
const sendCommandSessionTargets = useMemo(() => {
|
||||
if (!terminalWindows) return [];
|
||||
|
||||
const targets: {
|
||||
id: string;
|
||||
name: string;
|
||||
tabName: string;
|
||||
type: SessionType;
|
||||
}[] = [];
|
||||
const seen = new Set<string>();
|
||||
|
||||
const visit = (node: TerminalWindowNode) => {
|
||||
if (node.kind === "split") {
|
||||
visit(node.first);
|
||||
visit(node.second);
|
||||
return;
|
||||
const currentWindowLabel = getOwnerMainWindowLabel();
|
||||
const targetsById = new Map<
|
||||
string,
|
||||
{
|
||||
id: string;
|
||||
name: string;
|
||||
tabName: string;
|
||||
type: SessionType;
|
||||
ownerWindowLabel?: string | null;
|
||||
}
|
||||
>();
|
||||
|
||||
for (const tabId of node.tabIds) {
|
||||
const tab = tabsById.get(tabId);
|
||||
if (!tab) continue;
|
||||
|
||||
for (const pane of collectSessionPanes(tab.root)) {
|
||||
if (pane.paneKind !== "terminal" || !hasLiveSession(pane) || seen.has(pane.sessionId)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(pane.sessionId);
|
||||
targets.push({
|
||||
id: pane.sessionId,
|
||||
name: pane.name,
|
||||
tabName: getTabDisplayName(tab),
|
||||
type: pane.type,
|
||||
});
|
||||
if (terminalWindows) {
|
||||
const visit = (node: TerminalWindowNode) => {
|
||||
if (node.kind === "split") {
|
||||
visit(node.first);
|
||||
visit(node.second);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
visit(terminalWindows);
|
||||
return targets;
|
||||
}, [tabsById, terminalWindows]);
|
||||
for (const tabId of node.tabIds) {
|
||||
const tab = tabsById.get(tabId);
|
||||
if (!tab) continue;
|
||||
|
||||
for (const pane of collectSessionPanes(tab.root)) {
|
||||
if (pane.paneKind !== "terminal" || !hasLiveSession(pane)) {
|
||||
continue;
|
||||
}
|
||||
targetsById.set(pane.sessionId, {
|
||||
id: pane.sessionId,
|
||||
name: pane.name,
|
||||
tabName: getTabDisplayName(tab),
|
||||
type: pane.type,
|
||||
ownerWindowLabel: currentWindowLabel,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
visit(terminalWindows);
|
||||
}
|
||||
|
||||
for (const session of liveSessionsById?.values() ?? []) {
|
||||
if (!["SSH", "Local", "Telnet", "Serial"].includes(session.session_type)) continue;
|
||||
if (targetsById.has(session.id)) continue;
|
||||
targetsById.set(session.id, {
|
||||
id: session.id,
|
||||
name: session.name,
|
||||
tabName: session.name,
|
||||
type: session.session_type as SessionType,
|
||||
ownerWindowLabel: session.owner_window_label ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
return [...targetsById.values()];
|
||||
}, [liveSessionsById, tabsById, terminalWindows]);
|
||||
|
||||
const activeBottomPanel = uiConfig.show_serial_send_panel
|
||||
? "serialSend"
|
||||
@@ -3833,6 +3850,7 @@ function App() {
|
||||
activeNonSerialSessionId,
|
||||
activeNonSerialSessionIds,
|
||||
syncGroups,
|
||||
currentWindowLabel: getOwnerMainWindowLabel(),
|
||||
sessionTargets: sendCommandSessionTargets,
|
||||
sendCommandDraft,
|
||||
onSendCommandDraftConsumed: handleSendCommandDraftConsumed,
|
||||
|
||||
@@ -110,11 +110,13 @@ interface AppLayoutProps {
|
||||
activeNonSerialSessionId: string | null;
|
||||
activeNonSerialSessionIds: string[];
|
||||
syncGroups: SyncGroup[];
|
||||
currentWindowLabel: string;
|
||||
sessionTargets: {
|
||||
id: string;
|
||||
name: string;
|
||||
tabName: string;
|
||||
type: SessionType;
|
||||
ownerWindowLabel?: string | null;
|
||||
}[];
|
||||
sendCommandDraft: SendCommandPanelDraft | null;
|
||||
onSendCommandDraftConsumed: () => void;
|
||||
@@ -470,6 +472,7 @@ export default function AppLayout({
|
||||
currentShellSessionId={bottomPanel.activeNonSerialSessionId}
|
||||
shellSessionIds={bottomPanel.activeNonSerialSessionIds}
|
||||
syncGroups={bottomPanel.syncGroups}
|
||||
currentWindowLabel={bottomPanel.currentWindowLabel}
|
||||
sessionTargets={bottomPanel.sessionTargets}
|
||||
draft={bottomPanel.sendCommandDraft}
|
||||
onDraftConsumed={bottomPanel.onSendCommandDraftConsumed}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
type ChangeEvent,
|
||||
Fragment,
|
||||
type KeyboardEvent,
|
||||
type ReactNode,
|
||||
type UIEvent,
|
||||
@@ -17,6 +18,8 @@ import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
@@ -49,6 +52,7 @@ interface SendCommandPanelProps {
|
||||
currentShellSessionId: string | null;
|
||||
shellSessionIds: string[];
|
||||
syncGroups: SyncGroup[];
|
||||
currentWindowLabel: string;
|
||||
sessionTargets: SendCommandSessionTarget[];
|
||||
draft?: SendCommandPanelDraft | null;
|
||||
onDraftConsumed?: () => void;
|
||||
@@ -71,6 +75,7 @@ interface SendCommandSessionTarget {
|
||||
name: string;
|
||||
tabName: string;
|
||||
type: SessionType;
|
||||
ownerWindowLabel?: string | null;
|
||||
}
|
||||
|
||||
interface SendUnit {
|
||||
@@ -261,6 +266,7 @@ export default function SendCommandPanel({
|
||||
currentShellSessionId,
|
||||
shellSessionIds,
|
||||
syncGroups,
|
||||
currentWindowLabel,
|
||||
sessionTargets,
|
||||
draft,
|
||||
onDraftConsumed,
|
||||
@@ -331,6 +337,47 @@ export default function SendCommandPanel({
|
||||
[sessionTargetOptions],
|
||||
);
|
||||
|
||||
const allWindowSessionIds = useMemo(
|
||||
() => sessionTargetOptions.map((option) => option.session.id),
|
||||
[sessionTargetOptions],
|
||||
);
|
||||
|
||||
const sessionTargetGroups = useMemo(() => {
|
||||
const groups = new Map<
|
||||
string,
|
||||
{
|
||||
key: string;
|
||||
ownerWindowLabel: string | null;
|
||||
label: string;
|
||||
options: typeof sessionTargetOptions;
|
||||
}
|
||||
>();
|
||||
|
||||
for (const option of sessionTargetOptions) {
|
||||
const ownerWindowLabel = option.session.ownerWindowLabel ?? null;
|
||||
const isCurrentWindow = ownerWindowLabel === currentWindowLabel;
|
||||
const key = isCurrentWindow ? "__current__" : (ownerWindowLabel ?? "__unknown__");
|
||||
const label = isCurrentWindow
|
||||
? t("serialSend.currentWindowSessions", "Current window sessions")
|
||||
: t("serialSend.windowGroup", "Window: {{label}}", {
|
||||
label: ownerWindowLabel ?? t("serialSend.unknownWindow", "Unknown window"),
|
||||
});
|
||||
|
||||
const group = groups.get(key);
|
||||
if (group) {
|
||||
group.options.push(option);
|
||||
} else {
|
||||
groups.set(key, { key, ownerWindowLabel, label, options: [option] });
|
||||
}
|
||||
}
|
||||
|
||||
return [...groups.values()].sort((a, b) => {
|
||||
if (a.ownerWindowLabel === currentWindowLabel) return -1;
|
||||
if (b.ownerWindowLabel === currentWindowLabel) return 1;
|
||||
return a.label.localeCompare(b.label);
|
||||
});
|
||||
}, [currentWindowLabel, sessionTargetOptions, t]);
|
||||
|
||||
const groupTargetOptions = useMemo(() => {
|
||||
return syncGroups
|
||||
.filter((group) => group.enabled)
|
||||
@@ -388,6 +435,10 @@ export default function SendCommandPanel({
|
||||
return session ? getSessionTargetLabel(session) : t("serialSend.targetSessions", "Sessions");
|
||||
}
|
||||
|
||||
if (target === "allWindows") {
|
||||
return t("serialSend.allWindowSessions", "All window sessions");
|
||||
}
|
||||
|
||||
return target === "all"
|
||||
? t("serialSend.allSessions", "All sessions")
|
||||
: t("serialSend.currentSession", "Current session");
|
||||
@@ -402,6 +453,10 @@ export default function SendCommandPanel({
|
||||
return sessionTargetByValue.has(target) ? [getSessionTargetId(target)] : [];
|
||||
}
|
||||
|
||||
if (target === "allWindows") {
|
||||
return allWindowSessionIds;
|
||||
}
|
||||
|
||||
if (targetKind === "serial") {
|
||||
return serialSessionId ? [serialSessionId] : [];
|
||||
}
|
||||
@@ -412,6 +467,7 @@ export default function SendCommandPanel({
|
||||
: []
|
||||
: shellSessionIds;
|
||||
}, [
|
||||
allWindowSessionIds,
|
||||
currentTargetSessionId,
|
||||
groupTargetByValue,
|
||||
serialSessionId,
|
||||
@@ -460,6 +516,11 @@ export default function SendCommandPanel({
|
||||
return;
|
||||
}
|
||||
|
||||
if (target === "allWindows" && allWindowSessionIds.length === 0) {
|
||||
setTarget(fallbackTarget);
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetKind === "serial" && target === "all") {
|
||||
setTarget("current");
|
||||
return;
|
||||
@@ -471,6 +532,7 @@ export default function SendCommandPanel({
|
||||
setTarget("all");
|
||||
}
|
||||
}, [
|
||||
allWindowSessionIds.length,
|
||||
currentTargetSessionId,
|
||||
groupTargetByValue,
|
||||
sessionTargetByValue,
|
||||
@@ -906,6 +968,13 @@ export default function SendCommandPanel({
|
||||
{t("serialSend.allSessions", "All sessions")}
|
||||
</TargetMenuItem>
|
||||
)}
|
||||
<TargetMenuItem
|
||||
checked={target === "allWindows"}
|
||||
disabled={allWindowSessionIds.length === 0}
|
||||
onSelect={() => setTarget("allWindows")}
|
||||
>
|
||||
{t("serialSend.allWindowSessions", "All window sessions")}
|
||||
</TargetMenuItem>
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger
|
||||
disabled={sessionTargetOptions.length === 0}
|
||||
@@ -914,14 +983,22 @@ export default function SendCommandPanel({
|
||||
<span className="truncate">{t("serialSend.targetSessions", "Sessions")}</span>
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent className="max-h-[70vh] min-w-[14rem] max-w-[22rem] overflow-y-auto">
|
||||
{sessionTargetOptions.map((option) => (
|
||||
<TargetMenuItem
|
||||
key={option.session.id}
|
||||
checked={target === option.value}
|
||||
onSelect={() => setTarget(option.value)}
|
||||
>
|
||||
{getSessionTargetLabel(option.session)}
|
||||
</TargetMenuItem>
|
||||
{sessionTargetGroups.map((group, groupIndex) => (
|
||||
<Fragment key={group.key}>
|
||||
{groupIndex > 0 && <DropdownMenuSeparator />}
|
||||
<DropdownMenuLabel className="max-w-[20rem] truncate text-[0.625rem] text-muted-foreground">
|
||||
{group.label}
|
||||
</DropdownMenuLabel>
|
||||
{group.options.map((option) => (
|
||||
<TargetMenuItem
|
||||
key={option.session.id}
|
||||
checked={target === option.value}
|
||||
onSelect={() => setTarget(option.value)}
|
||||
>
|
||||
{getSessionTargetLabel(option.session)}
|
||||
</TargetMenuItem>
|
||||
))}
|
||||
</Fragment>
|
||||
))}
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
|
||||
@@ -4,7 +4,7 @@ export const OPEN_SEND_COMMAND_PANEL_EVENT = "nyaterm:open-send-command-panel";
|
||||
|
||||
export type SendCommandDataType = "text" | "hex";
|
||||
export type SendCommandMode = "line" | "character" | "byte" | "packet";
|
||||
export type SendCommandTarget = "current" | "all";
|
||||
export type SendCommandTarget = "current" | "all" | "allWindows";
|
||||
export type SendCommandCount = number | null;
|
||||
|
||||
export interface SendCommandPanelDraft {
|
||||
|
||||
Reference in new issue
Block a user