mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: create scrollable proxy menu list (#23764)
This allows the proxy menu to scroll for very large numbers of proxies in the menu. <img width="334" height="802" alt="screenshot" src="https://github.com/user-attachments/assets/f0e45b9c-5b77-43da-b566-28d0572fd56b" />
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { fn, userEvent, within } from "storybook/test";
|
||||
import { getAuthorizationKey } from "#/api/queries/authCheck";
|
||||
import type * as TypesGen from "#/api/typesGenerated";
|
||||
import { AuthProvider } from "#/contexts/auth/AuthProvider";
|
||||
import { getPreferredProxy } from "#/contexts/ProxyContext";
|
||||
import { permissionChecks } from "#/modules/permissions";
|
||||
@@ -14,6 +15,50 @@ import {
|
||||
import { withDesktopViewport } from "#/testHelpers/storybook";
|
||||
import { ProxyMenu } from "./ProxyMenu";
|
||||
|
||||
const buildProxies = (count: number): TypesGen.WorkspaceProxy[] => {
|
||||
const seedProxy = MockWorkspaceProxies[0];
|
||||
const proxies: TypesGen.WorkspaceProxy[] = [];
|
||||
|
||||
for (let index = 0; index < count; index++) {
|
||||
const suffix = String(index + 1).padStart(12, "0");
|
||||
const id = `10000000-0000-4000-8000-${suffix}`;
|
||||
const isHealthy = index % 7 !== 0;
|
||||
|
||||
proxies.push({
|
||||
...seedProxy,
|
||||
id,
|
||||
name: `region-${index + 1}`,
|
||||
display_name: `Region ${index + 1}`,
|
||||
healthy: isHealthy,
|
||||
});
|
||||
}
|
||||
|
||||
return proxies;
|
||||
};
|
||||
|
||||
const buildLatencies = (
|
||||
proxies: TypesGen.WorkspaceProxy[],
|
||||
): typeof MockProxyLatencies => {
|
||||
const latencies: typeof MockProxyLatencies = {};
|
||||
|
||||
for (const [index, proxy] of proxies.entries()) {
|
||||
if (!proxy.healthy) {
|
||||
continue;
|
||||
}
|
||||
|
||||
latencies[proxy.id] = {
|
||||
accurate: true,
|
||||
latencyMS: 20 + index * 3,
|
||||
at: new Date(),
|
||||
nextHopProtocol: "h2",
|
||||
};
|
||||
}
|
||||
|
||||
return latencies;
|
||||
};
|
||||
|
||||
const manyProxies = buildProxies(45);
|
||||
|
||||
const defaultProxyContextValue = {
|
||||
latenciesLoaded: true,
|
||||
proxyLatencies: MockProxyLatencies,
|
||||
@@ -77,3 +122,18 @@ export const SingleProxy: Story = {
|
||||
await userEvent.click(canvas.getByRole("button"));
|
||||
},
|
||||
};
|
||||
|
||||
export const ManyProxiesOpened: Story = {
|
||||
args: {
|
||||
proxyContextValue: {
|
||||
...defaultProxyContextValue,
|
||||
proxies: manyProxies,
|
||||
proxyLatencies: buildLatencies(manyProxies),
|
||||
proxy: getPreferredProxy(manyProxies, undefined),
|
||||
},
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await userEvent.click(canvas.getByRole("button"));
|
||||
},
|
||||
};
|
||||
|
||||
@@ -124,51 +124,53 @@ export const ProxyMenu: FC<ProxyMenuProps> = ({ proxyContextValue }) => {
|
||||
)}
|
||||
|
||||
{proxyContextValue.proxies && (
|
||||
<DropdownMenuRadioGroup value={selectedProxy?.id}>
|
||||
{sortProxiesByLatency(proxyContextValue.proxies, latencies).map(
|
||||
(proxy) => (
|
||||
<DropdownMenuRadioItem
|
||||
value={proxy.id}
|
||||
key={proxy.id}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
if (!proxy.healthy) {
|
||||
toast.error(
|
||||
`Failed to select proxy "${proxy.display_name}".`,
|
||||
{
|
||||
description:
|
||||
"Please select a healthy workspace proxy.",
|
||||
},
|
||||
);
|
||||
closeMenu();
|
||||
return;
|
||||
}
|
||||
<div className="max-h-[calc(100vh-22rem)] -mr-2 pr-2 overflow-y-auto [scrollbar-width:thin] [scrollbar-color:hsl(var(--surface-quaternary))_transparent]">
|
||||
<DropdownMenuRadioGroup value={selectedProxy?.id}>
|
||||
{sortProxiesByLatency(proxyContextValue.proxies, latencies).map(
|
||||
(proxy) => (
|
||||
<DropdownMenuRadioItem
|
||||
value={proxy.id}
|
||||
key={proxy.id}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
if (!proxy.healthy) {
|
||||
toast.error(
|
||||
`Failed to select proxy "${proxy.display_name}".`,
|
||||
{
|
||||
description:
|
||||
"Please select a healthy workspace proxy.",
|
||||
},
|
||||
);
|
||||
closeMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
proxyContextValue.setProxy(proxy);
|
||||
closeMenu();
|
||||
}}
|
||||
>
|
||||
<div className="flex gap-3 items-center w-full">
|
||||
<div className="leading-none size-4">
|
||||
<img
|
||||
src={proxy.icon_url}
|
||||
alt=""
|
||||
className="object-contain size-full"
|
||||
proxyContextValue.setProxy(proxy);
|
||||
closeMenu();
|
||||
}}
|
||||
>
|
||||
<div className="flex gap-3 items-center w-full">
|
||||
<div className="leading-none size-4">
|
||||
<img
|
||||
src={proxy.icon_url}
|
||||
alt=""
|
||||
className="object-contain size-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{proxy.display_name}
|
||||
|
||||
<Latency
|
||||
className="ml-auto"
|
||||
latency={latencies?.[proxy.id]?.latencyMS}
|
||||
isLoading={proxyLatencyLoading(proxy)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{proxy.display_name}
|
||||
|
||||
<Latency
|
||||
className="ml-auto"
|
||||
latency={latencies?.[proxy.id]?.latencyMS}
|
||||
isLoading={proxyLatencyLoading(proxy)}
|
||||
/>
|
||||
</div>
|
||||
</DropdownMenuRadioItem>
|
||||
),
|
||||
)}
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuRadioItem>
|
||||
),
|
||||
)}
|
||||
</DropdownMenuRadioGroup>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
Reference in New Issue
Block a user