diff --git a/apps/yaak-proxy/Sidebar.tsx b/apps/yaak-proxy/Sidebar.tsx index 1568493c..da9852a4 100644 --- a/apps/yaak-proxy/Sidebar.tsx +++ b/apps/yaak-proxy/Sidebar.tsx @@ -1,9 +1,9 @@ import type { HttpExchange } from "@yaakapp-internal/proxy-lib"; -import { Tree } from "@yaakapp-internal/ui"; +import { selectedIdsFamily, Tree } from "@yaakapp-internal/ui"; import type { TreeNode } from "@yaakapp-internal/ui"; import { atom, useAtomValue } from "jotai"; import { atomFamily } from "jotai/utils"; -import { useCallback, useMemo } from "react"; +import { useCallback } from "react"; import { httpExchangesAtom } from "./store"; /** A node in the sidebar tree — either a domain or a path segment. */ @@ -17,11 +17,48 @@ const collapsedAtom = atomFamily((treeId: string) => atom>({}), ); +export const SIDEBAR_TREE_ID = "proxy-sidebar"; + const sidebarTreeAtom = atom>((get) => { const exchanges = get(httpExchangesAtom); return buildTree(exchanges); }); +/** Exchanges filtered by the currently selected sidebar node(s). */ +export const filteredExchangesAtom = atom((get) => { + const exchanges = get(httpExchangesAtom); + const tree = get(sidebarTreeAtom); + const selectedIds = get(selectedIdsFamily(SIDEBAR_TREE_ID)); + + // Nothing selected or root selected → show all + if (selectedIds.length === 0 || selectedIds.includes("root")) { + return exchanges; + } + + // Collect exchange IDs from all selected nodes + const allowedIds = new Set(); + const nodeMap = new Map(); + collectNodes(tree, nodeMap); + + for (const selectedId of selectedIds) { + const node = nodeMap.get(selectedId); + if (node) { + for (const id of node.exchangeIds) { + allowedIds.add(id); + } + } + } + + return exchanges.filter((ex) => allowedIds.has(ex.id)); +}); + +function collectNodes(node: TreeNode, map: Map) { + map.set(node.item.id, node.item); + for (const child of node.children ?? []) { + collectNodes(child, map); + } +} + /** * Build a domain → path-segment trie from a flat list of exchanges. * @@ -123,14 +160,24 @@ function buildTree(exchanges: HttpExchange[]): TreeNode { return node; } - // Sort domains alphabetically, add to root + // Add a "Domains" folder between root and domain nodes + const allExchangeIds = exchanges.map((ex) => ex.id); + const domainsFolder: TreeNode = { + item: { id: "domains", label: "Domains", exchangeIds: allExchangeIds }, + parent: rootNode, + depth: 1, + children: [], + }; + const sortedDomains = [...domainMap.values()].sort((a, b) => a.label.localeCompare(b.label), ); for (const domain of sortedDomains) { - rootNode.children!.push(toTreeNode(domain, rootNode, 1)); + domainsFolder.children!.push(toTreeNode(domain, domainsFolder, 2)); } + rootNode.children!.push(domainsFolder); + return rootNode; } @@ -148,7 +195,7 @@ function ItemInner({ item }: { item: SidebarItem }) { export function Sidebar() { const tree = useAtomValue(sidebarTreeAtom); - const treeId = "proxy-sidebar"; + const treeId = SIDEBAR_TREE_ID; const getItemKey = useCallback((item: SidebarItem) => item.id, []); diff --git a/apps/yaak-proxy/main.tsx b/apps/yaak-proxy/main.tsx index 9ff31c70..21713d1f 100644 --- a/apps/yaak-proxy/main.tsx +++ b/apps/yaak-proxy/main.tsx @@ -15,13 +15,13 @@ import { createStore, Provider, useAtomValue } from 'jotai'; import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import { ActionButton } from './ActionButton'; -import { Sidebar } from './Sidebar'; +import { filteredExchangesAtom, Sidebar } from './Sidebar'; import './main.css'; +import type { ProxyHeader } from '@yaakapp-internal/proxy-lib'; import { initHotkeys } from './hotkeys'; import { listen, rpc } from './rpc'; import { useRpcQueryWithEvent } from './rpc-hooks'; -import type { ProxyHeader } from '@yaakapp-internal/proxy-lib'; -import { applyChange, dataAtom, httpExchangesAtom, replaceAll } from './store'; +import { applyChange, dataAtom, replaceAll } from './store'; const queryClient = new QueryClient(); const jotaiStore = createStore(); @@ -43,7 +43,7 @@ listen('model_write', (payload) => { function App() { const osType = type(); - const exchanges = useAtomValue(httpExchangesAtom); + const exchanges = useAtomValue(filteredExchangesAtom); const { data: proxyState } = useRpcQueryWithEvent('get_proxy_state', {}, 'proxy_state_changed'); const isRunning = proxyState?.state === 'running'; @@ -136,7 +136,13 @@ function StatusBadge({ status, error }: { status: number | null; error: string | if (status == null) return ; const color = - status >= 500 ? 'text-danger' : status >= 400 ? 'text-warning' : status >= 300 ? 'text-notice' : 'text-success'; + status >= 500 + ? 'text-danger' + : status >= 400 + ? 'text-warning' + : status >= 300 + ? 'text-notice' + : 'text-success'; return {status}; }