Fix workspace shortcut navigation

This commit is contained in:
Mohamed Boudra
2026-04-20 18:06:09 +07:00
parent 95c1d1fe0f
commit 2cd296e38f
16 changed files with 513 additions and 155 deletions
@@ -0,0 +1,39 @@
import { describe, expect, it } from "vitest";
import type { HostProfile } from "@/types/host-connection";
import { resolveActiveHost } from "./active-host";
function host(serverId: string): HostProfile {
return {
serverId,
label: serverId,
lifecycle: {},
connections: [],
preferredConnectionId: null,
createdAt: "",
updatedAt: "",
};
}
describe("resolveActiveHost", () => {
it("uses the route host when it exists", () => {
expect(
resolveActiveHost({
hosts: [host("srv-1"), host("srv-2")],
pathname: "/h/srv-2/workspace/ws-main",
})?.serverId,
).toBe("srv-2");
});
it("falls back to the first host when the route host is missing", () => {
expect(
resolveActiveHost({
hosts: [host("srv-1"), host("srv-2")],
pathname: "/h/missing/workspace/ws-main",
})?.serverId,
).toBe("srv-1");
});
it("returns null when no hosts exist", () => {
expect(resolveActiveHost({ hosts: [], pathname: "/settings" })).toBeNull();
});
});
+20
View File
@@ -0,0 +1,20 @@
import type { HostProfile } from "@/types/host-connection";
import { parseServerIdFromPathname } from "@/utils/host-routes";
export function resolveActiveHost({
hosts,
pathname,
}: {
hosts: readonly HostProfile[];
pathname: string;
}): HostProfile | null {
const serverIdFromPath = parseServerIdFromPathname(pathname);
if (serverIdFromPath) {
const routeMatch = hosts.find((host) => host.serverId === serverIdFromPath);
if (routeMatch) {
return routeMatch;
}
}
return hosts[0] ?? null;
}
@@ -4,7 +4,7 @@ import type {
SidebarWorkspaceEntry,
} from "@/hooks/use-sidebar-workspaces-list";
import { buildSidebarShortcutModel } from "./sidebar-shortcuts";
import { buildSidebarShortcutModel, getRelativeSidebarShortcutTarget } from "./sidebar-shortcuts";
function workspace(input: {
serverId: string;
@@ -75,10 +75,6 @@ describe("buildSidebarShortcutModel", () => {
collapsedProjectKeys: new Set<string>(["p2"]),
});
expect(model.visibleTargets).toEqual([
{ serverId: "s1", workspaceId: "ws-main" },
{ serverId: "s1", workspaceId: "ws-feat-a" },
]);
expect(model.shortcutTargets).toEqual([
{ serverId: "s1", workspaceId: "ws-main" },
{ serverId: "s1", workspaceId: "ws-feat-a" },
@@ -104,8 +100,6 @@ describe("buildSidebarShortcutModel", () => {
collapsedProjectKeys: new Set<string>(),
});
expect(model.visibleTargets).toHaveLength(20);
expect(model.visibleTargets[19]).toEqual({ serverId: "s", workspaceId: "ws-20" });
expect(model.shortcutTargets).toHaveLength(9);
expect(model.shortcutTargets[0]).toEqual({ serverId: "s", workspaceId: "ws-1" });
expect(model.shortcutTargets[8]).toEqual({ serverId: "s", workspaceId: "ws-9" });
@@ -128,7 +122,68 @@ describe("buildSidebarShortcutModel", () => {
collapsedProjectKeys: new Set<string>(["p1"]),
});
expect(model.visibleTargets).toEqual([]);
expect(model.shortcutTargets).toEqual([]);
});
});
describe("getRelativeSidebarShortcutTarget", () => {
const targets = [
{ serverId: "s1", workspaceId: "ws-1" },
{ serverId: "s1", workspaceId: "ws-2" },
{ serverId: "s1", workspaceId: "ws-3" },
];
it("moves backward and forward through the numbered shortcut target list", () => {
expect(
getRelativeSidebarShortcutTarget({
targets,
currentTarget: { serverId: "s1", workspaceId: "ws-2" },
delta: -1,
}),
).toEqual({ serverId: "s1", workspaceId: "ws-1" });
expect(
getRelativeSidebarShortcutTarget({
targets,
currentTarget: { serverId: "s1", workspaceId: "ws-2" },
delta: 1,
}),
).toEqual({ serverId: "s1", workspaceId: "ws-3" });
});
it("wraps around the numbered shortcut target list", () => {
expect(
getRelativeSidebarShortcutTarget({
targets,
currentTarget: { serverId: "s1", workspaceId: "ws-1" },
delta: -1,
}),
).toEqual({ serverId: "s1", workspaceId: "ws-3" });
expect(
getRelativeSidebarShortcutTarget({
targets,
currentTarget: { serverId: "s1", workspaceId: "ws-3" },
delta: 1,
}),
).toEqual({ serverId: "s1", workspaceId: "ws-1" });
});
it("falls back to the nearest edge when the current route is not in the numbered list", () => {
expect(
getRelativeSidebarShortcutTarget({
targets,
currentTarget: { serverId: "s1", workspaceId: "ws-hidden" },
delta: 1,
}),
).toEqual({ serverId: "s1", workspaceId: "ws-1" });
expect(
getRelativeSidebarShortcutTarget({
targets,
currentTarget: { serverId: "s1", workspaceId: "ws-hidden" },
delta: -1,
}),
).toEqual({ serverId: "s1", workspaceId: "ws-3" });
});
});
+28 -5
View File
@@ -10,7 +10,6 @@ export interface SidebarShortcutWorkspaceTarget {
}
export interface SidebarShortcutModel {
visibleTargets: SidebarShortcutWorkspaceTarget[];
shortcutTargets: SidebarShortcutWorkspaceTarget[];
shortcutIndexByWorkspaceKey: Map<string, number>;
}
@@ -28,7 +27,6 @@ export function buildSidebarShortcutModel(input: {
shortcutLimit?: number;
}): SidebarShortcutModel {
const maxShortcuts = Math.max(0, Math.floor(input.shortcutLimit ?? 9));
const visibleTargets: SidebarShortcutWorkspaceTarget[] = [];
const shortcutTargets: SidebarShortcutWorkspaceTarget[] = [];
const shortcutIndexByWorkspaceKey = new Map<string, number>();
@@ -38,8 +36,6 @@ export function buildSidebarShortcutModel(input: {
}
for (const workspace of project.workspaces) {
visibleTargets.push(createShortcutTarget(workspace));
if (shortcutTargets.length >= maxShortcuts) {
continue;
}
@@ -50,5 +46,32 @@ export function buildSidebarShortcutModel(input: {
}
}
return { visibleTargets, shortcutTargets, shortcutIndexByWorkspaceKey };
return { shortcutTargets, shortcutIndexByWorkspaceKey };
}
export function getRelativeSidebarShortcutTarget(input: {
targets: readonly SidebarShortcutWorkspaceTarget[];
currentTarget: SidebarShortcutWorkspaceTarget | null;
delta: 1 | -1;
}): SidebarShortcutWorkspaceTarget | null {
if (input.targets.length === 0) {
return null;
}
if (!input.currentTarget) {
return input.targets[input.delta > 0 ? 0 : input.targets.length - 1] ?? null;
}
const currentTarget = input.currentTarget;
const currentIndex = input.targets.findIndex(
(target) =>
target.serverId === currentTarget.serverId &&
target.workspaceId === currentTarget.workspaceId,
);
if (currentIndex < 0) {
return input.targets[input.delta > 0 ? 0 : input.targets.length - 1] ?? null;
}
const nextIndex = (currentIndex + input.delta + input.targets.length) % input.targets.length;
return input.targets[nextIndex] ?? null;
}