Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/toast-notification-removed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@openworkflowspec/diagram-editor": minor
---

Toast notification removed for side panel buttons
1 change: 0 additions & 1 deletion packages/open-workflow-diagram-editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@
"html-to-image": "catalog:",
"js-yaml": "catalog:",
"radix-ui": "catalog:",
"sonner": "catalog:",
"use-sync-external-store": "catalog:"
},
"devDependencies": {
Expand Down
40 changes: 0 additions & 40 deletions packages/open-workflow-diagram-editor/src/components/ui/sonner.css

This file was deleted.

51 changes: 0 additions & 51 deletions packages/open-workflow-diagram-editor/src/components/ui/sonner.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import { useResolvedColorMode } from "../hooks/useResolvedColorMode";
import { SidebarProvider } from "@/components/ui/sidebar";
import { SidePanel } from "@/side-panel/SidePanel";
import { DiagramEditorErrorBoundary } from "./error-pages/DiagramEditorErrorBoundary";
import { Toaster } from "@/components/ui/sonner";

/**
* Imperative handle exposed by `DiagramEditor` via `ref`.
Expand Down Expand Up @@ -184,7 +183,6 @@ export const DiagramEditor = React.forwardRef<DiagramEditorRef, DiagramEditorPro
editorRef={ref}
/>
</I18nProvider>
<Toaster theme={resolvedColorMode} />
</div>
);
},
Expand Down
5 changes: 2 additions & 3 deletions packages/open-workflow-diagram-editor/src/i18n/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ export const en = {
"sidebar.exportMermaid.copy": "Copy Mermaid Code",
"sidebar.exportMermaid.download": "Download as Mermaid File",
"sidebar.exportMermaid.copied": "Copied!",
"sidebar.export.downloaded": "Downloaded",

@lornakelly lornakelly Sep 23, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It doesnt need to say downloaded, I think just flashing downloading is fine - its obvious when its downloaded in browser

"sidebar.exportPng.download": "Download as PNG",
"sidebar.exportPng.downloading": "Downloading...",
"aria.minimap.hide": "Hide minimap",
"aria.minimap.show": "Show minimap",
"aria.badge": "Badge:",
Expand All @@ -53,9 +55,6 @@ export const en = {
"aria.panel.exportActions": "Export actions",
"workflowError.autoLayout.title": "Layout Error",
"workflowError.autoLayout.message": "Failed to apply auto-layout to the diagram.",
"toast.clipboard.error": "Failed to copy",
"toast.download.success": "Download started",
"toast.download.error": "Download failed",
} as const;

export type TranslationKeys = keyof typeof en;
2 changes: 0 additions & 2 deletions packages/open-workflow-diagram-editor/src/lib/download.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ export function downloadFile(content: string, filename: string, mimeType = "text
const link = document.createElement("a");
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
setTimeout(() => {
URL.revokeObjectURL(url);
}, 100);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import * as React from "react";
import { useI18n } from "@openworkflowspec/i18n";
import { ClipboardPen, Download, ClipboardCheck, FileImage } from "lucide-react";
import { ClipboardPen, Download, ClipboardCheck, FileImage, Import } from "lucide-react";
import { useReactFlow, useStore } from "@xyflow/react";
import { Button } from "@/components/ui/button";
import { exportToMermaid } from "@/core";
Expand All @@ -26,12 +26,13 @@ import { exportDiagramAsPng } from "@/lib/exportPng";
import { sanitizeFilename } from "@/lib/utils";
import { useDiagramEditorContext } from "@/store/DiagramEditorContext";
import type { Specification } from "@openworkflowspec/sdk";
import { toast } from "sonner";

export function WorkflowActions({ model }: { model: Specification.Workflow }): React.JSX.Element {
const { t } = useI18n();
const [isCopied, setIsCopied] = React.useState(false);
const [downloadedType, setDownloadedType] = React.useState<"mermaid" | "png" | null>(null);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Considering there is no need to show downloaded, it will simplify changes to this file, if you can make those updates to strip out that code

const copyTimeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
const downloadTimeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
const reactFlowInstance = useReactFlow();
const diagramDomNode = useStore((s) => s.domNode);
const { isExporting, setIsExporting } = useDiagramEditorContext();
Expand All @@ -41,6 +42,10 @@ export function WorkflowActions({ model }: { model: Specification.Workflow }): R
if (copyTimeoutRef.current) {
clearTimeout(copyTimeoutRef.current);
}

if (downloadTimeoutRef.current) {
clearTimeout(downloadTimeoutRef.current);
}
};
}, []);

Expand All @@ -59,9 +64,10 @@ export function WorkflowActions({ model }: { model: Specification.Workflow }): R
copyTimeoutRef.current = null;
}, 2000);
} catch (error) {
toast.error(t("toast.clipboard.error"), {
description: error instanceof Error ? error.message : undefined,
});
console.error(
"Failed to copy Mermaid code:",
error instanceof Error ? error.message : undefined,
);
}
};

Expand All @@ -70,11 +76,21 @@ export function WorkflowActions({ model }: { model: Specification.Workflow }): R
const mermaidCode = exportToMermaid(model);
const filename = `${sanitizeFilename(model.document?.name)}.mmd`;
downloadFile(mermaidCode, filename);
toast.success(t("toast.download.success"));
setDownloadedType("mermaid");

if (downloadTimeoutRef.current) {
clearTimeout(downloadTimeoutRef.current);
}

downloadTimeoutRef.current = setTimeout(() => {
setDownloadedType(null);
downloadTimeoutRef.current = null;
}, 2000);
} catch (error) {
toast.error(t("toast.download.error"), {
description: error instanceof Error ? error.message : undefined,
});
console.error(
"Failed to download Mermaid file:",
error instanceof Error ? error.message : undefined,
);
}
};

Expand All @@ -86,11 +102,22 @@ export function WorkflowActions({ model }: { model: Specification.Workflow }): R
`${sanitizeFilename(model.document?.name)}.png`,
diagramDomNode,
);
toast.success(t("toast.download.success"));

setDownloadedType("png");

if (downloadTimeoutRef.current) {
clearTimeout(downloadTimeoutRef.current);
}

downloadTimeoutRef.current = setTimeout(() => {
setDownloadedType(null);
downloadTimeoutRef.current = null;
}, 2000);
} catch (error) {
toast.error(t("toast.download.error"), {
description: error instanceof Error ? error.message : undefined,
});
console.error(
"Failed to export diagram as PNG:",
error instanceof Error ? error.message : undefined,
);
} finally {
setIsExporting(false);
}
Expand All @@ -113,8 +140,10 @@ export function WorkflowActions({ model }: { model: Specification.Workflow }): R
size="sm"
className="dec:cursor-pointer"
>
<Download />
{t("sidebar.exportMermaid.download")}
{downloadedType === "mermaid" ? <Import /> : <Download />}
{downloadedType === "mermaid"
? t("sidebar.export.downloaded")
: t("sidebar.exportMermaid.download")}
</Button>
<Button
onClick={handleExportPng}
Expand All @@ -123,8 +152,12 @@ export function WorkflowActions({ model }: { model: Specification.Workflow }): R
className="dec:cursor-pointer"
disabled={isExporting}
>
<FileImage />
{t("sidebar.exportPng.download")}
{downloadedType === "png" ? <Import /> : <FileImage />}
{isExporting
? t("sidebar.exportPng.downloading")
: downloadedType === "png"
? t("sidebar.export.downloaded")
: t("sidebar.exportPng.download")}
</Button>
</>
);
Expand Down
23 changes: 18 additions & 5 deletions packages/open-workflow-diagram-editor/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,28 @@
*/

/* layer order (Priority: lowest -> highest) */

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The ordering in this file was deliberate before so not sue why it changed?

I would revert it and add your new block after import but it should be


/* layer order (Priority: lowest -> highest) */
@layer base,
  side-panel,
  react-flow-overrides,
  custom-nodes,
  custom-edge-labels;

@import 'tailwindcss' prefix(dec);
@import './components/ui/shadcn.css';

@custom-variant dark (&:where(.dec-root.dark, .dec-root.dark *));

@layer base {
  .dec-root,
  .dec-root *,
  .dec-root::before,
  .dec-root::after,
  .dec-root::backdrop,
  .dec-root *::before,
  .dec-root *::after,
  .dec-root *::backdrop {
    --tw-border-style: solid;
  }
}


@import 'tailwindcss' prefix(dec);
@import './components/ui/shadcn.css';

@layer base {
.dec-root,
.dec-root *,
.dec-root::before,
.dec-root::after,
.dec-root::backdrop,
.dec-root *::before,
.dec-root *::after,
.dec-root *::backdrop {
--tw-border-style: solid;
}
}

@layer base,
side-panel,
react-flow-overrides,
custom-nodes,
custom-edge-labels,
sonner;

@import 'tailwindcss' prefix(dec);
@import './components/ui/shadcn.css';
custom-edge-labels;

@custom-variant dark (&:where(.dec-root.dark, .dec-root.dark *));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I also see further down this file there are toast tokens that are no longer used if you can remove please

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ describe("downloadFile", () => {

it("creates and triggers file download", () => {
const mockClick = vi.fn();
const mockAppendChild = vi.fn();
const mockRemoveChild = vi.fn();
const mockElement = {
click: mockClick,
href: "",
Expand All @@ -35,17 +33,15 @@ describe("downloadFile", () => {
vi.spyOn(document, "createElement").mockReturnValue(
mockElement as unknown as HTMLAnchorElement,
);
vi.spyOn(document.body, "appendChild").mockImplementation(mockAppendChild);
vi.spyOn(document.body, "removeChild").mockImplementation(mockRemoveChild);
vi.spyOn(URL, "createObjectURL").mockReturnValue("blob:mock-url");
vi.spyOn(URL, "revokeObjectURL").mockImplementation(vi.fn());

const testCode = "flowchart TD\n A --> B";
downloadFile(testCode, "test.mmd");

expect(document.createElement).toHaveBeenCalledWith("a");
expect(mockElement.href).toBe("blob:mock-url");
expect(mockElement.download).toBe("test.mmd");
expect(mockClick).toHaveBeenCalled();
expect(mockAppendChild).toHaveBeenCalled();
expect(mockRemoveChild).toHaveBeenCalled();
});
});
Loading
Loading