{children}
- >
+
);
}
diff --git a/src-web/components/core/tree/AutoScrollWhileDragging.tsx b/src-web/components/core/tree/AutoScrollWhileDragging.tsx
deleted file mode 100644
index 04a7ce0b..00000000
--- a/src-web/components/core/tree/AutoScrollWhileDragging.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-// AutoScrollWhileDragging.tsx
-import { useEffect, useRef } from 'react';
-import { useDragLayer } from 'react-dnd';
-
-type Props = {
- container: HTMLElement | null | undefined;
- edgeDistance?: number;
- maxSpeedPerFrame?: number;
-};
-
-export function AutoScrollWhileDragging({
- container,
- edgeDistance = 30,
- maxSpeedPerFrame = 6,
-}: Props) {
- const rafId = useRef(null);
-
- const { isDragging, pointer } = useDragLayer((monitor) => ({
- isDragging: monitor.isDragging(),
- pointer: monitor.getClientOffset(), // { x, y } | null
- }));
-
- useEffect(() => {
- if (!container || !isDragging) {
- if (rafId.current != null) cancelAnimationFrame(rafId.current);
- rafId.current = null;
- return;
- }
-
- const tick = () => {
- if (!container || !isDragging || !pointer) return;
-
- const rect = container.getBoundingClientRect();
- const y = pointer.y;
-
- // Compute vertical speed based on proximity to edges
- let dy = 0;
- if (y < rect.top + edgeDistance) {
- const t = (rect.top + edgeDistance - y) / edgeDistance; // 0..1
- dy = -Math.min(maxSpeedPerFrame, Math.ceil(t * maxSpeedPerFrame));
- } else if (y > rect.bottom - edgeDistance) {
- const t = (y - (rect.bottom - edgeDistance)) / edgeDistance; // 0..1
- dy = Math.min(maxSpeedPerFrame, Math.ceil(t * maxSpeedPerFrame));
- }
-
- if (dy !== 0) {
- // Only scroll if there’s more content in that direction
- const prev = container.scrollTop;
- container.scrollTop = prev + dy;
- }
-
- rafId.current = requestAnimationFrame(tick);
- };
-
- rafId.current = requestAnimationFrame(tick);
- return () => {
- if (rafId.current != null) cancelAnimationFrame(rafId.current);
- rafId.current = null;
- };
- }, [container, isDragging, pointer, edgeDistance, maxSpeedPerFrame]);
-
- return null;
-}
diff --git a/src-web/components/core/tree/Tree.tsx b/src-web/components/core/tree/Tree.tsx
index 307864cb..d47882b0 100644
--- a/src-web/components/core/tree/Tree.tsx
+++ b/src-web/components/core/tree/Tree.tsx
@@ -14,6 +14,7 @@ import { forwardRef, memo, useCallback, useImperativeHandle, useMemo, useRef } f
import { useKey, useKeyPressEvent } from 'react-use';
import type { HotkeyAction, HotKeyOptions } from '../../../hooks/useHotKey';
import { useHotKey } from '../../../hooks/useHotKey';
+import { computeSideForDragMove } from '../../../lib/dnd';
import { jotaiStore } from '../../../lib/jotai';
import type { ContextMenuProps } from '../Dropdown';
import {
@@ -24,7 +25,7 @@ import {
selectedIdsFamily,
} from './atoms';
import type { SelectableTreeNode, TreeNode } from './common';
-import { computeSideForDragMove, equalSubtree, getSelectedItems, hasAncestor } from './common';
+import { equalSubtree, getSelectedItems, hasAncestor } from './common';
import { TreeDragOverlay } from './TreeDragOverlay';
import type { TreeItemProps } from './TreeItem';
import type { TreeItemListProps } from './TreeItemList';
@@ -255,7 +256,7 @@ function TreeInner(
}
const node = selectableItem.node;
- const side = computeSideForDragMove(node, e);
+ const side = computeSideForDragMove(node.item.id, e);
const item = node.item;
let hoveredParent = node.parent;
diff --git a/src-web/components/core/tree/TreeItem.tsx b/src-web/components/core/tree/TreeItem.tsx
index 5cb11a17..4a833e79 100644
--- a/src-web/components/core/tree/TreeItem.tsx
+++ b/src-web/components/core/tree/TreeItem.tsx
@@ -5,13 +5,13 @@ import { useAtomValue } from 'jotai';
import { selectAtom } from 'jotai/utils';
import type { MouseEvent, PointerEvent } from 'react';
import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import { computeSideForDragMove } from '../../../lib/dnd';
import { jotaiStore } from '../../../lib/jotai';
import type { ContextMenuProps, DropdownItem } from '../Dropdown';
import { ContextMenu } from '../Dropdown';
import { Icon } from '../Icon';
import { collapsedFamily, isCollapsedFamily, isLastFocusedFamily, isSelectedFamily } from './atoms';
import type { TreeNode } from './common';
-import { computeSideForDragMove } from './common';
import type { TreeProps } from './Tree';
import { TreeIndentGuide } from './TreeIndentGuide';
@@ -161,7 +161,7 @@ function TreeItem_({
clearDropHover();
},
onDragMove(e: DragMoveEvent) {
- const side = computeSideForDragMove(node, e);
+ const side = computeSideForDragMove(node.item.id, e);
const isFolder = node.children != null;
const hasChildren = (node.children?.length ?? 0) > 0;
const isCollapsed = jotaiStore.get(isCollapsedFamily({ treeId, itemId: node.item.id }));
diff --git a/src-web/components/core/tree/common.ts b/src-web/components/core/tree/common.ts
index d2056dda..9cf6569d 100644
--- a/src-web/components/core/tree/common.ts
+++ b/src-web/components/core/tree/common.ts
@@ -1,8 +1,7 @@
-import type { DragMoveEvent } from '@dnd-kit/core';
import { jotaiStore } from '../../../lib/jotai';
import { selectedIdsFamily } from './atoms';
-export interface TreeNode {
+export interface TreeNode {
children?: TreeNode[];
item: T;
parent: TreeNode | null;
@@ -48,25 +47,3 @@ export function hasAncestor(node: TreeNode, ancesto
// Check parents recursively
return hasAncestor(node.parent, ancestorId);
}
-
-export function computeSideForDragMove(
- node: TreeNode,
- e: DragMoveEvent,
-): 'above' | 'below' | null {
- if (e.over == null || e.over.id !== node.item.id) {
- return null;
- }
- if (e.active.rect.current.initial == null) return null;
-
- const overRect = e.over.rect;
- const activeTop =
- e.active.rect.current.translated?.top ?? e.active.rect.current.initial.top + e.delta.y;
- const pointerY = activeTop + e.active.rect.current.initial.height / 2;
-
- const hoverTop = overRect.top;
- const hoverBottom = overRect.bottom;
- const hoverMiddleY = (hoverBottom - hoverTop) / 2;
- const hoverClientY = pointerY - hoverTop;
-
- return hoverClientY < hoverMiddleY ? 'above' : 'below';
-}
diff --git a/src-web/components/graphql/GraphQLEditor.tsx b/src-web/components/graphql/GraphQLEditor.tsx
index c0c19786..35ed37ea 100644
--- a/src-web/components/graphql/GraphQLEditor.tsx
+++ b/src-web/components/graphql/GraphQLEditor.tsx
@@ -13,7 +13,7 @@ import { Button } from '../core/Button';
import type { DropdownItem } from '../core/Dropdown';
import { Dropdown } from '../core/Dropdown';
import type { EditorProps } from '../core/Editor/Editor';
-import { Editor } from '../core/Editor/Editor';
+import { Editor } from '../core/Editor/LazyEditor';
import { FormattedError } from '../core/FormattedError';
import { Icon } from '../core/Icon';
import { Separator } from '../core/Separator';
diff --git a/src-web/components/responseViewers/EventStreamViewer.tsx b/src-web/components/responseViewers/EventStreamViewer.tsx
index 266f0499..d993c403 100644
--- a/src-web/components/responseViewers/EventStreamViewer.tsx
+++ b/src-web/components/responseViewers/EventStreamViewer.tsx
@@ -9,7 +9,7 @@ import { AutoScroller } from '../core/AutoScroller';
import { Banner } from '../core/Banner';
import { Button } from '../core/Button';
import type { EditorProps } from '../core/Editor/Editor';
-import { Editor } from '../core/Editor/Editor';
+import { Editor } from '../core/Editor/LazyEditor';
import { Icon } from '../core/Icon';
import { InlineCode } from '../core/InlineCode';
import { Separator } from '../core/Separator';
diff --git a/src-web/components/responseViewers/PdfViewer.tsx b/src-web/components/responseViewers/PdfViewer.tsx
index 0763165a..165db843 100644
--- a/src-web/components/responseViewers/PdfViewer.tsx
+++ b/src-web/components/responseViewers/PdfViewer.tsx
@@ -3,10 +3,19 @@ import 'react-pdf/dist/Page/AnnotationLayer.css';
import { convertFileSrc } from '@tauri-apps/api/core';
import './PdfViewer.css';
import type { PDFDocumentProxy } from 'pdfjs-dist';
-import React, { useRef, useState } from 'react';
-import { Document, Page } from 'react-pdf';
+import React, { lazy, useRef, useState } from 'react';
import { useContainerSize } from '../../hooks/useContainerQuery';
+const Document = lazy(() => import('react-pdf').then((m) => ({ default: m.Document })));
+const Page = lazy(() => import('react-pdf').then((m) => ({ default: m.Page })));
+
+import('react-pdf').then(({ pdfjs }) => {
+ pdfjs.GlobalWorkerOptions.workerSrc = new URL(
+ 'pdfjs-dist/build/pdf.worker.min.mjs',
+ import.meta.url,
+ ).toString();
+});
+
interface Props {
bodyPath: string;
}
diff --git a/src-web/components/responseViewers/TextViewer.tsx b/src-web/components/responseViewers/TextViewer.tsx
index d3ee59cb..ea918b23 100644
--- a/src-web/components/responseViewers/TextViewer.tsx
+++ b/src-web/components/responseViewers/TextViewer.tsx
@@ -7,7 +7,7 @@ import { useDebouncedValue } from '../../hooks/useDebouncedValue';
import { useFormatText } from '../../hooks/useFormatText';
import { useResponseBodyText } from '../../hooks/useResponseBodyText';
import type { EditorProps } from '../core/Editor/Editor';
-import { Editor } from '../core/Editor/Editor';
+import { Editor } from '../core/Editor/LazyEditor';
import { hyperlink } from '../core/Editor/hyperlink/extension';
import { IconButton } from '../core/IconButton';
import { Input } from '../core/Input';
diff --git a/src-web/lib/dnd.ts b/src-web/lib/dnd.ts
new file mode 100644
index 00000000..61e1749e
--- /dev/null
+++ b/src-web/lib/dnd.ts
@@ -0,0 +1,23 @@
+import type { DragMoveEvent } from '@dnd-kit/core';
+
+export function computeSideForDragMove(
+ id: string,
+ e: DragMoveEvent,
+): 'above' | 'below' | null {
+ if (e.over == null || e.over.id !== id) {
+ return null;
+ }
+ if (e.active.rect.current.initial == null) return null;
+
+ const overRect = e.over.rect;
+ const activeTop =
+ e.active.rect.current.translated?.top ?? e.active.rect.current.initial.top + e.delta.y;
+ const pointerY = activeTop + e.active.rect.current.initial.height / 2;
+
+ const hoverTop = overRect.top;
+ const hoverBottom = overRect.bottom;
+ const hoverMiddleY = (hoverBottom - hoverTop) / 2;
+ const hoverClientY = pointerY - hoverTop;
+
+ return hoverClientY < hoverMiddleY ? 'above' : 'below';
+}
diff --git a/src-web/main.tsx b/src-web/main.tsx
index 33584ca3..527e32c0 100644
--- a/src-web/main.tsx
+++ b/src-web/main.tsx
@@ -10,13 +10,6 @@ import { initGlobalListeners } from './lib/initGlobalListeners';
import { jotaiStore } from './lib/jotai';
import { router } from './lib/router';
-import('react-pdf').then(({ pdfjs }) => {
- pdfjs.GlobalWorkerOptions.workerSrc = new URL(
- 'pdfjs-dist/build/pdf.worker.min.mjs',
- import.meta.url,
- ).toString();
-});
-
// Hide decorations here because it doesn't work in Rust for some reason (bug?)
const osType = type();
if (osType !== 'macos') {
diff --git a/src-web/package.json b/src-web/package.json
index 39bcfd86..1e77f4d4 100644
--- a/src-web/package.json
+++ b/src-web/package.json
@@ -23,10 +23,9 @@
"@replit/codemirror-emacs": "^6.1.0",
"@replit/codemirror-vim": "^6.3.0",
"@replit/codemirror-vscode-keymap": "^6.0.2",
- "@tailwindcss/container-queries": "^0.1.1",
- "@tanstack/react-query": "^5.76.1",
- "@tanstack/react-router": "^1.120.3",
- "@tanstack/react-virtual": "^3.13.8",
+ "@tanstack/react-query": "^5.90.5",
+ "@tanstack/react-router": "^1.133.13",
+ "@tanstack/react-virtual": "^3.13.12",
"@tauri-apps/api": "^2.8.0",
"@tauri-apps/plugin-clipboard-manager": "^2.3.0",
"@tauri-apps/plugin-dialog": "^2.4.0",
@@ -56,8 +55,6 @@
"parse-color": "^1.0.0",
"react": "^19.1.0",
"react-colorful": "^5.6.1",
- "react-dnd": "^16.0.1",
- "react-dnd-touch-backend": "^16.0.1",
"react-dom": "^19.1.0",
"react-markdown": "^10.1.0",
"react-pdf": "^10.0.1",
@@ -86,6 +83,7 @@
"@types/whatwg-mimetype": "^3.0.2",
"@vitejs/plugin-react": "^4.6.0",
"autoprefixer": "^10.4.21",
+ "@tailwindcss/container-queries": "^0.1.1",
"decompress": "^4.2.1",
"eslint-plugin-react-refresh": "^0.4.20",
"internal-ip": "^8.0.0",
diff --git a/src-web/routes/__root.tsx b/src-web/routes/__root.tsx
index ea2c0a73..6670e90f 100644
--- a/src-web/routes/__root.tsx
+++ b/src-web/routes/__root.tsx
@@ -3,36 +3,35 @@ import { createRootRoute, Outlet } from '@tanstack/react-router';
import { type } from '@tauri-apps/plugin-os';
import classNames from 'classnames';
import { Provider as JotaiProvider } from 'jotai';
-import { domAnimation, LazyMotion, MotionConfig } from 'motion/react';
-import React, { Suspense } from 'react';
-import { DndProvider } from 'react-dnd';
-import { TouchBackend } from 'react-dnd-touch-backend';
-import { Dialogs } from '../components/Dialogs';
+import { LazyMotion, MotionConfig } from 'motion/react';
+import React, { lazy, Suspense } from 'react';
import { GlobalHooks } from '../components/GlobalHooks';
import RouteError from '../components/RouteError';
-import { Toasts } from '../components/Toasts';
import { jotaiStore } from '../lib/jotai';
import { queryClient } from '../lib/queryClient';
+const Toasts = lazy(() => import('../components/Toasts').then((m) => ({ default: m.Toasts })));
+const Dialogs = lazy(() => import('../components/Dialogs').then((m) => ({ default: m.Dialogs })));
+
export const Route = createRootRoute({
component: RouteComponent,
errorComponent: RouteError,
});
+const motionFeatures = () => import('framer-motion').then((mod) => mod.domAnimation);
+
function RouteComponent() {
return (
-
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/src-web/vite.config.ts b/src-web/vite.config.ts
index 50c2aa00..fc8236bb 100644
--- a/src-web/vite.config.ts
+++ b/src-web/vite.config.ts
@@ -1,5 +1,5 @@
// @ts-ignore
-import { TanStackRouterVite } from '@tanstack/router-plugin/vite';
+import { tanstackRouter } from '@tanstack/router-plugin/vite';
import react from '@vitejs/plugin-react';
import reactRefresh from 'eslint-plugin-react-refresh';
import { internalIpV4 } from 'internal-ip';
@@ -26,7 +26,8 @@ export default defineConfig(async () => ({
plugins: [
wasm(),
reactRefresh.configs.vite,
- TanStackRouterVite({
+ tanstackRouter({
+ target: 'react',
routesDirectory: './routes',
generatedRouteTree: './routeTree.gen.ts',
autoCodeSplitting: true,
@@ -44,6 +45,14 @@ export default defineConfig(async () => ({
build: {
outDir: '../dist',
emptyOutDir: true,
+ rollupOptions: {
+ output: {
+ // Make chunk names readable
+ chunkFileNames: 'assets/chunk-[name]-[hash].js',
+ entryFileNames: 'assets/entry-[name]-[hash].js',
+ assetFileNames: 'assets/asset-[name]-[hash][extname]',
+ },
+ },
},
clearScreen: false,
server: {