mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 22:55:24 +00:00
feat: Sidebar host click should focus existing tab instead of opening a new connection (#1289)
https://github.com/Termix-SSH/Support/issues/1289
This commit is contained in:
1 parent
f8a8139031
commit
afae2889cb
9 files changed
+109
-12
No files matched your search
@@ -153,6 +153,20 @@ export function CustomizeSidebarPanel({
|
||||
}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
label={t("hosts.focusExistingTab")}
|
||||
description={t("hosts.focusExistingTabDesc")}
|
||||
>
|
||||
<FakeSwitch
|
||||
checked={preferences.display.focusExistingTab}
|
||||
onChange={(v) =>
|
||||
update((prev) => ({
|
||||
...prev,
|
||||
display: { ...prev.display, focusExistingTab: v },
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
label={t("newUi.sidebar.userProfile.statusColors")}
|
||||
description={t("newUi.sidebar.userProfile.statusColorsDesc")}
|
||||
|
||||
@@ -192,7 +192,11 @@ export function HostsPanel({
|
||||
onOpenTab: (
|
||||
host: Host,
|
||||
type: TabType,
|
||||
options?: { endpointId?: string; label?: string },
|
||||
options?: {
|
||||
endpointId?: string;
|
||||
label?: string;
|
||||
forceNewTab?: boolean;
|
||||
},
|
||||
) => void;
|
||||
onEditHost: (host: Host) => void;
|
||||
hostTree?: HostFolder;
|
||||
@@ -1077,6 +1081,7 @@ export function HostsPanel({
|
||||
showTags={sidebarPrefs.display.showTags}
|
||||
openOnDoubleClick={sidebarPrefs.display.openOnDoubleClick}
|
||||
showFolderPaths={sidebarPrefs.display.showFolderPaths}
|
||||
focusExistingTab={sidebarPrefs.display.focusExistingTab}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -69,6 +69,7 @@ export function FolderItem({
|
||||
showTags = true,
|
||||
openOnDoubleClick = false,
|
||||
showFolderPaths = true,
|
||||
focusExistingTab = true,
|
||||
arrangeMode = false,
|
||||
isDragging = false,
|
||||
onReorderDrop,
|
||||
@@ -83,7 +84,11 @@ export function FolderItem({
|
||||
onOpenTab: (
|
||||
host: Host,
|
||||
type: TabType,
|
||||
options?: { endpointId?: string; label?: string },
|
||||
options?: {
|
||||
endpointId?: string;
|
||||
label?: string;
|
||||
forceNewTab?: boolean;
|
||||
},
|
||||
) => void;
|
||||
onEditHost?: (host: Host) => void;
|
||||
onShareHost?: (host: Host) => void;
|
||||
@@ -118,6 +123,8 @@ export function FolderItem({
|
||||
openOnDoubleClick?: boolean;
|
||||
/** When false, nested folders hide the parent-path breadcrumb before their name. */
|
||||
showFolderPaths?: boolean;
|
||||
/** When true, clicking a host with an already-open tab focuses it instead of opening a new one. */
|
||||
focusExistingTab?: boolean;
|
||||
/** When true (rearranging unlocked), the header can be dragged and its
|
||||
* top/bottom edges become reorder drop zones. The middle still accepts
|
||||
* hosts dropped into the folder. */
|
||||
@@ -342,6 +349,7 @@ export function FolderItem({
|
||||
showTags={showTags}
|
||||
openOnDoubleClick={openOnDoubleClick}
|
||||
showFolderPaths={showFolderPaths}
|
||||
focusExistingTab={focusExistingTab}
|
||||
arrangeMode={arrangeMode}
|
||||
onReorderDrop={onReorderDrop}
|
||||
onFolderDragStart={onFolderDragStart}
|
||||
@@ -378,6 +386,7 @@ export function FolderItem({
|
||||
trayTrigger={trayTrigger}
|
||||
showTags={showTags}
|
||||
openOnDoubleClick={openOnDoubleClick}
|
||||
focusExistingTab={focusExistingTab}
|
||||
arrangeMode={arrangeMode}
|
||||
onReorderDrop={
|
||||
onReorderDrop
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
Pin,
|
||||
Server,
|
||||
Share2,
|
||||
SquarePlus,
|
||||
Terminal,
|
||||
Trash2,
|
||||
Users,
|
||||
@@ -268,6 +269,7 @@ export function HostItem({
|
||||
trayTrigger = "hover",
|
||||
showTags = true,
|
||||
openOnDoubleClick = false,
|
||||
focusExistingTab = true,
|
||||
showResourceBars = true,
|
||||
showStatusStripes = true,
|
||||
rowActions = "full",
|
||||
@@ -285,7 +287,7 @@ export function HostItem({
|
||||
host: Host;
|
||||
onOpenTab: (
|
||||
type: TabType,
|
||||
options?: { endpointId?: string; label?: string },
|
||||
options?: { endpointId?: string; label?: string; forceNewTab?: boolean },
|
||||
) => void;
|
||||
onEditHost?: () => void;
|
||||
onShareHost?: () => void;
|
||||
@@ -312,6 +314,8 @@ export function HostItem({
|
||||
showTags?: boolean;
|
||||
/** Requires a double click to launch instead of a single click. */
|
||||
openOnDoubleClick?: boolean;
|
||||
/** When true, clicking a host with an already-open tab focuses it instead of opening a new one. */
|
||||
focusExistingTab?: boolean;
|
||||
/** Preset-driven: hides the CPU/RAM bars without changing density. */
|
||||
showResourceBars?: boolean;
|
||||
/** Preset-driven: hides the per-row status color stripe. */
|
||||
@@ -502,11 +506,14 @@ export function HostItem({
|
||||
: "terminal";
|
||||
const openHostTab = (
|
||||
type: TabType,
|
||||
options?: { endpointId?: string; label?: string },
|
||||
options?: { endpointId?: string; label?: string; forceNewTab?: boolean },
|
||||
) => {
|
||||
markTabSurfaceUsed(type);
|
||||
recordHostActionPreference(host.id, type);
|
||||
onOpenTab(type, options);
|
||||
onOpenTab(type, {
|
||||
...options,
|
||||
forceNewTab: options?.forceNewTab ?? !focusExistingTab,
|
||||
});
|
||||
};
|
||||
|
||||
// Mirrors getSshActions: the single Web UI entry carries an endpointId only
|
||||
@@ -848,6 +855,17 @@ export function HostItem({
|
||||
)}
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
{focusExistingTab && (
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
openHostTab(defaultAction, { forceNewTab: true });
|
||||
}}
|
||||
>
|
||||
<SquarePlus className="size-3.5 mr-2" />
|
||||
{t("hosts.openInNewTab")}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
{onEditHost && (
|
||||
<DropdownMenuItem
|
||||
@@ -1241,6 +1259,13 @@ export function HostItem({
|
||||
? "bg-muted/15"
|
||||
: ""
|
||||
} ${isMenuOpen ? "bg-muted/50" : ""} ${parentDragOver ? "ring-1 ring-inset ring-accent-brand bg-accent-brand/10" : ""} ${isDragging ? "opacity-40" : ""}`}
|
||||
onMouseDown={(e) => {
|
||||
// Middle-click always opens a new tab, matching browser tab behavior.
|
||||
if (e.button === 1 && !selectionMode && !isTouchOnly) {
|
||||
e.preventDefault();
|
||||
openHostTab(defaultAction, { forceNewTab: true });
|
||||
}
|
||||
}}
|
||||
onClick={(e) => {
|
||||
if (selectionMode) {
|
||||
onToggleSelect?.();
|
||||
@@ -1264,12 +1289,14 @@ export function HostItem({
|
||||
return;
|
||||
}
|
||||
if (openOnDoubleClick) return;
|
||||
openHostTab(defaultAction);
|
||||
const forceNewTab = e.ctrlKey || e.metaKey;
|
||||
openHostTab(defaultAction, forceNewTab ? { forceNewTab } : undefined);
|
||||
}}
|
||||
onDoubleClick={(e) => {
|
||||
if (selectionMode || isTouchOnly || !openOnDoubleClick) return;
|
||||
e.stopPropagation();
|
||||
openHostTab(defaultAction);
|
||||
const forceNewTab = e.ctrlKey || e.metaKey;
|
||||
openHostTab(defaultAction, forceNewTab ? { forceNewTab } : undefined);
|
||||
}}
|
||||
>
|
||||
{/* Status stripe */}
|
||||
|
||||
@@ -88,12 +88,17 @@ export function SidebarTree({
|
||||
showTags = true,
|
||||
openOnDoubleClick = false,
|
||||
showFolderPaths = true,
|
||||
focusExistingTab = true,
|
||||
}: {
|
||||
children: (Host | HostFolder)[];
|
||||
onOpenTab: (
|
||||
host: Host,
|
||||
type: TabType,
|
||||
options?: { endpointId?: string; label?: string },
|
||||
options?: {
|
||||
endpointId?: string;
|
||||
label?: string;
|
||||
forceNewTab?: boolean;
|
||||
},
|
||||
) => void;
|
||||
onEditHost: (host: Host) => void;
|
||||
onShareHost?: (host: Host) => void;
|
||||
@@ -111,6 +116,8 @@ export function SidebarTree({
|
||||
openOnDoubleClick?: boolean;
|
||||
/** When false, nested folders hide the parent-path breadcrumb before their name. */
|
||||
showFolderPaths?: boolean;
|
||||
/** When true, clicking a host with an already-open tab focuses it instead of opening a new one. */
|
||||
focusExistingTab?: boolean;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
// Knobs with no other owner come straight from the interface preset; the
|
||||
@@ -1000,6 +1007,7 @@ export function SidebarTree({
|
||||
showTags={showTags}
|
||||
openOnDoubleClick={openOnDoubleClick}
|
||||
showFolderPaths={showFolderPaths}
|
||||
focusExistingTab={focusExistingTab}
|
||||
arrangeMode={arrangeMode}
|
||||
isDragging={
|
||||
draggedReorderKey === `folder:${item.path ?? item.name}`
|
||||
@@ -1076,6 +1084,7 @@ export function SidebarTree({
|
||||
trayTrigger={trayTrigger}
|
||||
showTags={showTags}
|
||||
openOnDoubleClick={openOnDoubleClick}
|
||||
focusExistingTab={focusExistingTab}
|
||||
showResourceBars={showResourceBars}
|
||||
showStatusStripes={showStatusStripes}
|
||||
rowActions={rowActions}
|
||||
|
||||
Reference in new issue
Block a user