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
Expand Up @@ -2,6 +2,7 @@ import { PromptInputProvider } from "@superset/ui/ai-elements/prompt-input";
import { createFileRoute } from "@tanstack/react-router";
import { NewWorkspaceScreen } from "renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/NewWorkspaceScreen";
import { DashboardNewWorkspaceDraftProvider } from "renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/DashboardNewWorkspaceDraftContext";
import { newWorkspaceAttachmentsStore } from "renderer/stores/new-workspace-attachments";

export const Route = createFileRoute(
"/_authenticated/_dashboard/new-workspace/",
Expand All @@ -23,7 +24,7 @@ function NewWorkspacePage() {
const { projectId } = Route.useSearch();
return (
<DashboardNewWorkspaceDraftProvider onClose={() => {}}>
<PromptInputProvider>
<PromptInputProvider attachmentsStore={newWorkspaceAttachmentsStore}>
<NewWorkspaceScreen isOpen preSelectedProjectId={projectId ?? null} />
{/* Window-drag surface replacing the hidden TopBar's drag region.
Stops short of the top-right corner so the screen's naming
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { PromptInputProvider } from "@superset/ui/ai-elements/prompt-input";
import { NewWorkspaceScreen } from "renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/NewWorkspaceScreen";
import { DashboardNewWorkspaceDraftProvider } from "renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/DashboardNewWorkspaceDraftContext";
import { newWorkspaceAttachmentsStore } from "renderer/stores/new-workspace-attachments";

/**
* Experiment test arm (new-workspace-screen): replaces the "No workspaces yet"
Expand All @@ -12,7 +13,7 @@ import { DashboardNewWorkspaceDraftProvider } from "renderer/routes/_authenticat
export function NewWorkspaceEmptyScreen() {
return (
<DashboardNewWorkspaceDraftProvider onClose={() => {}}>
<PromptInputProvider>
<PromptInputProvider attachmentsStore={newWorkspaceAttachmentsStore}>
<NewWorkspaceScreen isOpen preSelectedProjectId={null} />
</PromptInputProvider>
</DashboardNewWorkspaceDraftProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from "@superset/ui/dialog";
import { useNavigate } from "@tanstack/react-router";
import { useEffect, useRef } from "react";
import { newWorkspaceAttachmentsStore } from "renderer/stores/new-workspace-attachments";
import {
useCloseNewWorkspaceModal,
useNewWorkspaceModalOpen,
Expand Down Expand Up @@ -66,7 +67,7 @@ export function DashboardNewWorkspaceModal() {

return (
<DashboardNewWorkspaceDraftProvider onClose={closeModal}>
<PromptInputProvider>
<PromptInputProvider attachmentsStore={newWorkspaceAttachmentsStore}>
<PromptInputResetSync />
<Dialog
modal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { electronTrpc } from "renderer/lib/electron-trpc";
import { showHostServiceUnavailableToast } from "renderer/lib/host-service-unavailable";
import { SupersetIcon } from "renderer/routes/_authenticated/onboarding/providers/components/SupersetIcon";
import { useLocalHostService } from "renderer/routes/_authenticated/providers/LocalHostServiceProvider";
import { newWorkspaceAttachmentPaths } from "renderer/stores/new-workspace-attachments";
import { useNewWorkspacePromptContext } from "renderer/stores/new-workspace-prompt-context";
import { useV2WorkspaceCreateDefaultsStore } from "renderer/stores/v2-workspace-create-defaults";
import { useDashboardNewWorkspaceDraft } from "../../DashboardNewWorkspaceDraftContext";
Expand Down Expand Up @@ -118,9 +119,6 @@ export function NewWorkspaceScreen({
// case (Esc-cancelled drags, drops outside the window) that an enter/leave
// counter gets permanently stuck on.
const [isDraggingFiles, setIsDraggingFiles] = useState(false);
// Source paths for dropped/picked files, keyed by filename — the attachment
// items only keep an object URL, and Finder reveal needs the original path.
const attachmentPathsRef = useRef(new Map<string, string>());
useEffect(() => {
if (!isOpen) return;
let timer: number | null = null;
Expand All @@ -132,8 +130,8 @@ export function NewWorkspaceScreen({
// Attachment items only expose the basename, so the map is
// name-keyed; a second same-named file from elsewhere makes the
// name ambiguous — poison it so no card reveals the wrong file.
const existing = attachmentPathsRef.current.get(file.name);
attachmentPathsRef.current.set(
const existing = newWorkspaceAttachmentPaths.get(file.name);
newWorkspaceAttachmentPaths.set(
file.name,
existing !== undefined && existing !== path ? "" : path,
);
Expand Down Expand Up @@ -601,7 +599,7 @@ export function NewWorkspaceScreen({
))}
{visibleFiles.map((file) => {
const sourcePath = file.filename
? attachmentPathsRef.current.get(file.filename) || null
? newWorkspaceAttachmentPaths.get(file.filename) || null
: null;
return (
<AttachmentCard
Expand Down
64 changes: 64 additions & 0 deletions apps/desktop/src/renderer/stores/new-workspace-attachments.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { beforeEach, describe, expect, it } from "bun:test";
import {
newWorkspaceAttachmentPaths,
newWorkspaceAttachmentsStore,
} from "./new-workspace-attachments";
import { useNewWorkspaceDraftStore } from "./new-workspace-draft";

const imageFile = {
id: "file-1",
type: "file" as const,
url: "blob:renderer/one",
mediaType: "image/png",
filename: "one.png",
};

describe("newWorkspaceAttachmentsStore", () => {
beforeEach(() => {
newWorkspaceAttachmentsStore.set([]);
newWorkspaceAttachmentPaths.clear();
});

it("retains files independent of any mounted provider", () => {
newWorkspaceAttachmentsStore.set([imageFile]);
expect(newWorkspaceAttachmentsStore.get()).toEqual([imageFile]);
});

it("keeps files across non-reset draft updates", () => {
newWorkspaceAttachmentsStore.set([imageFile]);
useNewWorkspaceDraftStore.getState().updateDraft({ prompt: "hello" });
expect(newWorkspaceAttachmentsStore.get()).toEqual([imageFile]);
});

it("clears files, revokes blob URLs, and drops source paths on draft reset", () => {
const revoked: string[] = [];
const original = URL.revokeObjectURL;
URL.revokeObjectURL = (url: string) => {
revoked.push(url);
};
try {
newWorkspaceAttachmentsStore.set([imageFile]);
newWorkspaceAttachmentPaths.set("one.png", "/tmp/one.png");

useNewWorkspaceDraftStore.getState().resetDraft();

expect(newWorkspaceAttachmentsStore.get()).toEqual([]);
expect(revoked).toEqual(["blob:renderer/one"]);
expect(newWorkspaceAttachmentPaths.size).toBe(0);
} finally {
URL.revokeObjectURL = original;
}
});

it("notifies subscribers when files change", () => {
let calls = 0;
const unsubscribe = newWorkspaceAttachmentsStore.subscribe(() => {
calls += 1;
});
newWorkspaceAttachmentsStore.set([imageFile]);
expect(calls).toBe(1);
unsubscribe();
newWorkspaceAttachmentsStore.set([]);
expect(calls).toBe(1);
});
});
26 changes: 26 additions & 0 deletions apps/desktop/src/renderer/stores/new-workspace-attachments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { createPromptInputAttachmentsStore } from "@superset/ui/ai-elements/prompt-input";
import { useNewWorkspaceDraftStore } from "renderer/stores/new-workspace-draft";

/**
* Module-scoped attachment state for the new-workspace draft. Keeps attached
* files alive across navigation the same way the prompt text survives in
* useNewWorkspaceDraftStore — the create surfaces mount and unmount, this
* store does not.
*/
export const newWorkspaceAttachmentsStore = createPromptInputAttachmentsStore();

/**
* Source paths for dropped/picked files, keyed by filename — attachment items
* only keep an object URL, and Finder reveal needs the original path. Module
* scoped so reveal keeps working for attachments restored after navigation.
*/
export const newWorkspaceAttachmentPaths = new Map<string, string>();

// A draft reset (successful create or explicit discard) clears attachments
// with it, even while no create surface is mounted.
useNewWorkspaceDraftStore.subscribe((state, prevState) => {
if (state.resetKey !== prevState.resetKey) {
newWorkspaceAttachmentsStore.clear();
newWorkspaceAttachmentPaths.clear();
}
});
Loading
Loading