mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-10 19:16:55 +02:00
Generalized frontend model store (#193)
This commit is contained in:
@@ -5,29 +5,26 @@ import type {
|
||||
WebsocketRequest,
|
||||
Workspace,
|
||||
} from '@yaakapp-internal/models';
|
||||
import { getAnyModel, patchModelById } from '@yaakapp-internal/models';
|
||||
import classNames from 'classnames';
|
||||
import { useAtom, useAtomValue } from 'jotai';
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import { useKey, useKeyPressEvent } from 'react-use';
|
||||
import { upsertWebsocketRequest } from '../../commands/upsertWebsocketRequest';
|
||||
import { getActiveRequest } from '../../hooks/useActiveRequest';
|
||||
import { useActiveWorkspace } from '../../hooks/useActiveWorkspace';
|
||||
import { activeRequestIdAtom } from '../../hooks/useActiveRequestId';
|
||||
import { activeWorkspaceAtom } from '../../hooks/useActiveWorkspace';
|
||||
import { useCreateDropdownItems } from '../../hooks/useCreateDropdownItems';
|
||||
import { useDeleteAnyRequest } from '../../hooks/useDeleteAnyRequest';
|
||||
import { useHotKey } from '../../hooks/useHotKey';
|
||||
import { useSidebarHidden } from '../../hooks/useSidebarHidden';
|
||||
import { getSidebarCollapsedMap } from '../../hooks/useSidebarItemCollapsed';
|
||||
import { useUpdateAnyFolder } from '../../hooks/useUpdateAnyFolder';
|
||||
import { useUpdateAnyGrpcRequest } from '../../hooks/useUpdateAnyGrpcRequest';
|
||||
import { useUpdateAnyHttpRequest } from '../../hooks/useUpdateAnyHttpRequest';
|
||||
import { getWebsocketRequest } from '../../hooks/useWebsocketRequests';
|
||||
import { deleteModelWithConfirm } from '../../lib/deleteModelWithConfirm';
|
||||
import { jotaiStore } from '../../lib/jotai';
|
||||
import { router } from '../../lib/router';
|
||||
import { setWorkspaceSearchParams } from '../../lib/setWorkspaceSearchParams';
|
||||
import { ContextMenu } from '../core/Dropdown';
|
||||
import { GitDropdown } from '../GitDropdown';
|
||||
import { sidebarSelectedIdAtom, sidebarTreeAtom } from './SidebarAtoms';
|
||||
import type { SidebarItemProps } from './SidebarItem';
|
||||
import { SidebarItems } from './SidebarItems';
|
||||
import { GitDropdown } from '../GitDropdown';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
@@ -49,13 +46,10 @@ export interface SidebarTreeNode {
|
||||
export function Sidebar({ className }: Props) {
|
||||
const [hidden, setHidden] = useSidebarHidden();
|
||||
const sidebarRef = useRef<HTMLElement>(null);
|
||||
const activeWorkspace = useActiveWorkspace();
|
||||
const activeWorkspace = useAtomValue(activeWorkspaceAtom);
|
||||
const [hasFocus, setHasFocus] = useState<boolean>(false);
|
||||
const [selectedId, setSelectedId] = useAtom(sidebarSelectedIdAtom);
|
||||
const [selectedTree, setSelectedTree] = useState<SidebarTreeNode | null>(null);
|
||||
const { mutateAsync: updateAnyHttpRequest } = useUpdateAnyHttpRequest();
|
||||
const { mutateAsync: updateAnyGrpcRequest } = useUpdateAnyGrpcRequest();
|
||||
const { mutateAsync: updateAnyFolder } = useUpdateAnyFolder();
|
||||
const [draggingId, setDraggingId] = useState<string | null>(null);
|
||||
const [hoveredTree, setHoveredTree] = useState<SidebarTreeNode | null>(null);
|
||||
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
|
||||
@@ -72,11 +66,11 @@ export function Sidebar({ className }: Props) {
|
||||
noFocusSidebar?: boolean;
|
||||
} = {},
|
||||
) => {
|
||||
const activeRequest = getActiveRequest();
|
||||
const activeRequestId = jotaiStore.get(activeRequestIdAtom);
|
||||
const { forced, noFocusSidebar } = args;
|
||||
const tree = forced?.tree ?? treeParentMap[activeRequest?.id ?? 'n/a'] ?? null;
|
||||
const tree = forced?.tree ?? treeParentMap[activeRequestId ?? 'n/a'] ?? null;
|
||||
const children = tree?.children ?? [];
|
||||
const id = forced?.id ?? children.find((m) => m.id === activeRequest?.id)?.id ?? null;
|
||||
const id = forced?.id ?? children.find((m) => m.id === activeRequestId)?.id ?? null;
|
||||
|
||||
setHasFocus(true);
|
||||
setSelectedId(id);
|
||||
@@ -129,14 +123,14 @@ export function Sidebar({ className }: Props) {
|
||||
}, [focusActiveRequest, hasFocus]);
|
||||
|
||||
const handleBlur = useCallback(() => setHasFocus(false), [setHasFocus]);
|
||||
const deleteRequest = useDeleteAnyRequest();
|
||||
|
||||
useHotKey(
|
||||
'http_request.delete',
|
||||
'sidebar.delete_selected_item',
|
||||
async () => {
|
||||
// Delete only works if a request is focused
|
||||
if (selectedId == null) return;
|
||||
deleteRequest.mutate(selectedId);
|
||||
const request = getAnyModel(selectedId ?? 'n/a');
|
||||
if (request != null) {
|
||||
await deleteModelWithConfirm(request);
|
||||
}
|
||||
},
|
||||
{ enable: hasFocus },
|
||||
);
|
||||
@@ -178,7 +172,8 @@ export function Sidebar({ className }: Props) {
|
||||
if (!hasFocus) return;
|
||||
e.preventDefault();
|
||||
const i = selectableRequests.findIndex((r) => r.id === selectedId);
|
||||
const newSelectable = selectableRequests[i - 1];
|
||||
const newI = i <= 0 ? selectableRequests.length - 1 : i - 1;
|
||||
const newSelectable = selectableRequests[newI];
|
||||
if (newSelectable == null) {
|
||||
return;
|
||||
}
|
||||
@@ -196,7 +191,8 @@ export function Sidebar({ className }: Props) {
|
||||
if (!hasFocus) return;
|
||||
e.preventDefault();
|
||||
const i = selectableRequests.findIndex((r) => r.id === selectedId);
|
||||
const newSelectable = selectableRequests[i + 1];
|
||||
const newI = i >= selectableRequests.length - 1 ? 0 : i + 1;
|
||||
const newSelectable = selectableRequests[newI];
|
||||
if (newSelectable == null) {
|
||||
return;
|
||||
}
|
||||
@@ -277,52 +273,16 @@ export function Sidebar({ className }: Props) {
|
||||
await Promise.all(
|
||||
newChildren.map((child, i) => {
|
||||
const sortPriority = i * 1000;
|
||||
if (child.model === 'folder') {
|
||||
const updateFolder = (f: Folder) => ({ ...f, sortPriority, folderId });
|
||||
return updateAnyFolder({ id: child.id, update: updateFolder });
|
||||
} else if (child.model === 'grpc_request') {
|
||||
const updateRequest = (r: GrpcRequest) => ({ ...r, sortPriority, folderId });
|
||||
return updateAnyGrpcRequest({ id: child.id, update: updateRequest });
|
||||
} else if (child.model === 'http_request') {
|
||||
const updateRequest = (r: HttpRequest) => ({ ...r, sortPriority, folderId });
|
||||
return updateAnyHttpRequest({ id: child.id, update: updateRequest });
|
||||
} else if (child.model === 'websocket_request') {
|
||||
const request = getWebsocketRequest(child.id);
|
||||
return upsertWebsocketRequest.mutateAsync({ ...request, sortPriority, folderId });
|
||||
} else {
|
||||
throw new Error('Invalid model to update: ' + child.model);
|
||||
}
|
||||
return patchModelById(child.model, child.id, { sortPriority, folderId });
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
const sortPriority = afterPriority - (afterPriority - beforePriority) / 2;
|
||||
if (child.model === 'folder') {
|
||||
const updateFolder = (f: Folder) => ({ ...f, sortPriority, folderId });
|
||||
await updateAnyFolder({ id: child.id, update: updateFolder });
|
||||
} else if (child.model === 'grpc_request') {
|
||||
const updateRequest = (r: GrpcRequest) => ({ ...r, sortPriority, folderId });
|
||||
await updateAnyGrpcRequest({ id: child.id, update: updateRequest });
|
||||
} else if (child.model === 'http_request') {
|
||||
const updateRequest = (r: HttpRequest) => ({ ...r, sortPriority, folderId });
|
||||
await updateAnyHttpRequest({ id: child.id, update: updateRequest });
|
||||
} else if (child.model === 'websocket_request') {
|
||||
const request = getWebsocketRequest(child.id);
|
||||
return upsertWebsocketRequest.mutateAsync({ ...request, sortPriority, folderId });
|
||||
} else {
|
||||
throw new Error('Invalid model to update: ' + child.model);
|
||||
}
|
||||
return patchModelById(child.model, child.id, { sortPriority, folderId });
|
||||
}
|
||||
setDraggingId(null);
|
||||
},
|
||||
[
|
||||
handleClearSelected,
|
||||
hoveredTree,
|
||||
hoveredIndex,
|
||||
treeParentMap,
|
||||
updateAnyFolder,
|
||||
updateAnyGrpcRequest,
|
||||
updateAnyHttpRequest,
|
||||
],
|
||||
[handleClearSelected, hoveredTree, hoveredIndex, treeParentMap],
|
||||
);
|
||||
|
||||
const [showMainContextMenu, setShowMainContextMenu] = useState<{
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import type { Folder, GrpcRequest, HttpRequest, WebsocketRequest } from '@yaakapp-internal/models';
|
||||
import {
|
||||
type Folder,
|
||||
foldersAtom,
|
||||
type GrpcRequest,
|
||||
type HttpRequest,
|
||||
type WebsocketRequest,
|
||||
} from '@yaakapp-internal/models';
|
||||
|
||||
// This is an atom so we can use it in the child items to avoid re-rendering the entire list
|
||||
import { atom } from 'jotai';
|
||||
import { activeWorkspaceAtom } from '../../hooks/useActiveWorkspace';
|
||||
import { foldersAtom } from '../../hooks/useFolders';
|
||||
import { requestsAtom } from '../../hooks/useRequests';
|
||||
import { allRequestsAtom } from '../../hooks/useAllRequests';
|
||||
import { deepEqualAtom } from '../../lib/atoms';
|
||||
import { resolvedModelName } from '../../lib/resolvedModelName';
|
||||
import type { SidebarTreeNode } from './Sidebar';
|
||||
@@ -12,7 +17,7 @@ import type { SidebarTreeNode } from './Sidebar';
|
||||
export const sidebarSelectedIdAtom = atom<string | null>(null);
|
||||
|
||||
const allPotentialChildrenAtom = atom((get) => {
|
||||
const requests = get(requestsAtom);
|
||||
const requests = get(allRequestsAtom);
|
||||
const folders = get(foldersAtom);
|
||||
return [...requests, ...folders].map((v) => ({
|
||||
id: v.id,
|
||||
|
||||
@@ -4,26 +4,22 @@ import type {
|
||||
HttpResponse,
|
||||
WebsocketConnection,
|
||||
} from '@yaakapp-internal/models';
|
||||
import { foldersAtom, patchModelById } from '@yaakapp-internal/models';
|
||||
import classNames from 'classnames';
|
||||
import { atom, useAtomValue } from 'jotai';
|
||||
import type { ReactElement } from 'react';
|
||||
import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import type { XYCoord } from 'react-dnd';
|
||||
import { useDrag, useDrop } from 'react-dnd';
|
||||
import { upsertWebsocketRequest } from '../../commands/upsertWebsocketRequest';
|
||||
import { activeRequestAtom } from '../../hooks/useActiveRequest';
|
||||
import { foldersAtom } from '../../hooks/useFolders';
|
||||
import { requestsAtom } from '../../hooks/useRequests';
|
||||
import {allRequestsAtom} from "../../hooks/useAllRequests";
|
||||
import { useScrollIntoView } from '../../hooks/useScrollIntoView';
|
||||
import { useSidebarItemCollapsed } from '../../hooks/useSidebarItemCollapsed';
|
||||
import { useUpdateAnyGrpcRequest } from '../../hooks/useUpdateAnyGrpcRequest';
|
||||
import { useUpdateAnyHttpRequest } from '../../hooks/useUpdateAnyHttpRequest';
|
||||
import { getWebsocketRequest } from '../../hooks/useWebsocketRequests';
|
||||
import { jotaiStore } from '../../lib/jotai';
|
||||
import { HttpMethodTag } from '../core/HttpMethodTag';
|
||||
import { HttpStatusTag } from '../core/HttpStatusTag';
|
||||
import { Icon } from '../core/Icon';
|
||||
import { LoadingIcon } from '../core/LoadingIcon';
|
||||
import { HttpStatusTag } from '../core/HttpStatusTag';
|
||||
import type { SidebarTreeNode } from './Sidebar';
|
||||
import { sidebarSelectedIdAtom } from './SidebarAtoms';
|
||||
import { SidebarItemContextMenu } from './SidebarItemContextMenu';
|
||||
@@ -110,8 +106,6 @@ export const SidebarItem = memo(function SidebarItem({
|
||||
|
||||
connectDrag(connectDrop(ref));
|
||||
|
||||
const updateHttpRequest = useUpdateAnyHttpRequest();
|
||||
const updateGrpcRequest = useUpdateAnyGrpcRequest();
|
||||
const [editing, setEditing] = useState<boolean>(false);
|
||||
|
||||
const [selected, setSelected] = useState<boolean>(
|
||||
@@ -137,26 +131,12 @@ export const SidebarItem = memo(function SidebarItem({
|
||||
|
||||
const handleSubmitNameEdit = useCallback(
|
||||
async (el: HTMLInputElement) => {
|
||||
if (itemModel === 'http_request') {
|
||||
await updateHttpRequest.mutateAsync({
|
||||
id: itemId,
|
||||
update: (r) => ({ ...r, name: el.value }),
|
||||
});
|
||||
} else if (itemModel === 'grpc_request') {
|
||||
await updateGrpcRequest.mutateAsync({
|
||||
id: itemId,
|
||||
update: (r) => ({ ...r, name: el.value }),
|
||||
});
|
||||
} else if (itemModel === 'websocket_request') {
|
||||
const request = getWebsocketRequest(itemId);
|
||||
if (request == null) return;
|
||||
await upsertWebsocketRequest.mutateAsync({ ...request, name: el.value });
|
||||
}
|
||||
await patchModelById(itemModel, itemId, { name: el.value });
|
||||
|
||||
// Slight delay for the model to propagate to the local store
|
||||
setTimeout(() => setEditing(false));
|
||||
},
|
||||
[itemId, itemModel, updateGrpcRequest, updateHttpRequest],
|
||||
[itemId, itemModel],
|
||||
);
|
||||
|
||||
const handleFocus = useCallback((el: HTMLInputElement | null) => {
|
||||
@@ -199,8 +179,11 @@ export const SidebarItem = memo(function SidebarItem({
|
||||
);
|
||||
|
||||
const handleSelect = useCallback(async () => {
|
||||
if (itemModel === 'folder') toggleCollapsed();
|
||||
else onSelect(itemId);
|
||||
if (itemModel === 'folder') {
|
||||
toggleCollapsed();
|
||||
} else {
|
||||
onSelect(itemId);
|
||||
}
|
||||
}, [itemModel, toggleCollapsed, onSelect, itemId]);
|
||||
const [showContextMenu, setShowContextMenu] = useState<{
|
||||
x: number;
|
||||
@@ -220,7 +203,7 @@ export const SidebarItem = memo(function SidebarItem({
|
||||
if (itemModel === 'folder') {
|
||||
return get(foldersAtom).find((v) => v.id === itemId);
|
||||
} else {
|
||||
return get(requestsAtom).find((v) => v.id === itemId);
|
||||
return get(allRequestsAtom).find((v) => v.id === itemId);
|
||||
}
|
||||
});
|
||||
}, [itemId, itemModel]);
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import {
|
||||
deleteModelById,
|
||||
duplicateModelById,
|
||||
getModel,
|
||||
workspacesAtom,
|
||||
} from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import React, { useMemo } from 'react';
|
||||
import { duplicateWebsocketRequest } from '../../commands/duplicateWebsocketRequest';
|
||||
import { useCreateDropdownItems } from '../../hooks/useCreateDropdownItems';
|
||||
import { useDeleteAnyRequest } from '../../hooks/useDeleteAnyRequest';
|
||||
import { useDeleteFolder } from '../../hooks/useDeleteFolder';
|
||||
import { useDuplicateFolder } from '../../hooks/useDuplicateFolder';
|
||||
import { useDuplicateGrpcRequest } from '../../hooks/useDuplicateGrpcRequest';
|
||||
import { useDuplicateHttpRequest } from '../../hooks/useDuplicateHttpRequest';
|
||||
import { useHttpRequestActions } from '../../hooks/useHttpRequestActions';
|
||||
import { getHttpRequest } from '../../hooks/useHttpRequests';
|
||||
import { useMoveToWorkspace } from '../../hooks/useMoveToWorkspace';
|
||||
import { useRenameRequest } from '../../hooks/useRenameRequest';
|
||||
import { useSendAnyHttpRequest } from '../../hooks/useSendAnyHttpRequest';
|
||||
import { useSendManyRequests } from '../../hooks/useSendManyRequests';
|
||||
import { useWorkspaces } from '../../hooks/useWorkspaces';
|
||||
import { deleteModelWithConfirm } from '../../lib/deleteModelWithConfirm';
|
||||
import { duplicateRequestAndNavigate } from '../../lib/deleteRequestAndNavigate';
|
||||
|
||||
import { showDialog } from '../../lib/dialog';
|
||||
import { renameModelWithPrompt } from '../../lib/renameModelWithPrompt';
|
||||
import type { DropdownItem } from '../core/Dropdown';
|
||||
import { ContextMenu } from '../core/Dropdown';
|
||||
import { Icon } from '../core/Icon';
|
||||
@@ -29,15 +30,9 @@ interface Props {
|
||||
|
||||
export function SidebarItemContextMenu({ child, show, close }: Props) {
|
||||
const sendManyRequests = useSendManyRequests();
|
||||
const duplicateFolder = useDuplicateFolder(child.id);
|
||||
const deleteFolder = useDeleteFolder(child.id);
|
||||
const httpRequestActions = useHttpRequestActions();
|
||||
const sendRequest = useSendAnyHttpRequest();
|
||||
const workspaces = useWorkspaces();
|
||||
const deleteRequest = useDeleteAnyRequest();
|
||||
const renameRequest = useRenameRequest(child.id);
|
||||
const duplicateHttpRequest = useDuplicateHttpRequest({ id: child.id, navigateAfter: true });
|
||||
const duplicateGrpcRequest = useDuplicateGrpcRequest({ id: child.id, navigateAfter: true });
|
||||
const workspaces = useAtomValue(workspacesAtom);
|
||||
const moveToWorkspace = useMoveToWorkspace(child.id);
|
||||
const createDropdownItems = useCreateDropdownItems({
|
||||
folderId: child.model === 'folder' ? child.id : null,
|
||||
@@ -65,13 +60,15 @@ export function SidebarItemContextMenu({ child, show, close }: Props) {
|
||||
{
|
||||
label: 'Duplicate',
|
||||
leftSlot: <Icon icon="copy" />,
|
||||
onSelect: () => duplicateFolder.mutate(),
|
||||
onSelect: () => duplicateModelById(child.model, child.id),
|
||||
},
|
||||
{
|
||||
label: 'Delete',
|
||||
color: 'danger',
|
||||
leftSlot: <Icon icon="trash" />,
|
||||
onSelect: () => deleteFolder.mutate(),
|
||||
onSelect: async () => {
|
||||
await deleteModelWithConfirm(getModel(child.model, child.id));
|
||||
},
|
||||
},
|
||||
{ type: 'separator' },
|
||||
...createDropdownItems,
|
||||
@@ -92,7 +89,7 @@ export function SidebarItemContextMenu({ child, show, close }: Props) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
leftSlot: <Icon icon={(a.icon as any) ?? 'empty'} />,
|
||||
onSelect: async () => {
|
||||
const request = getHttpRequest(child.id);
|
||||
const request = getModel('http_request', child.id);
|
||||
if (request != null) await a.call(request);
|
||||
},
|
||||
})),
|
||||
@@ -104,23 +101,25 @@ export function SidebarItemContextMenu({ child, show, close }: Props) {
|
||||
{
|
||||
label: 'Rename',
|
||||
leftSlot: <Icon icon="pencil" />,
|
||||
onSelect: renameRequest.mutate,
|
||||
onSelect: async () => {
|
||||
const request = getModel(
|
||||
['http_request', 'grpc_request', 'websocket_request'],
|
||||
child.id,
|
||||
);
|
||||
await renameModelWithPrompt(request);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Duplicate',
|
||||
hotKeyAction: 'http_request.duplicate',
|
||||
hotKeyLabelOnly: true, // Would trigger for every request (bad)
|
||||
leftSlot: <Icon icon="copy" />,
|
||||
onSelect: () => {
|
||||
if (child.model === 'http_request') {
|
||||
duplicateHttpRequest.mutate();
|
||||
} else if (child.model === 'grpc_request') {
|
||||
duplicateGrpcRequest.mutate();
|
||||
} else if (child.model === 'websocket_request') {
|
||||
duplicateWebsocketRequest.mutate(child.id);
|
||||
} else {
|
||||
throw new Error('Cannot duplicate invalid model: ' + child.model);
|
||||
}
|
||||
onSelect: async () => {
|
||||
const request = getModel(
|
||||
['http_request', 'grpc_request', 'websocket_request'],
|
||||
child.id,
|
||||
);
|
||||
await duplicateRequestAndNavigate(request);
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -132,10 +131,10 @@ export function SidebarItemContextMenu({ child, show, close }: Props) {
|
||||
{
|
||||
color: 'danger',
|
||||
label: 'Delete',
|
||||
hotKeyAction: 'http_request.delete',
|
||||
hotKeyAction: 'sidebar.delete_selected_item',
|
||||
hotKeyLabelOnly: true,
|
||||
leftSlot: <Icon icon="trash" />,
|
||||
onSelect: () => deleteRequest.mutate(child.id),
|
||||
onSelect: async () => deleteModelById(child.model, child.id),
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -144,14 +143,8 @@ export function SidebarItemContextMenu({ child, show, close }: Props) {
|
||||
child.id,
|
||||
child.model,
|
||||
createDropdownItems,
|
||||
deleteFolder,
|
||||
deleteRequest,
|
||||
duplicateFolder,
|
||||
duplicateGrpcRequest,
|
||||
duplicateHttpRequest,
|
||||
httpRequestActions,
|
||||
moveToWorkspace.mutate,
|
||||
renameRequest.mutate,
|
||||
sendManyRequests,
|
||||
sendRequest,
|
||||
workspaces.length,
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import {
|
||||
grpcConnectionsAtom,
|
||||
httpResponsesAtom,
|
||||
websocketConnectionsAtom,
|
||||
} from '@yaakapp-internal/models';
|
||||
import classNames from 'classnames';
|
||||
import { useAtomValue } from 'jotai/index';
|
||||
import React, { Fragment, memo } from 'react';
|
||||
import { useGrpcConnections } from '../../hooks/useGrpcConnections';
|
||||
import { useHttpResponses } from '../../hooks/useHttpResponses';
|
||||
import { useWebsocketConnections } from '../../hooks/useWebsocketConnections';
|
||||
import { VStack } from '../core/Stacks';
|
||||
import { DropMarker } from '../DropMarker';
|
||||
import type { SidebarTreeNode } from './Sidebar';
|
||||
@@ -33,9 +36,9 @@ export const SidebarItems = memo(function SidebarItems({
|
||||
handleMove,
|
||||
handleDragStart,
|
||||
}: SidebarItemsProps) {
|
||||
const httpResponses = useHttpResponses();
|
||||
const grpcConnections = useGrpcConnections();
|
||||
const websocketConnections = useWebsocketConnections();
|
||||
const httpResponses = useAtomValue(httpResponsesAtom);
|
||||
const grpcConnections = useAtomValue(grpcConnectionsAtom);
|
||||
const websocketConnections = useAtomValue(websocketConnectionsAtom);
|
||||
|
||||
return (
|
||||
<VStack
|
||||
|
||||
Reference in New Issue
Block a user