(frontend) add folder export action

Expose the backend folder ZIP export from explorer folder menus
through the Download action.
This commit is contained in:
Nicolas Clerc
2026-05-28 10:43:52 +02:00
parent ded840a6e1
commit cd7bebfcb8
4 changed files with 44 additions and 0 deletions
+1
View File
@@ -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
@@ -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;
@@ -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: <Download />,
label: t("explorer.item.actions.export"),
isHidden: !item.abilities?.export || minimal,
callback: () => {
window.location.href = `${baseApiUrl()}items/${effectiveItemId}/export/`;
},
},
{
icon: <Copy />,
label: t("explorer.item.actions.duplicate"),
@@ -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");