mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-29 13:41:51 +02:00
Sidebar filtering
This commit is contained in:
@@ -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<Record<string, boolean>>({}),
|
||||
);
|
||||
|
||||
export const SIDEBAR_TREE_ID = "proxy-sidebar";
|
||||
|
||||
const sidebarTreeAtom = atom<TreeNode<SidebarItem>>((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<string>();
|
||||
const nodeMap = new Map<string, SidebarItem>();
|
||||
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<SidebarItem>, map: Map<string, SidebarItem>) {
|
||||
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<SidebarItem> {
|
||||
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<SidebarItem> = {
|
||||
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, []);
|
||||
|
||||
|
||||
@@ -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 <span className="text-xs text-text-subtlest">—</span>;
|
||||
|
||||
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 <span className={classNames('text-xs font-mono', color)}>{status}</span>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user