Fix workspace shortcut navigation
This commit is contained in:
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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" });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user