From 5737f47a0c466ecbb8160c5f86e295f01d447f65 Mon Sep 17 00:00:00 2001 From: Todd Schiller Date: Tue, 30 Apr 2024 20:01:25 -0400 Subject: [PATCH] #8382: brick to export data url as file --- src/bricks/effects/exportFileEffect.test.ts | 40 +++++++++++ src/bricks/effects/exportFileEffect.ts | 73 +++++++++++++++++++++ src/bricks/effects/getAllEffects.ts | 2 + src/development/headers.test.ts | 2 +- 4 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 src/bricks/effects/exportFileEffect.test.ts create mode 100644 src/bricks/effects/exportFileEffect.ts diff --git a/src/bricks/effects/exportFileEffect.test.ts b/src/bricks/effects/exportFileEffect.test.ts new file mode 100644 index 0000000000..74b1afc275 --- /dev/null +++ b/src/bricks/effects/exportFileEffect.test.ts @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2024 PixieBrix, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import ExportFileEffect from "@/bricks/effects/exportFileEffect"; +import { unsafeAssumeValidArg } from "@/runtime/runtimeTypes"; +import { brickOptionsFactory } from "@/testUtils/factories/runtimeFactories"; +import download from "downloadjs"; + +jest.mock("downloadjs"); + +const downloadMock = jest.mocked(download); + +describe("ExportFileEffect", () => { + it("downloads GIF file", async () => { + const brick = new ExportFileEffect(); + + await brick.run( + unsafeAssumeValidArg({ + data: "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", + }), + brickOptionsFactory(), + ); + + expect(downloadMock).toHaveBeenCalledOnce(); + }); +}); diff --git a/src/bricks/effects/exportFileEffect.ts b/src/bricks/effects/exportFileEffect.ts new file mode 100644 index 0000000000..7840dd71d0 --- /dev/null +++ b/src/bricks/effects/exportFileEffect.ts @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2024 PixieBrix, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { EffectABC } from "@/types/bricks/effectTypes"; +import type { BrickArgs } from "@/types/runtimeTypes"; +import type { Schema } from "@/types/schemaTypes"; +import type { PlatformCapability } from "@/platform/capabilities"; +import { propertiesToSchema } from "@/utils/schemaUtils"; + +/** + * Brick to export a data URL as a file. + * @since 1.8.14 + */ +class ExportFileEffect extends EffectABC { + constructor() { + super( + "@pixiebrix/export/file", + "[Experimental] Export as File", + "Export/download a data URL as a file", + ); + } + + inputSchema: Schema = propertiesToSchema( + { + data: { + type: "string", + description: + "A data URL: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs", + }, + filename: { + type: "string", + description: "An optional filename, or exclude to generate", + }, + }, + ["data"], + ); + + override async getRequiredCapabilities(): Promise { + // XXX: might introduce a "download" capability in the future, e.g., to support making the file as an artifact + // from a headless platform run + return ["dom"]; + } + + async effect({ + filename = "exported", + data, + }: BrickArgs<{ + filename?: string; + data: string; + }>): Promise { + const { default: download } = await import( + /* webpackChunkName: "downloadjs" */ "downloadjs" + ); + + download(data, filename); + } +} + +export default ExportFileEffect; diff --git a/src/bricks/effects/getAllEffects.ts b/src/bricks/effects/getAllEffects.ts index 12f8dccb83..6a4b8f4f66 100644 --- a/src/bricks/effects/getAllEffects.ts +++ b/src/bricks/effects/getAllEffects.ts @@ -58,6 +58,7 @@ import SetToolbarBadge from "@/bricks/effects/setToolbarBadge"; import InsertAtCursorEffect from "@/bricks/effects/InsertAtCursorEffect"; import AddDynamicTextSnippet from "@/bricks/effects/AddDynamicTextSnippet"; import AddTextSnippets from "@/bricks/effects/AddTextSnippets"; +import ExportFileEffect from "@/bricks/effects/exportFileEffect"; function getAllEffects(): Brick[] { return [ @@ -80,6 +81,7 @@ function getAllEffects(): Brick[] { new AssignModVariable(), new HideEffect(), new ExportCsv(), + new ExportFileEffect(), new HideSidebar(), new ShowSidebar(), new ToggleSidebar(), diff --git a/src/development/headers.test.ts b/src/development/headers.test.ts index ca5101885c..3dfe524825 100644 --- a/src/development/headers.test.ts +++ b/src/development/headers.test.ts @@ -25,7 +25,7 @@ import registerBuiltinBricks from "@/bricks/registerBuiltinBricks"; import registerContribBlocks from "@/contrib/registerContribBlocks"; // Maintaining this number is a simple way to ensure bricks don't accidentally get dropped -const EXPECTED_HEADER_COUNT = 136; +const EXPECTED_HEADER_COUNT = 137; registerBuiltinBricks(); registerContribBlocks();