Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import type { PaneRegistry, RendererContext } from "@superset/panes";
import { FileCode2, Globe, MessageSquare, TerminalSquare } from "lucide-react";
import { useMemo } from "react";
import { ChatPane } from "./components/ChatPane";
import { WorkspaceFilePreview } from "./components/FilesPane/components/WorkspaceFilePreview/WorkspaceFilePreview";
import { TerminalPane } from "./components/TerminalPane";
import type {
BrowserPaneData,
ChatPaneData,
DevtoolsPaneData,
FilePaneData,
PaneViewerData,
} from "../../types";
import { ChatPane } from "./components/ChatPane";
import { WorkspaceFilePreview } from "./components/FilesPane/components/WorkspaceFilePreview/WorkspaceFilePreview";
import { TerminalPane } from "./components/TerminalPane";

function getFileTitle(filePath: string): string {
return filePath.split("/").pop() ?? filePath;
Expand Down Expand Up @@ -40,9 +40,7 @@ export function usePaneRegistry(
terminal: {
getIcon: () => <TerminalSquare className="size-4" />,
getTitle: () => "Terminal",
renderPane: () => (
<TerminalPane workspaceId={workspaceId} />
),
renderPane: () => <TerminalPane workspaceId={workspaceId} />,
},
browser: {
getIcon: () => <Globe className="size-4" />,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import {
createWorkspaceStore,
type WorkspaceState,
} from "@superset/panes";
import { createWorkspaceStore, type WorkspaceState } from "@superset/panes";
import { eq } from "@tanstack/db";
import { useLiveQuery } from "@tanstack/react-db";
import { useEffect, useMemo, useRef, useState } from "react";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { Workspace, type PaneActionConfig } from "@superset/panes";
import { type PaneActionConfig, Workspace } from "@superset/panes";
import { eq } from "@tanstack/db";
import { useLiveQuery } from "@tanstack/react-db";
import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { useCallback, useMemo } from "react";
import { HiMiniXMark } from "react-icons/hi2";
import { TbLayoutColumns, TbLayoutRows } from "react-icons/tb";
import { HotkeyTooltipContent } from "renderer/components/HotkeyTooltipContent";
import { electronTrpc } from "renderer/lib/electron-trpc";
import { useCollections } from "renderer/routes/_authenticated/providers/CollectionsProvider";
import {
CommandPalette,
useCommandPalette,
} from "renderer/screens/main/components/CommandPalette";
import { PresetsBar } from "renderer/screens/main/components/WorkspaceView/ContentView/components/PresetsBar";
import { HotkeyTooltipContent } from "renderer/components/HotkeyTooltipContent";
import { useAppHotkey } from "renderer/stores/hotkeys";
import { AddTabMenu } from "./components/AddTabMenu";
import { WorkspaceEmptyState } from "./components/WorkspaceEmptyState";
Expand Down Expand Up @@ -79,22 +79,24 @@ function WorkspaceContent({
const utils = electronTrpc.useUtils();
const { data: showPresetsBar, isLoading: isLoadingPresetsBar } =
electronTrpc.settings.getShowPresetsBar.useQuery();
const setShowPresetsBar = electronTrpc.settings.setShowPresetsBar.useMutation({
onMutate: async ({ enabled }) => {
await utils.settings.getShowPresetsBar.cancel();
const previous = utils.settings.getShowPresetsBar.getData();
utils.settings.getShowPresetsBar.setData(undefined, enabled);
return { previous };
},
onError: (_error, _variables, context) => {
if (context?.previous !== undefined) {
utils.settings.getShowPresetsBar.setData(undefined, context.previous);
}
},
onSettled: () => {
utils.settings.getShowPresetsBar.invalidate();
const setShowPresetsBar = electronTrpc.settings.setShowPresetsBar.useMutation(
{
onMutate: async ({ enabled }) => {
await utils.settings.getShowPresetsBar.cancel();
const previous = utils.settings.getShowPresetsBar.getData();
utils.settings.getShowPresetsBar.setData(undefined, enabled);
return { previous };
},
onError: (_error, _variables, context) => {
if (context?.previous !== undefined) {
utils.settings.getShowPresetsBar.setData(undefined, context.previous);
}
},
onSettled: () => {
utils.settings.getShowPresetsBar.invalidate();
},
},
});
);

const openFilePane = useCallback(
(filePath: string) => {
Expand Down Expand Up @@ -206,10 +208,7 @@ function WorkspaceContent({
key: "close",
icon: <HiMiniXMark className="size-3.5" />,
tooltip: (
<HotkeyTooltipContent
label="Close pane"
hotkeyId="CLOSE_TERMINAL"
/>
<HotkeyTooltipContent label="Close pane" hotkeyId="CLOSE_TERMINAL" />
),
onClick: (ctx) => ctx.actions.close(),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,11 @@ function ensureSidebarWorkspaceRecord(
tabOrder: getNextTabOrder(topLevelOrders),
sectionId: null,
},
paneLayout: { version: 1, tabs: [], activeTabId: null } satisfies WorkspaceState<unknown>,
paneLayout: {
version: 1,
tabs: [],
activeTabId: null,
} satisfies WorkspaceState<unknown>,
});
}

Expand Down
2 changes: 2 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packages/panes/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
"dependencies": {
"@superset/ui": "workspace:*",
"lucide-react": "^0.563.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
Comment on lines +20 to +21
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Is react-dnd version 16 compatible with React 19?

💡 Result:

No, react-dnd version 16 is not fully compatible with React 19. While some users report successful upgrades where it functions (per DepFixer verification), the official GitHub issue #3655 documents compatibility problems including runtime errors like "TypeError: _reactDom.default.findDOMNode is not a function" and TypeScript ref typing issues due to React 19 removing findDOMNode and changing ref typings. The library's last release (v16.0.1) was ~4 years ago in 2022, before React 19, with no official React 19 support or updated peer dependencies. Workarounds like custom drag ref hooks exist, but official compatibility is lacking. Consider alternatives like @hello-pangea/dnd or dnd-kit which have added React 19 support.

Citations:


Replace react-dnd@^16.0.1 with a React 19 compatible alternative.

react-dnd v16.0.1 (released 2022) does not support React 19. This causes runtime errors including "TypeError: _reactDom.default.findDOMNode is not a function" due to React 19 removing the findDOMNode API, and TypeScript ref typing incompatibilities. Consider using @hello-pangea/dnd or dnd-kit, both of which have official React 19 support.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/panes/package.json` around lines 20 - 21, Update the package.json
dependencies by removing "react-dnd" and "react-dnd-html5-backend" and replacing
them with a React-19-compatible library (for example "@hello-pangea/dnd" or
"dnd-kit"); then update all references/imports in the codebase that currently
import from "react-dnd" or "react-dnd-html5-backend" to the chosen library's
import paths and adapt any API differences in files using DragDropContext,
useDrag/useDrop, or HTML5Backend equivalents to the chosen library's APIs so
runtime errors like findDOMNode and ref incompatibilities are resolved.

"zustand": "^5.0.8"
},
"devDependencies": {
Expand Down
144 changes: 143 additions & 1 deletion packages/panes/src/core/store/store.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from "bun:test";
import type { WorkspaceState } from "../../types";
import type { Tab, WorkspaceState } from "../../types";
import type { CreatePaneInput } from "./store";
import { createWorkspaceStore } from "./store";

Expand Down Expand Up @@ -577,6 +577,148 @@ describe("openPane", () => {
});
});

describe("movePaneToSplit", () => {
it("moves a pane within the same tab", () => {
const store = makeStore({
version: 1,
tabs: [
{
id: "t1",
createdAt: Date.now(),
activePaneId: "p1",
layout: {
type: "split",
id: "s1",
direction: "horizontal",
children: [
{ type: "pane", paneId: "p1" },
{ type: "pane", paneId: "p2" },
],
weights: [1, 1],
},
panes: {
p1: { id: "p1", kind: "test", data: { label: "p1" } },
p2: { id: "p2", kind: "test", data: { label: "p2" } },
},
},
],
activeTabId: "t1",
});

store.getState().movePaneToSplit({
sourcePaneId: "p1",
targetPaneId: "p2",
position: "bottom",
});

const tab = store.getState().tabs[0];
expect(tab).toBeDefined();
// p1 should now be split below p2
expect(tab?.panes.p1).toBeDefined();
expect(tab?.panes.p2).toBeDefined();
expect(tab?.activePaneId).toBe("p1");
});

it("moves a pane across tabs", () => {
const store = makeStore({
version: 1,
tabs: [
{
id: "t1",
createdAt: Date.now(),
activePaneId: "p1",
layout: {
type: "split",
id: "s1",
direction: "horizontal",
children: [
{ type: "pane", paneId: "p1" },
{ type: "pane", paneId: "p2" },
],
weights: [1, 1],
},
panes: {
p1: { id: "p1", kind: "test", data: { label: "p1" } },
p2: { id: "p2", kind: "test", data: { label: "p2" } },
},
},
{
id: "t2",
createdAt: Date.now(),
activePaneId: "p3",
layout: { type: "pane", paneId: "p3" },
panes: {
p3: { id: "p3", kind: "test", data: { label: "p3" } },
},
},
],
activeTabId: "t1",
});

store.getState().movePaneToSplit({
sourcePaneId: "p1",
targetPaneId: "p3",
position: "right",
});

// Source tab should have p2 only
const t1 = store.getState().tabs.find((t) => t.id === "t1");
expect(t1?.panes.p1).toBeUndefined();
expect(t1?.layout).toEqual({ type: "pane", paneId: "p2" });

// Target tab should have p3 + p1
const t2 = store.getState().tabs.find((t) => t.id === "t2");
expect(t2?.panes.p1).toBeDefined();
expect(t2?.panes.p3).toBeDefined();
expect(t2?.activePaneId).toBe("p1");
expect(store.getState().activeTabId).toBe("t2");
});

it("removes source tab when last pane is moved out", () => {
const store = makeStore();
store.getState().addTab({ id: "t1", panes: [tp("p1")] });
store.getState().addTab({ id: "t2", panes: [tp("p2")] });

const tab1 = store.getState().tabs[0] as Tab<TestData>;
const tab2 = store.getState().tabs[1] as Tab<TestData>;
const p1Id = Object.keys(tab1.panes)[0] as string;
const p2Id = Object.keys(tab2.panes)[0] as string;

store.getState().movePaneToSplit({
sourcePaneId: p1Id,
targetPaneId: p2Id,
position: "right",
});

expect(store.getState().tabs).toHaveLength(1);
});

it("is a no-op when dropping on self", () => {
const store = makeStore();
store.getState().addTab({ id: "t1", panes: [tp("p1")] });

const tab0 = store.getState().tabs[0] as Tab<TestData>;
const p1Id = Object.keys(tab0.panes)[0] as string;
const before = structuredClone({
version: store.getState().version,
tabs: store.getState().tabs,
activeTabId: store.getState().activeTabId,
});

store.getState().movePaneToSplit({
sourcePaneId: p1Id,
targetPaneId: p1Id,
position: "right",
});

expect({
version: store.getState().version,
tabs: store.getState().tabs,
activeTabId: store.getState().activeTabId,
}).toEqual(before);
});
});

describe("edge cases", () => {
it("invalid IDs are no-ops", () => {
const store = makeStore();
Expand Down
Loading
Loading