diff --git a/CHANGELOG.md b/CHANGELOG.md index ae1b29f2..9a09ccb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to - ✨(backend) manage reconciliation requests for user accounts - ✨(backend) add recursive folder export as ZIP archive +- ✨(frontend) add folder export action ### Changed diff --git a/src/frontend/apps/drive/src/features/drivers/types.ts b/src/frontend/apps/drive/src/features/drivers/types.ts index 5acc9d2f..7a6b20ac 100644 --- a/src/frontend/apps/drive/src/features/drivers/types.ts +++ b/src/frontend/apps/drive/src/features/drivers/types.ts @@ -79,6 +79,7 @@ export type Item = { children_create: boolean; children_list: boolean; destroy: boolean; + export: boolean; favorite: boolean; invite_owner: boolean; link_configuration: boolean; diff --git a/src/frontend/apps/drive/src/features/explorer/hooks/useItemActionMenuItems.tsx b/src/frontend/apps/drive/src/features/explorer/hooks/useItemActionMenuItems.tsx index f9ba0bec..39be937d 100644 --- a/src/frontend/apps/drive/src/features/explorer/hooks/useItemActionMenuItems.tsx +++ b/src/frontend/apps/drive/src/features/explorer/hooks/useItemActionMenuItems.tsx @@ -20,6 +20,7 @@ import { useGlobalExplorer, } from "../components/GlobalExplorerContext"; import { useDownloadItem } from "@/features/items/hooks/useDownloadItem"; +import { baseApiUrl } from "@/features/api/utils"; import { ExplorerRenameItemModal } from "../components/modals/ExplorerRenameItemModal"; import { ExplorerCreateFolderModal } from "../components/modals/ExplorerCreateFolderModal"; import { ItemShareModal } from "../components/modals/share/ItemShareModal"; @@ -164,6 +165,14 @@ export const useItemActionMenuItems = ({ handleDownloadItem(item); }, }, + { + icon: , + label: t("explorer.item.actions.export"), + isHidden: !item.abilities?.export || minimal, + callback: () => { + window.location.href = `${baseApiUrl()}items/${effectiveItemId}/export/`; + }, + }, { icon: , label: t("explorer.item.actions.duplicate"), diff --git a/src/frontend/apps/e2e/__tests__/app-drive/context-menu.spec.ts b/src/frontend/apps/e2e/__tests__/app-drive/context-menu.spec.ts index 884cf227..ea474d56 100644 --- a/src/frontend/apps/e2e/__tests__/app-drive/context-menu.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-drive/context-menu.spec.ts @@ -79,11 +79,44 @@ test.describe("Context menu", () => { ).toBeVisible(); await expect(page.getByRole("menuitem", { name: "Share" })).toBeVisible(); await expect(page.getByRole("menuitem", { name: "Move" })).toBeVisible(); + await expect( + page.getByRole("menuitem", { name: "Export" }), + ).toBeVisible(); await expect(page.getByRole("menuitem", { name: "Rename" })).toBeVisible(); await expect(page.getByRole("menuitem", { name: "Star" })).toBeVisible(); await expect(page.getByRole("menuitem", { name: "Delete" })).toBeVisible(); }); + test("Right-click on folder > Export calls the export endpoint", async ({ + page, + }) => { + await createFolderInCurrentFolder(page, "TestFolder"); + + await page.route("**/api/v1.0/items/*/export/", async (route) => { + await route.fulfill({ + status: 200, + headers: { + "content-disposition": "attachment; filename=test.zip", + "content-type": "application/zip", + }, + body: "", + }); + }); + + const exportRequestPromise = page.waitForRequest((request) => + /\/api\/v1\.0\/items\/[^/]+\/export\/$/.test(request.url()), + ); + + const row = await getRowItem(page, "TestFolder"); + await row.click({ button: "right" }); + await page.getByRole("menuitem", { name: "Export" }).click(); + + const exportRequest = await exportRequestPromise; + expect(exportRequest.url()).toMatch( + /\/api\/v1\.0\/items\/[^/]+\/export\/$/, + ); + }); + test("Right-click on item > Rename works", async ({ page }) => { await createFolderInCurrentFolder(page, "TestFolder");