From e3a8c2e81884ab47fc234072903ecd32f68044db Mon Sep 17 00:00:00 2001 From: Juan Manuel Spoleti <104365141+juans-chainsafe@users.noreply.github.com> Date: Fri, 8 Jul 2022 09:24:47 -0300 Subject: [PATCH 01/14] Rename folder and file in Storage test (#2214) * replace switch for type in bucket test * progress rename bucket folder and file * progress rename bucket folder and file * rename folder and file in storage tests * suggestions in PR done for rename file or folder test storage * fix in rename file in Files test * fix rename test in files --- .../cypress/tests/file-management.cy.ts | 20 +-- .../page-objects/bucketContentsPage.ts | 29 +++- .../support/page-objects/bucketsPage.ts | 20 +-- .../page-objects/modals/createFolderModal.ts | 7 + .../cypress/support/utils/TestConstants.ts | 4 - .../cypress/tests/bucket-management.cy.ts | 151 +++++++++++++++++- .../src/Components/Layouts/AppNav.tsx | 1 + .../CreateFolderModal/CreateFolderModal.tsx | 10 +- .../FileSystemItem/FileSystemTableItem.tsx | 1 + 9 files changed, 215 insertions(+), 28 deletions(-) create mode 100644 packages/storage-ui/cypress/support/page-objects/modals/createFolderModal.ts delete mode 100644 packages/storage-ui/cypress/support/utils/TestConstants.ts diff --git a/packages/files-ui/cypress/tests/file-management.cy.ts b/packages/files-ui/cypress/tests/file-management.cy.ts index f180c4c236..10d23fcbb2 100644 --- a/packages/files-ui/cypress/tests/file-management.cy.ts +++ b/packages/files-ui/cypress/tests/file-management.cy.ts @@ -225,28 +225,30 @@ describe("File management", () => { homePage.fileItemRow().should("have.length", 1) // ensure an error is displayed if the edited name is blank - homePage.fileItemKebabButton().first().click() + homePage.fileItemKebabButton().click() homePage.renameMenuOption().click() homePage.fileRenameInput().type("{selectall}{del}") homePage.fileRenameErrorLabel().should("be.visible") - // rename a file - homePage.fileRenameInput().type(`${newName}{enter}`) - homePage.fileItemName().contains(newName) - // ensure the original name persists if the rename submission is blank - homePage.fileItemKebabButton().first().click() + homePage.fileItemKebabButton().click() homePage.renameMenuOption().click() homePage.fileRenameInput().type("{selectall}{del}{esc}") homePage.fileRenameInput().should("not.exist") + homePage.fileItemName().contains("text-file.txt") + + // rename a file + homePage.fileItemKebabButton().click() + homePage.renameMenuOption().click() + homePage.fileRenameInput().type(`{selectall}${newName}{enter}`) homePage.fileItemName().contains(newName) // ensure that the name is reset when renaming is canceled - homePage.fileItemKebabButton().first().click() + homePage.fileItemKebabButton().click() homePage.renameMenuOption().click() - homePage.fileRenameInput().type("{selectall}abc{del}{esc}") + homePage.fileRenameInput().type("{selectall}{del}abc{esc}") homePage.fileRenameInput().should("not.exist") - homePage.fileItemKebabButton().first().click() + homePage.fileItemKebabButton().click() homePage.renameMenuOption().click() homePage.fileRenameInput().should("have.value", newName) }) diff --git a/packages/storage-ui/cypress/support/page-objects/bucketContentsPage.ts b/packages/storage-ui/cypress/support/page-objects/bucketContentsPage.ts index 474c97d88e..29824fa695 100644 --- a/packages/storage-ui/cypress/support/page-objects/bucketContentsPage.ts +++ b/packages/storage-ui/cypress/support/page-objects/bucketContentsPage.ts @@ -1,4 +1,6 @@ import { basePage } from "./basePage" +import { createFolderModal } from "./modals/createFolderModal" +import { fileUploadModal } from "./modals/fileUploadModal" export const bucketContentsPage = { ...basePage, @@ -9,9 +11,11 @@ export const bucketContentsPage = { uploadButton: () => cy.get("[data-testid=button-upload-file]"), // file or folder browser row elements - fileItemKebabButton: () => cy.get("[data-testid=icon-file-item-kebab]"), + fileItemKebabButton: () => cy.get("[data-testid=dropdown-title-file-item-kebab]"), fileItemName: () => cy.get("[data-cy=label-file-item-name]"), fileItemRow: () => cy.get("[data-cy=row-file-item]"), + fileRenameInput: () => cy.get("[data-cy=input-rename-file-or-folder]"), + fileRenameErrorLabel: () => cy.get("[data-cy=form-rename] span.minimal.error"), // kebab menu elements downloadMenuOption: () => cy.get("[data-cy=menu-download]"), @@ -23,5 +27,28 @@ export const bucketContentsPage = { awaitBucketRefresh() { cy.intercept("POST", "**/bucket/*/ls").as("refresh") cy.wait("@refresh") + }, + + uploadFileToBucket(filePath: string) { + this.uploadButton().click() + fileUploadModal.body().attachFile(filePath) + fileUploadModal.fileList().should("have.length", 1) + fileUploadModal.uploadButton().safeClick() + fileUploadModal.body().should("not.exist") + this.awaitBucketRefresh() + }, + + createNewFolder(folderName: string) { + this.newFolderButton().click() + createFolderModal.body().should("exist") + createFolderModal.folderNameInput().type(folderName) + createFolderModal.createButton().click() + createFolderModal.body().should("not.exist") + }, + + renameFileOrFolder(newName: string) { + bucketContentsPage.fileItemKebabButton().click() + bucketContentsPage.renameMenuOption().click() + bucketContentsPage.fileRenameInput().type(newName) } } diff --git a/packages/storage-ui/cypress/support/page-objects/bucketsPage.ts b/packages/storage-ui/cypress/support/page-objects/bucketsPage.ts index dda685fad6..e98155f99b 100644 --- a/packages/storage-ui/cypress/support/page-objects/bucketsPage.ts +++ b/packages/storage-ui/cypress/support/page-objects/bucketsPage.ts @@ -1,6 +1,5 @@ // Only add things here that could be applicable to the bucket page -import { FILE_SYSTEM_TYPES } from "../utils/TestConstants" import { basePage } from "./basePage" import { createBucketModal } from "./modals/createBucketModal" @@ -24,19 +23,22 @@ export const bucketsPage = { deleteBucketMenuOption: () => cy.get("[data-cy=menu-delete-bucket]"), // helpers and convenience functions - createBucket(bucketName: string, bucketType: FILE_SYSTEM_TYPES) { + awaitBucketRefresh() { + cy.intercept("GET", "**/buckets/summary").as("refresh") + cy.wait("@refresh") + }, + + createBucket(bucketName: string, bucketType: "ipfs" | "chainsafe") { this.createBucketButton().click() createBucketModal.body().should("be.visible") createBucketModal.bucketNameInput().type(bucketName) - switch (bucketType) { - case FILE_SYSTEM_TYPES.IPFS: - createBucketModal.ipfsRadioInput().click() - break - case FILE_SYSTEM_TYPES.CHAINSAFE: - createBucketModal.chainsafeRadioInput().click() - break + if (bucketType == "ipfs") { + createBucketModal.ipfsRadioInput().click() + } else { + createBucketModal.chainsafeRadioInput().click() } createBucketModal.submitButton().click() createBucketModal.body().should("not.exist") + this.awaitBucketRefresh() } } diff --git a/packages/storage-ui/cypress/support/page-objects/modals/createFolderModal.ts b/packages/storage-ui/cypress/support/page-objects/modals/createFolderModal.ts new file mode 100644 index 0000000000..f06d7f3a33 --- /dev/null +++ b/packages/storage-ui/cypress/support/page-objects/modals/createFolderModal.ts @@ -0,0 +1,7 @@ +export const createFolderModal = { + body: () => cy.get("[data-cy=modal-create-folder]", { timeout: 10000 }), + cancelButton: () => cy.get("[data-cy=button-cancel-create-folder]"), + createButton: () => cy.get("[data-cy=button-create-folder]", { timeout: 10000 }), + errorLabel: () => cy.get("[data-cy=form-folder-creation] span.default.error"), + folderNameInput: () => cy.get("[data-cy=input-folder-name]") +} \ No newline at end of file diff --git a/packages/storage-ui/cypress/support/utils/TestConstants.ts b/packages/storage-ui/cypress/support/utils/TestConstants.ts deleted file mode 100644 index 00f55cfab3..0000000000 --- a/packages/storage-ui/cypress/support/utils/TestConstants.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum FILE_SYSTEM_TYPES { - CHAINSAFE, - IPFS -} \ No newline at end of file diff --git a/packages/storage-ui/cypress/tests/bucket-management.cy.ts b/packages/storage-ui/cypress/tests/bucket-management.cy.ts index 28582db3c5..7c2f9155c5 100644 --- a/packages/storage-ui/cypress/tests/bucket-management.cy.ts +++ b/packages/storage-ui/cypress/tests/bucket-management.cy.ts @@ -5,7 +5,6 @@ import { navigationMenu } from "../support/page-objects/navigationMenu" import { fileUploadModal } from "../support/page-objects/modals/fileUploadModal" import { deleteBucketModal } from "../support/page-objects/modals/deleteBucketModal" import { uploadCompleteToast } from "../support/page-objects/toasts/uploadCompleteToast" -import { FILE_SYSTEM_TYPES } from "../support/utils/TestConstants" describe("Bucket management", () => { @@ -115,9 +114,9 @@ describe("Bucket management", () => { cy.web3Login({ deleteFpsBuckets: true }) navigationMenu.bucketsNavButton().click() - bucketsPage.createBucket(chainSafeBucketName, FILE_SYSTEM_TYPES.CHAINSAFE) + bucketsPage.createBucket(chainSafeBucketName, "chainsafe") bucketsPage.bucketItemRow().should("have.length", 1) - bucketsPage.createBucket(ipfsBucketName, FILE_SYSTEM_TYPES.IPFS) + bucketsPage.createBucket(ipfsBucketName, "ipfs") bucketsPage.bucketItemRow().should("have.length", 2) // by default should be sort by date uploading in ascending order (oldest first) @@ -144,5 +143,151 @@ describe("Bucket management", () => { bucketsPage.bucketItemName().eq(0).should("have.text", chainSafeBucketName) bucketsPage.bucketItemName().eq(1).should("have.text", ipfsBucketName) }) + + it("can rename a folder inside the ipfs bucket", () => { + const ipfsBucketName = `ipfs bucket ${Date.now()}` + const folderName = `folder ${Date.now()}` + const newFolderName = `new folder name ${Date.now()}` + + cy.web3Login({ deleteFpsBuckets: true }) + navigationMenu.bucketsNavButton().click() + + // create a new bucket and go inside the bucket + bucketsPage.createBucket(ipfsBucketName, "ipfs") + bucketsPage.bucketItemRow().should("have.length", 1) + bucketsPage.bucketItemName().dblclick() + + // create a folder inside the bucket + bucketContentsPage.createNewFolder(folderName) + + // ensure an error is displayed if the edited name of the folder is blank + bucketContentsPage.renameFileOrFolder("{selectall}{del}") + bucketContentsPage.fileRenameErrorLabel().should("be.visible") + + // ensure the original name of the folder persists if the rename submission is blank + bucketContentsPage.renameFileOrFolder("{selectall}{del}{esc}") + bucketContentsPage.fileRenameInput().should("not.exist") + bucketContentsPage.fileItemName().contains(folderName) + + // rename a folder + bucketContentsPage.renameFileOrFolder(`{selectall}${newFolderName}{enter}`) + bucketContentsPage.fileItemName().contains(newFolderName) + + // ensure that the name of the folder is reset when renaming is canceled + bucketContentsPage.renameFileOrFolder("{selectall}{del}}abc{esc}") + bucketContentsPage.fileRenameInput().should("not.exist") + bucketContentsPage.fileItemKebabButton().click() + bucketContentsPage.renameMenuOption().click() + bucketContentsPage.fileRenameInput().should("have.value", newFolderName) + }) + + it("can rename a folder inside the chainsafe bucket", () => { + const chainsafeBucketName = `chainsafe bucket ${Date.now()}` + const folderName = `folder ${Date.now()}` + const newFolderName = `new folder name ${Date.now()}` + + cy.web3Login({ deleteFpsBuckets: true }) + navigationMenu.bucketsNavButton().click() + + // create a new bucket and go inside the bucket + bucketsPage.createBucket(chainsafeBucketName, "chainsafe") + bucketsPage.bucketItemRow().should("have.length", 1) + bucketsPage.bucketItemName().dblclick() + + // create a folder inside the bucket + bucketContentsPage.createNewFolder(folderName) + + // ensure an error is displayed if the edited name of the folder is blank + bucketContentsPage.renameFileOrFolder("{selectall}{del}") + bucketContentsPage.fileRenameErrorLabel().should("be.visible") + + // ensure the original name of the folder persists if the rename submission is blank + bucketContentsPage.renameFileOrFolder("{selectall}{del}{esc}") + bucketContentsPage.fileRenameInput().should("not.exist") + bucketContentsPage.fileItemName().contains(folderName) + + // rename a folder + bucketContentsPage.renameFileOrFolder(`{selectall}${newFolderName}{enter}`) + bucketContentsPage.fileItemName().contains(newFolderName) + + // ensure that the name of the folder is reset when renaming is canceled + bucketContentsPage.renameFileOrFolder("{selectall}{del}abc{esc}") + bucketContentsPage.fileRenameInput().should("not.exist") + bucketContentsPage.fileItemKebabButton().click() + bucketContentsPage.renameMenuOption().click() + bucketContentsPage.fileRenameInput().should("have.value", newFolderName) + }) + + it("can rename a file inside the ipfs bucket", () => { + const ipfsBucketName = `ipfs bucket ${Date.now()}` + const newFileName = `new file name ${Date.now()}` + + cy.web3Login({ deleteFpsBuckets: true }) + navigationMenu.bucketsNavButton().click() + + // create a new bucket and go inside the bucket + bucketsPage.createBucket(ipfsBucketName, "ipfs") + bucketsPage.bucketItemRow().should("have.length", 1) + bucketsPage.bucketItemName().dblclick() + + // upload a file + bucketContentsPage.uploadFileToBucket("../fixtures/uploadedFiles/logo.png") + + // ensure an error is displayed if the edited name of the file is blank + bucketContentsPage.renameFileOrFolder("{selectall}{del}") + bucketContentsPage.fileRenameErrorLabel().should("be.visible") + + // ensure the original name of the file persists if the rename submission is blank + bucketContentsPage.renameFileOrFolder("{selectall}{del}{esc}") + bucketContentsPage.fileRenameInput().should("not.exist") + bucketContentsPage.fileItemName().contains("logo.png") + + // rename the file + bucketContentsPage.renameFileOrFolder(`{selectall}${newFileName}{enter}`) + bucketContentsPage.fileItemName().contains(newFileName) + + // ensure that the name of the file is reset when renaming is canceled + bucketContentsPage.renameFileOrFolder("{selectall}{del}abc{esc}") + bucketContentsPage.fileRenameInput().should("not.exist") + bucketContentsPage.fileItemKebabButton().click() + bucketContentsPage.renameMenuOption().click() + bucketContentsPage.fileRenameInput().should("have.value", newFileName) + }) + + it("can rename a file inside the chainsafe bucket", () => { + const chainsafeBucketName = `chainsafe bucket ${Date.now()}` + const newFileName = `new file name ${Date.now()}` + + cy.web3Login({ deleteFpsBuckets: true }) + navigationMenu.bucketsNavButton().click() + + // create a new bucket and go inside the bucket + bucketsPage.createBucket(chainsafeBucketName, "chainsafe") + bucketsPage.bucketItemRow().should("have.length", 1) + bucketsPage.bucketItemName().dblclick() + + // upload a file + bucketContentsPage.uploadFileToBucket("../fixtures/uploadedFiles/logo.png") + + // ensure an error is displayed if the edited name of the file is blank + bucketContentsPage.renameFileOrFolder("{selectall}{del}") + bucketContentsPage.fileRenameErrorLabel().should("be.visible") + + // ensure the original name of the file persists if the rename submission is blank + bucketContentsPage.renameFileOrFolder("{selectall}{del}{esc}") + bucketContentsPage.fileRenameInput().should("not.exist") + bucketContentsPage.fileItemName().contains("logo.png") + + // rename the file + bucketContentsPage.renameFileOrFolder(`{selectall}${newFileName}{enter}`) + bucketContentsPage.fileItemName().contains(newFileName) + + // ensure that the name of the file is reset when renaming is canceled + bucketContentsPage.renameFileOrFolder("{selectall}{del}abc{esc}") + bucketContentsPage.fileRenameInput().should("not.exist") + bucketContentsPage.fileItemKebabButton().click() + bucketContentsPage.renameMenuOption().click() + bucketContentsPage.fileRenameInput().should("have.value", newFileName) + }) }) }) diff --git a/packages/storage-ui/src/Components/Layouts/AppNav.tsx b/packages/storage-ui/src/Components/Layouts/AppNav.tsx index 2a9c0bd063..7b0f55fc32 100644 --- a/packages/storage-ui/src/Components/Layouts/AppNav.tsx +++ b/packages/storage-ui/src/Components/Layouts/AppNav.tsx @@ -303,6 +303,7 @@ const AppNav: React.FC = ({ navOpen, setNavOpen }: IAppNav) => { {desktop && (
diff --git a/packages/storage-ui/src/Components/Modules/CreateFolderModal/CreateFolderModal.tsx b/packages/storage-ui/src/Components/Modules/CreateFolderModal/CreateFolderModal.tsx index ff5526b88d..f0c6381c12 100644 --- a/packages/storage-ui/src/Components/Modules/CreateFolderModal/CreateFolderModal.tsx +++ b/packages/storage-ui/src/Components/Modules/CreateFolderModal/CreateFolderModal.tsx @@ -107,8 +107,11 @@ const CreateFolderModal = ({ modalOpen, close }: ICreateFolderModalProps) => { }} enableReinitialize > -
-
+ +
{!desktop && ( { className={classes.input} > { justifyContent="flex-end" > close()} size="medium" variant={"outline"} @@ -153,6 +158,7 @@ const CreateFolderModal = ({ modalOpen, close }: ICreateFolderModalProps) => { Cancel
- + { cid } From 3c43b959834a3f204942b96f63e7ec969480f970 Mon Sep 17 00:00:00 2001 From: Tanmoy Basak Anjan Date: Tue, 26 Jul 2022 15:51:40 +0600 Subject: [PATCH 05/14] Feat/nft grid view 2206 (#2213) * nft page * icons * NFTs page is ready * making grid * grid ready * copy system * nft grid ready * lingui extract * spacings * lingui extract * reading NFTs * NFT async load * NFTs grid * spacings * no loader system * type cleanings * api client update * gateway and asyncs * undefined in state * update api-client * updates * no nfts show * lingui extract * package update * lock file * loaders * lingui extract Co-authored-by: GitHub Actions Co-authored-by: Thibaut Sardan <33178835+Tbaut@users.noreply.github.com> Co-authored-by: Michael Yankelev --- .../src/Icons/icons/FileWithImage.icon.tsx | 7 + packages/common-components/src/Icons/index.ts | 1 + .../src/Icons/svgs/file-with-image.svg | 5 + packages/storage-ui/package.json | 2 +- .../src/Components/Elements/CidRow.tsx | 3 +- .../src/Components/Layouts/AppNav.tsx | 19 ++- .../Modules/FilesList/FilesList.tsx | 1 + .../Components/Modules/NFTsList/NFTItem.tsx | 149 +++++++++++++++++ .../Components/Modules/NFTsList/NFTsList.tsx | 152 ++++++++++++++++++ .../src/Components/Pages/NFTsPage.tsx | 77 +++++++++ .../src/Components/StorageRoutes.tsx | 11 +- .../src/Contexts/FileBrowserContext.tsx | 2 +- .../src/Contexts/StorageContext.tsx | 8 +- packages/storage-ui/src/Utils/Constants.ts | 3 + .../storage-ui/src/locales/en/messages.po | 18 +++ yarn.lock | 40 +++++ 16 files changed, 490 insertions(+), 8 deletions(-) create mode 100644 packages/common-components/src/Icons/icons/FileWithImage.icon.tsx create mode 100644 packages/common-components/src/Icons/svgs/file-with-image.svg create mode 100644 packages/storage-ui/src/Components/Modules/NFTsList/NFTItem.tsx create mode 100644 packages/storage-ui/src/Components/Modules/NFTsList/NFTsList.tsx create mode 100644 packages/storage-ui/src/Components/Pages/NFTsPage.tsx diff --git a/packages/common-components/src/Icons/icons/FileWithImage.icon.tsx b/packages/common-components/src/Icons/icons/FileWithImage.icon.tsx new file mode 100644 index 0000000000..c7dbddc615 --- /dev/null +++ b/packages/common-components/src/Icons/icons/FileWithImage.icon.tsx @@ -0,0 +1,7 @@ +import * as React from "react" +import createSvgIcon from "../createSvgIcon" +import { ReactComponent as FileWithImageSvg } from "../svgs/file-with-image.svg" + +export { FileWithImageSvg } + +export default createSvgIcon() diff --git a/packages/common-components/src/Icons/index.ts b/packages/common-components/src/Icons/index.ts index 02290953a4..65bc4cf983 100644 --- a/packages/common-components/src/Icons/index.ts +++ b/packages/common-components/src/Icons/index.ts @@ -49,6 +49,7 @@ export { default as FileImageIcon, FileImageSvg } from "./icons/FileImage.icon" export { default as FilePdfIcon, FilePdfSvg } from "./icons/FilePdf.icon" export { default as FileTextIcon, FileTextSvg } from "./icons/FileText.icon" export { default as FileVideoIcon, FileVideoSvg } from "./icons/FileVideo.icon" +export { default as FileWithImageIcon, FileWithImageSvg } from "./icons/FileWithImage.icon" export { default as FolderIcon, FolderSvg } from "./icons/Folder.icon" export { default as FolderFilledIcon, FolderFilledSvg } from "./icons/FolderFilled.icon" export { default as FullscreenIcon, FullscreenSvg } from "./icons/Fullscreen.icon" diff --git a/packages/common-components/src/Icons/svgs/file-with-image.svg b/packages/common-components/src/Icons/svgs/file-with-image.svg new file mode 100644 index 0000000000..0eb3466d63 --- /dev/null +++ b/packages/common-components/src/Icons/svgs/file-with-image.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/storage-ui/package.json b/packages/storage-ui/package.json index e44ad5ffe9..37d11fa268 100644 --- a/packages/storage-ui/package.json +++ b/packages/storage-ui/package.json @@ -6,7 +6,7 @@ "@babel/core": "^7.12.10", "@babel/runtime": "^7.0.0", "@chainsafe/browser-storage-hooks": "^1.0.1", - "@chainsafe/files-api-client": "1.18.37", + "@chainsafe/files-api-client": "1.18.42", "@chainsafe/web3-context": "1.3.0", "@emeraldpay/hashicon-react": "0.5.2", "@lingui/core": "^3.7.2", diff --git a/packages/storage-ui/src/Components/Elements/CidRow.tsx b/packages/storage-ui/src/Components/Elements/CidRow.tsx index 3e774af211..538d5e288c 100644 --- a/packages/storage-ui/src/Components/Elements/CidRow.tsx +++ b/packages/storage-ui/src/Components/Elements/CidRow.tsx @@ -8,6 +8,7 @@ import { CSSTheme } from "../../Themes/types" import { useStorage } from "../../Contexts/StorageContext" import { desktopGridSettings, mobileGridSettings } from "../Pages/CidsPage" import { trimChar } from "../../Utils/pathUtils" +import { IPFS_GATEWAY } from "../../Utils/Constants" const useStyles = makeStyles(({ animation, constants, breakpoints, palette }: CSSTheme) => createStyles({ @@ -66,8 +67,6 @@ interface Props { pinStatus: PinStatus } -const IPFS_GATEWAY = process.env.REACT_APP_IPFS_GATEWAY || "https://ipfs.io/ipfs/" - const CidRow = ({ pinStatus }: Props) => { const classes = useStyles() const { unpin } = useStorage() diff --git a/packages/storage-ui/src/Components/Layouts/AppNav.tsx b/packages/storage-ui/src/Components/Layouts/AppNav.tsx index 7b0f55fc32..4cc4d30471 100644 --- a/packages/storage-ui/src/Components/Layouts/AppNav.tsx +++ b/packages/storage-ui/src/Components/Layouts/AppNav.tsx @@ -17,7 +17,8 @@ import { PowerDownIcon, useLocation, KeySvg, - CreditCardOutlinedSvg + CreditCardOutlinedSvg, + FileWithImageSvg } from "@chainsafe/common-components" import { ROUTE_LINKS } from "../StorageRoutes" import { Trans } from "@lingui/macro" @@ -257,7 +258,7 @@ interface IAppNav { setNavOpen: (state: boolean) => void } -type AppNavTab = "buckets" | "cids" | "settings" | "api-keys" | "subscription" +type AppNavTab = "buckets" | "cids" | "nfts" | "settings" | "api-keys" | "subscription" const AppNav: React.FC = ({ navOpen, setNavOpen }: IAppNav) => { const { desktop } = useThemeSwitcher() @@ -283,6 +284,7 @@ const AppNav: React.FC = ({ navOpen, setNavOpen }: IAppNav) => { case "cids": return "cids" case "buckets": return "buckets" case "bucket": return "buckets" + case "nfts": return "nfts" case "api-keys": return "api-keys" case "subscription": return "subscription" case "settings": return "settings" @@ -342,6 +344,19 @@ const AppNav: React.FC = ({ navOpen, setNavOpen }: IAppNav) => { CIDs + + + + NFTs + + { const getItemOperations = useCallback( (contentType: string) => { + if (!itemOperations) return [] const result = Object.keys(itemOperations).reduce( (acc: FileOperation[], item: string) => { const matcher = new MimeMatcher(item) diff --git a/packages/storage-ui/src/Components/Modules/NFTsList/NFTItem.tsx b/packages/storage-ui/src/Components/Modules/NFTsList/NFTItem.tsx new file mode 100644 index 0000000000..b1555849e1 --- /dev/null +++ b/packages/storage-ui/src/Components/Modules/NFTsList/NFTItem.tsx @@ -0,0 +1,149 @@ +import React, { useEffect, useState } from "react" +import { makeStyles, createStyles } from "@chainsafe/common-theme" +import { + Typography, + Button +} from "@chainsafe/common-components" +import { CSSTheme } from "../../../Themes/types" +import { Trans } from "@lingui/macro" +import clsx from "clsx" +import axios from "axios" +import { trimChar } from "../../../Utils/pathUtils" +import { IPFS_GATEWAY } from "../../../Utils/Constants" + +const useStyles = makeStyles(({ constants, palette }: CSSTheme) => + createStyles({ + root: { + maxWidth: "100%" + }, + imageBox: { + width: "100%", + objectFit: "cover", + marginBottom: constants.generalUnit + }, + nameTitle: { + marginBottom: constants.generalUnit * 0.5 + }, + nameContainer: { + borderBottom: `1px solid ${palette.additional["gray"][6]}`, + marginBottom: constants.generalUnit * 0.5 + }, + cidRow: { + display: "flex", + alignItems: "center", + justifyContent: "space-between", + "&.clickable": { + cursor: "pointer" + } + }, + cidStartSection: { + display: "table", + tableLayout: "fixed", + width: "100%", + whiteSpace: "nowrap" + }, + cidSubtitle: { + width: "35px", + display: "table-cell", + color: palette.additional["gray"][7] + }, + cid: { + display: "table-cell", + overflow: "hidden", + textOverflow: "ellipsis" + }, + copyButton: { + marginLeft: constants.generalUnit, + color: palette.primary.main, + padding: `${constants.generalUnit * 0.5}px 0 !important` + }, + copiedText: { + marginLeft: constants.generalUnit, + padding: `${constants.generalUnit * 0.5}px 0 !important` + } + }) +) + +interface NFTData { + name: string + CID: string + image?: string +} + +const NFTItem = ({ CID }: {CID: string}) => { + const classes = useStyles() + const [isCopied, setIsCopied] = useState(false) + const [NFTData, setNFTData] = useState() + + useEffect(() => { + axios.get(`${trimChar(IPFS_GATEWAY, "/")}/${CID}`) + .then(({ data }) => { setNFTData({ + CID, + name: data.name, + image: data.image?.replace("ipfs://", `${trimChar(IPFS_GATEWAY, "/")}/`) + }) }) + .catch(console.error) + }, [CID]) + + if (!NFTData) return null + + return ( +
+ {NFTData.name} +
+ + {NFTData.name} + +
+
{ + navigator.clipboard.writeText(CID) + setIsCopied(true) + setInterval(() => setIsCopied(false), 3000) + }} + > +
+ + CID :  + + + {CID} + +
+ {isCopied + ? + Copied! + + : + } +
+
+ ) +} + +export default NFTItem diff --git a/packages/storage-ui/src/Components/Modules/NFTsList/NFTsList.tsx b/packages/storage-ui/src/Components/Modules/NFTsList/NFTsList.tsx new file mode 100644 index 0000000000..ca90eeb48a --- /dev/null +++ b/packages/storage-ui/src/Components/Modules/NFTsList/NFTsList.tsx @@ -0,0 +1,152 @@ +import React from "react" +import { makeStyles, createStyles } from "@chainsafe/common-theme" +import { + Typography, + Button, + PlusIcon, + Divider, + Link, + Loading +} from "@chainsafe/common-components" +import { CSSTheme } from "../../../Themes/types" +import { Trans } from "@lingui/macro" +import { ROUTE_LINKS } from "../../StorageRoutes" +import NFTItem from "./NFTItem" +import { useFileBrowser } from "../../../Contexts/FileBrowserContext" +import EmptySvg from "../../../Media/Empty.svg" + +const useStyles = makeStyles(({ constants, breakpoints }: CSSTheme) => + createStyles({ + root: { + position: "relative", + [breakpoints.down("md")]: { + margin: constants.generalUnit + } + }, + loaderText: { + marginTop: constants.generalUnit * 2 + }, + loadingContainer: { + display: "flex", + flexDirection: "column", + alignItems: "center", + justifyContent: "center", + marginTop: constants.generalUnit * 8 + }, + noFiles: { + display: "flex", + flexDirection: "column", + justifyContent: "center", + alignItems: "center", + marginTop: constants.generalUnit * 20, + "& svg": { + maxWidth: 180, + marginBottom: constants.generalUnit * 3 + } + }, + header: { + display: "flex", + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + marginTop: constants.generalUnit * 2, + marginBottom: constants.generalUnit * 2, + [breakpoints.down("md")]: { + marginTop: constants.generalUnit + } + }, + controls: { + display: "flex", + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + "& > button": { + marginLeft: constants.generalUnit + }, + "& > a": { + textDecoration: "none" + } + }, + nftGrid: { + display: "grid", + gridColumnGap: constants.generalUnit * 5, + gridRowGap: constants.generalUnit * 7, + gridTemplateColumns: "1fr 1fr 1fr 1fr", + marginBottom: constants.generalUnit * 12, + [breakpoints.down("lg")]: { + gridTemplateColumns: "1fr 1fr 1fr", + gridColumnGap: constants.generalUnit * 3, + gridRowGap: constants.generalUnit * 5 + }, + [breakpoints.down("sm")]: { + gridTemplateColumns: "1fr 1fr" + } + } + }) +) + +const NFTsList = () => { + const classes = useStyles() + const { sourceFiles, loadingCurrentPath } = useFileBrowser() + + return ( +
+
+ + NFTs + +
+ + + +
+
+ + {loadingCurrentPath + ?
+ + + One sec, getting NFTs... + +
+ : !sourceFiles.length + ?
+ + + No NFTs to show + +
+ :
+ {sourceFiles?.map((sourceFile, i) => + + ) + } +
+ } +
+ ) +} + +export default NFTsList diff --git a/packages/storage-ui/src/Components/Pages/NFTsPage.tsx b/packages/storage-ui/src/Components/Pages/NFTsPage.tsx new file mode 100644 index 0000000000..2f7078a5c6 --- /dev/null +++ b/packages/storage-ui/src/Components/Pages/NFTsPage.tsx @@ -0,0 +1,77 @@ +import React, { useCallback, useEffect, useState } from "react" +import { useStorage, FileSystemItem } from "../../Contexts/StorageContext" +import { IFileBrowserModuleProps } from "../../Contexts/types" +import { useStorageApi } from "../../Contexts/StorageApiContext" +import { FileBrowserContext } from "../../Contexts/FileBrowserContext" +import { parseFileContentResponse } from "../../Utils/Helpers" +import { useLocalStorage } from "@chainsafe/browser-storage-hooks" +import { DISMISSED_SURVEY_KEY } from "../Modules/SurveyBanner" +import { usePageTrack } from "../../Contexts/PosthogContext" +import { Helmet } from "react-helmet-async" +import NFTsList from "../Modules/NFTsList/NFTsList" + + +const BucketPage: React.FC = () => { + const { NFTBucket, getStorageSummary } = useStorage() + const { storageApiClient } = useStorageApi() + const { localStorageGet, localStorageSet } = useLocalStorage() + + const [loadingCurrentPath, setLoadingCurrentPath] = useState(false) + const [pathContents, setPathContents] = useState([]) + const showSurvey = localStorageGet(DISMISSED_SURVEY_KEY) === "false" + usePageTrack() + + useEffect(() => { + const dismissedFlag = localStorageGet(DISMISSED_SURVEY_KEY) + + if (dismissedFlag === undefined || dismissedFlag === null) { + localStorageSet(DISMISSED_SURVEY_KEY, "false") + } + }, [localStorageGet, localStorageSet]) + + const refreshContents = useCallback((showLoading?: boolean) => { + if (!NFTBucket) return + showLoading && setLoadingCurrentPath(true) + storageApiClient.getBucketObjectChildrenList(NFTBucket.id, { path: "/" }) + .then((newContents) => { + showLoading && setLoadingCurrentPath(false) + const mappedContents = newContents.map((fcr) => parseFileContentResponse(fcr)) + setPathContents(mappedContents) + }).catch(error => { + console.error(error) + }).finally(() => { + getStorageSummary() + showLoading && setLoadingCurrentPath(false) + }) + }, [NFTBucket, storageApiClient, getStorageSummary]) + + useEffect(() => { + refreshContents(true) + }, [refreshContents]) + + return ( + <> + + + NFTs - Chainsafe Storage + + + + + ) +} + +export default BucketPage diff --git a/packages/storage-ui/src/Components/StorageRoutes.tsx b/packages/storage-ui/src/Components/StorageRoutes.tsx index 54dadcae39..e482637d4e 100644 --- a/packages/storage-ui/src/Components/StorageRoutes.tsx +++ b/packages/storage-ui/src/Components/StorageRoutes.tsx @@ -10,6 +10,7 @@ import BillingHistory from "./Pages/BillingHistory" import UploadNFTPage from "./Pages/UploadNFTPage" import ApiKeysPage from "./Pages/ApiKeysPage" import SubscriptionPage from "./Pages/SubscriptionPage" +import NFTsPage from "./Pages/NFTsPage" export const SETTINGS_BASE = "/settings" export const SETTINGS_PATHS = ["apiKeys", "plan"] as const @@ -33,7 +34,8 @@ export const ROUTE_LINKS = { BucketRoot: "/bucket", Bucket: (id: string, bucketPath: string) => `/bucket/${id}${bucketPath}`, DiscordInvite: "https://discord.gg/YYFqgHp4Tu", - UploadNFT: "/upload-nft" + UploadNFT: "/upload-nft", + NFTs: "/nfts" } const StorageRoutes = () => { @@ -103,6 +105,13 @@ const StorageRoutes = () => { redirectPath={ROUTE_LINKS.Buckets} redirectToSource /> + Promise moveItems?: (toMove: ISelectedFile[], newPath: string) => Promise diff --git a/packages/storage-ui/src/Contexts/StorageContext.tsx b/packages/storage-ui/src/Contexts/StorageContext.tsx index d0cad27474..e0053d0533 100644 --- a/packages/storage-ui/src/Contexts/StorageContext.tsx +++ b/packages/storage-ui/src/Contexts/StorageContext.tsx @@ -57,6 +57,7 @@ type StorageContext = { refreshPins: (params?: RefreshPinParams) => void unpin: (requestId: string) => void storageBuckets: Bucket[] + NFTBucket: Bucket | undefined createBucket: (name: string, fileSystemType: FileSystemType) => Promise removeBucket: (id: string) => Promise refreshBuckets: () => void @@ -85,6 +86,7 @@ const StorageProvider = ({ children }: StorageContextProps) => { const { storageApiClient, isLoggedIn } = useStorageApi() const [storageSummary, setBucketSummary] = useState() const [storageBuckets, setStorageBuckets] = useState([]) + const [NFTBucket, setNFTBucket] = useState() const [pins, setPins] = useState([]) const [pinsParams, setPinsParams] = useState<{ pageNumber: number @@ -221,7 +223,10 @@ const StorageProvider = ({ children }: StorageContextProps) => { const refreshBuckets = useCallback(() => { storageApiClient.listBuckets() - .then((buckets) => setStorageBuckets(buckets.filter(b => b.type === "fps"))) + .then((buckets) => { + setNFTBucket(buckets.find(b => b.type === "nft")) + setStorageBuckets(buckets.filter(b => b.type === "fps")) + }) .catch(console.error) .finally(() => getStorageSummary()) }, [storageApiClient, getStorageSummary]) @@ -529,6 +534,7 @@ const StorageProvider = ({ children }: StorageContextProps) => { onPreviousPins, unpin, storageBuckets, + NFTBucket, downloadFile, createBucket, removeBucket, diff --git a/packages/storage-ui/src/Utils/Constants.ts b/packages/storage-ui/src/Utils/Constants.ts index 0cff32342c..9ef76b1ac5 100644 --- a/packages/storage-ui/src/Utils/Constants.ts +++ b/packages/storage-ui/src/Utils/Constants.ts @@ -9,3 +9,6 @@ export enum CONTENT_TYPES { Audio = "audio/*" } +export const IPFS_GATEWAY = process.env.REACT_APP_IPFS_GATEWAY || "https://ipfs.io/ipfs/" + + diff --git a/packages/storage-ui/src/locales/en/messages.po b/packages/storage-ui/src/locales/en/messages.po index 47f2666e99..d9cc58ffb9 100644 --- a/packages/storage-ui/src/locales/en/messages.po +++ b/packages/storage-ui/src/locales/en/messages.po @@ -133,6 +133,9 @@ msgstr "By connecting your wallet, you agree to our <0>Terms of Service and msgid "By switching plan, you will loose access to:" msgstr "By switching plan, you will loose access to:" +msgid "CID" +msgstr "CID" + msgid "CID invalid" msgstr "CID invalid" @@ -223,6 +226,9 @@ msgstr "Continue with Web3 Wallet" msgid "Copied!" msgstr "Copied!" +msgid "Copy" +msgstr "Copy" + msgid "Create" msgstr "Create" @@ -406,6 +412,9 @@ msgstr "Move to..." msgid "NFT" msgstr "NFT" +msgid "NFTs" +msgstr "NFTs" + msgid "Name" msgstr "Name" @@ -415,6 +424,9 @@ msgstr "Name (optional)" msgid "New Key" msgstr "New Key" +msgid "New NFT" +msgstr "New NFT" + msgid "New folder" msgstr "New folder" @@ -424,6 +436,9 @@ msgstr "Next payment" msgid "No Card" msgstr "No Card" +msgid "No NFTs to show" +msgstr "No NFTs to show" + msgid "No files to show" msgstr "No files to show" @@ -445,6 +460,9 @@ msgstr "Older notifications" msgid "Once you proceed, your account is expected to make a payment within 60 minutes. If no payment is received , your plan will not change." msgstr "Once you proceed, your account is expected to make a payment within 60 minutes. If no payment is received , your plan will not change." +msgid "One sec, getting NFTs..." +msgstr "One sec, getting NFTs..." + msgid "One sec, getting files ready..." msgstr "One sec, getting files ready..." diff --git a/yarn.lock b/yarn.lock index 94adec5971..37ffb5fcf4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1941,6 +1941,23 @@ "@redocly/openapi-core" "^1.0.0-beta.69" redoc-cli "^0.12.3" +"@chainsafe/files-api-client@1.18.42": + version "1.18.42" + resolved "https://registry.yarnpkg.com/@chainsafe/files-api-client/-/files-api-client-1.18.42.tgz#529ac39b2bcffd5b15569149382b8328d782a29a" + integrity sha512-h0tSk5eUZZTBaiHt+rkNQ6RAsIoDnHMFSev4qYpRkZHAM4hGt4UT0l+4D6Nr+zefFiXYQ6xmGbqCiz/MdIDOeA== + dependencies: + "@chainsafe/form-data-encoder" "^1.0.0" + "@redocly/openapi-cli" "^1.0.0-beta.58" + "@redocly/openapi-core" "^1.0.0-beta.69" + browser-or-node "^2.0.0" + formdata-node "^4.3.3" + redoc-cli "^0.12.3" + +"@chainsafe/form-data-encoder@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@chainsafe/form-data-encoder/-/form-data-encoder-1.0.0.tgz#1e1df2c9c8153478f0021c71ef654f210c2ef280" + integrity sha512-rQpAMNJJm10IRCe43ZDLgvOgjf5l6G96VCAlpdhtuBeUhVURA+ybOOlmD036Q1tDfmecuNdCvxuOMkl0FXwZfA== + "@chainsafe/web3-context@1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@chainsafe/web3-context/-/web3-context-1.3.0.tgz#e546f160145dce12e00845d53f1a00165f6a96b2" @@ -9228,6 +9245,11 @@ brorand@^1.0.1, brorand@^1.1.0: resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= +browser-or-node@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/browser-or-node/-/browser-or-node-2.0.0.tgz#808ea90282a670931cdc0ea98166538a50dd0d89" + integrity sha512-3Lrks/Okgof+/cRguUNG+qRXSeq79SO3hY4QrXJayJofwJwHiGC0qi99uDjsfTwULUFSr1OGVsBkdIkygKjTUA== + browser-process-hrtime@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" @@ -13800,6 +13822,14 @@ format@^0.2.0: resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" integrity sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs= +formdata-node@^4.3.3: + version "4.3.3" + resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-4.3.3.tgz#21415225be66e2c87a917bfc0fedab30a119c23c" + integrity sha512-coTew7WODO2vF+XhpUdmYz4UBvlsiTMSNaFYZlrXIqYbFd4W7bMwnoALNLE6uvNgzTg2j1JDF0ZImEfF06VPAA== + dependencies: + node-domexception "1.0.0" + web-streams-polyfill "4.0.0-beta.1" + formidable@^1.2.0: version "1.2.2" resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.2.tgz#bf69aea2972982675f00865342b982986f6b8dd9" @@ -18307,6 +18337,11 @@ node-dir@^0.1.10: dependencies: minimatch "^3.0.2" +node-domexception@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + node-fetch-h2@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/node-fetch-h2/-/node-fetch-h2-2.3.0.tgz#c6188325f9bd3d834020bf0f2d6dc17ced2241ac" @@ -24877,6 +24912,11 @@ wcwidth@^1.0.1: dependencies: defaults "^1.0.3" +web-streams-polyfill@4.0.0-beta.1: + version "4.0.0-beta.1" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.1.tgz#3b19b9817374b7cee06d374ba7eeb3aeb80e8c95" + integrity sha512-3ux37gEX670UUphBF9AMCq8XM6iQ8Ac6A+DSRRjDoRBm1ufCkaCDdNVbaqq60PsEkdNlLKrGtv/YBP4EJXqNtQ== + web3-core-helpers@1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.3.6.tgz#c478246a9abe4e5456acf42657dac2f7c330be74" From b3d6d5b126bc70b047c5fb63f6ff499f4c2c5d8b Mon Sep 17 00:00:00 2001 From: Juan Manuel Spoleti <104365141+juans-chainsafe@users.noreply.github.com> Date: Tue, 26 Jul 2022 10:18:52 -0300 Subject: [PATCH 06/14] [Storage] pin same cid twice test (#2234) * pin same cid twice test * Update packages/storage-ui/cypress/tests/cid-management.cy.ts Co-authored-by: Andrew Snaith Co-authored-by: Andrew Snaith --- .../storage-ui/cypress/tests/cid-management.cy.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/storage-ui/cypress/tests/cid-management.cy.ts b/packages/storage-ui/cypress/tests/cid-management.cy.ts index c7080ff414..64c54fef0a 100644 --- a/packages/storage-ui/cypress/tests/cid-management.cy.ts +++ b/packages/storage-ui/cypress/tests/cid-management.cy.ts @@ -42,7 +42,7 @@ describe("CID management", () => { cidsPage.cidItemRow().should("contain.text", "queued") }) - it("can see a warning when attempting to pin the same CID twice", () => { + it("can pin the same CID twice following initial warning", () => { cy.web3Login({ withNewSession: true }) navigationMenu.cidsNavButton().click() @@ -52,8 +52,19 @@ describe("CID management", () => { // see warning if attempting to pin the cid again cidsPage.pinButton().click() addCidModal.body().should("be.visible") + addCidModal.nameInput().type(testCidName) addCidModal.cidInput().type(testCid) addCidModal.cidPinnedWarningLabel().should("be.visible") + + // confirm pin same cid + addCidModal.pinSubmitButton().click() + + // ensure both cid are pinned in the table + cidsPage.cidItemRow().should("have.length", 2) + cidsPage.cidItemRow().within(() => { + cidsPage.cidNameCell().eq(0).should("have.text", testCidName) + cidsPage.cidNameCell().eq(1).should("have.text", testCidName) + }) }) it("can search via name or cid", () => { From 604c9aa253590b63b1d5783926cfdf2451fb8964 Mon Sep 17 00:00:00 2001 From: Juan Manuel Spoleti <104365141+juans-chainsafe@users.noreply.github.com> Date: Tue, 26 Jul 2022 12:51:41 -0300 Subject: [PATCH 07/14] [Storage] Delete file and folder (ipfs and chainsafe types) inside a bucket test (#2233) * delete file in chainsafe bucket progress * delete file and folder test * Update packages/storage-ui/cypress/tests/bucket-management.cy.ts Co-authored-by: Andrew Snaith * Update packages/storage-ui/cypress/tests/bucket-management.cy.ts Co-authored-by: Andrew Snaith * Update packages/storage-ui/cypress/tests/bucket-management.cy.ts Co-authored-by: Andrew Snaith * Update packages/storage-ui/cypress/tests/bucket-management.cy.ts Co-authored-by: Andrew Snaith * Update packages/storage-ui/cypress/tests/bucket-management.cy.ts Co-authored-by: Andrew Snaith Co-authored-by: Andrew Snaith --- .../page-objects/bucketContentsPage.ts | 8 ++ .../page-objects/modals/deleteFileModal.ts | 5 ++ .../toasts/deletionCompleteToast.ts | 4 + .../cypress/tests/bucket-management.cy.ts | 83 +++++++++++++++++++ .../Modules/FilesList/FilesList.tsx | 1 + .../src/Components/Pages/BucketPage.tsx | 3 +- 6 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 packages/storage-ui/cypress/support/page-objects/modals/deleteFileModal.ts create mode 100644 packages/storage-ui/cypress/support/page-objects/toasts/deletionCompleteToast.ts diff --git a/packages/storage-ui/cypress/support/page-objects/bucketContentsPage.ts b/packages/storage-ui/cypress/support/page-objects/bucketContentsPage.ts index 690fd7090e..bb224bbcf2 100644 --- a/packages/storage-ui/cypress/support/page-objects/bucketContentsPage.ts +++ b/packages/storage-ui/cypress/support/page-objects/bucketContentsPage.ts @@ -1,5 +1,6 @@ import { basePage } from "./basePage" import { createFolderModal } from "./modals/createFolderModal" +import { deleteFileModal } from "./modals/deleteFileModal" import { fileUploadModal } from "./modals/fileUploadModal" export const bucketContentsPage = { @@ -51,5 +52,12 @@ export const bucketContentsPage = { bucketContentsPage.fileItemKebabButton().click() bucketContentsPage.renameMenuOption().click() bucketContentsPage.fileRenameInput().type(newName) + }, + + deleteFileOrFolder(){ + bucketContentsPage.fileItemKebabButton().click() + bucketContentsPage.deleteMenuOption().click() + deleteFileModal.body().should("be.visible") + deleteFileModal.confirmButton().click() } } diff --git a/packages/storage-ui/cypress/support/page-objects/modals/deleteFileModal.ts b/packages/storage-ui/cypress/support/page-objects/modals/deleteFileModal.ts new file mode 100644 index 0000000000..1477df6696 --- /dev/null +++ b/packages/storage-ui/cypress/support/page-objects/modals/deleteFileModal.ts @@ -0,0 +1,5 @@ +export const deleteFileModal = { + body: () => cy.get("[data-testid=modal-container-file-deletion]"), + cancelButton: () => cy.get("[data-testid=button-reject-file-deletion]"), + confirmButton: () => cy.get("[data-testid=button-confirm-file-deletion]", { timeout: 10000 }) +} \ No newline at end of file diff --git a/packages/storage-ui/cypress/support/page-objects/toasts/deletionCompleteToast.ts b/packages/storage-ui/cypress/support/page-objects/toasts/deletionCompleteToast.ts new file mode 100644 index 0000000000..b339add6b4 --- /dev/null +++ b/packages/storage-ui/cypress/support/page-objects/toasts/deletionCompleteToast.ts @@ -0,0 +1,4 @@ +export const deletionCompleteToast = { + body: () => cy.get("[data-testid=toast-deletion-complete]", { timeout: 10000 }), + closeButton: () => cy.get("[data-testid=button-close-toast-deletion-complete]") +} diff --git a/packages/storage-ui/cypress/tests/bucket-management.cy.ts b/packages/storage-ui/cypress/tests/bucket-management.cy.ts index 1eed5edc9a..68997a4710 100644 --- a/packages/storage-ui/cypress/tests/bucket-management.cy.ts +++ b/packages/storage-ui/cypress/tests/bucket-management.cy.ts @@ -6,6 +6,7 @@ import { fileUploadModal } from "../support/page-objects/modals/fileUploadModal" import { deleteBucketModal } from "../support/page-objects/modals/deleteBucketModal" import { uploadCompleteToast } from "../support/page-objects/toasts/uploadCompleteToast" import { downloadCompleteToast } from "../support/page-objects/toasts/downloadCompleteToast" +import { deletionCompleteToast } from "../support/page-objects/toasts/deletionCompleteToast" describe("Bucket management", () => { @@ -431,5 +432,87 @@ describe("Bucket management", () => { bucketContentsPage.fileItemCid().should("have.text", text) }) }) + + it("can delete a file inside the chainsafe bucket", () => { + const chainsafeBucketName = `chainsafe bucket ${Date.now()}` + + cy.web3Login({ deleteFpsBuckets: true, createFpsBuckets: [{ name: chainsafeBucketName, type: "chainsafe" }] }) + navigationMenu.bucketsNavButton().click() + + // go inside the bucket + bucketsPage.bucketItemRow().should("have.length", 1) + bucketsPage.bucketItemName().dblclick() + + // upload a file + bucketContentsPage.uploadFileToBucket("../fixtures/uploadedFiles/logo.png") + + // delete the file + bucketContentsPage.deleteFileOrFolder() + deletionCompleteToast.body().should("be.visible") + deletionCompleteToast.closeButton().click() + bucketContentsPage.fileItemRow().should("have.length", 0) + }) + + it("can delete a folder inside the chainsafe bucket", () => { + const chainsafeBucketName = `chainsafe bucket ${Date.now()}` + const folderName = `folder ${Date.now()}` + + cy.web3Login({ deleteFpsBuckets: true, createFpsBuckets: [{ name: chainsafeBucketName, type: "chainsafe" }] }) + navigationMenu.bucketsNavButton().click() + + // go inside the bucket + bucketsPage.bucketItemRow().should("have.length", 1) + bucketsPage.bucketItemName().dblclick() + + // create a folder inside the bucket + bucketContentsPage.createNewFolder(folderName) + + // delete the folder + bucketContentsPage.deleteFileOrFolder() + deletionCompleteToast.body().should("be.visible") + deletionCompleteToast.closeButton().click() + bucketContentsPage.fileItemRow().should("have.length", 0) + }) + + it("can delete a file inside the ipfs bucket", () => { + const ipfsBucketName = `ipfs bucket ${Date.now()}` + + cy.web3Login({ deleteFpsBuckets: true, createFpsBuckets: [{ name: ipfsBucketName, type: "ipfs" }] }) + navigationMenu.bucketsNavButton().click() + + // go inside the bucket + bucketsPage.bucketItemRow().should("have.length", 1) + bucketsPage.bucketItemName().dblclick() + + // upload a file + bucketContentsPage.uploadFileToBucket("../fixtures/uploadedFiles/logo.png") + + // delete the file + bucketContentsPage.deleteFileOrFolder() + deletionCompleteToast.body().should("be.visible") + deletionCompleteToast.closeButton().click() + bucketContentsPage.fileItemRow().should("have.length", 0) + }) + + it("can delete a folder inside the ipfs bucket", () => { + const ipfsBucketName = `ipfs bucket ${Date.now()}` + const folderName = `folder ${Date.now()}` + + cy.web3Login({ deleteFpsBuckets: true, createFpsBuckets: [{ name: ipfsBucketName, type: "ipfs" }] }) + navigationMenu.bucketsNavButton().click() + + // go inside the bucket + bucketsPage.bucketItemRow().should("have.length", 1) + bucketsPage.bucketItemName().dblclick() + + // create a folder inside the bucket + bucketContentsPage.createNewFolder(folderName) + + // delete the folder + bucketContentsPage.deleteFileOrFolder() + deletionCompleteToast.body().should("be.visible") + deletionCompleteToast.closeButton().click() + bucketContentsPage.fileItemRow().should("have.length", 0) + }) }) }) diff --git a/packages/storage-ui/src/Components/Modules/FilesList/FilesList.tsx b/packages/storage-ui/src/Components/Modules/FilesList/FilesList.tsx index e3b883c50b..41a87701ae 100644 --- a/packages/storage-ui/src/Components/Modules/FilesList/FilesList.tsx +++ b/packages/storage-ui/src/Components/Modules/FilesList/FilesList.tsx @@ -1181,6 +1181,7 @@ const FilesList = () => { e.preventDefault() e.stopPropagation() }} + testId="file-deletion" /> { refreshContents && ( diff --git a/packages/storage-ui/src/Components/Pages/BucketPage.tsx b/packages/storage-ui/src/Components/Pages/BucketPage.tsx index e816239373..e98f700f9c 100644 --- a/packages/storage-ui/src/Components/Pages/BucketPage.tsx +++ b/packages/storage-ui/src/Components/Pages/BucketPage.tsx @@ -84,7 +84,8 @@ const BucketPage: React.FC = () => { }).then(() => { addToast({ title: t`Deletion successful`, - type: "success" + type: "success", + testId: "deletion-complete" }) }) .catch((e) => { From 84b396edf4e96ce4956cf3c5cfa12ee384865cd6 Mon Sep 17 00:00:00 2001 From: Thibaut Sardan <33178835+Tbaut@users.noreply.github.com> Date: Mon, 1 Aug 2022 17:47:31 +0200 Subject: [PATCH 08/14] [Storage] Allow renaming a bucket (#2201) * edit * lingui extract * fix text selection and ready to be reviewed * fix error overflow Co-authored-by: GitHub Actions Co-authored-by: Michael Yankelev <12774278+FSM1@users.noreply.github.com> --- .../src/Components/Elements/BucketRow.tsx | 177 +++++++++++++++--- .../src/Components/Pages/BucketsPage.tsx | 11 +- .../src/Contexts/StorageContext.tsx | 9 +- .../storage-ui/src/locales/en/messages.po | 3 + 4 files changed, 170 insertions(+), 30 deletions(-) diff --git a/packages/storage-ui/src/Components/Elements/BucketRow.tsx b/packages/storage-ui/src/Components/Elements/BucketRow.tsx index 2f07f7b7a5..ae341621c9 100644 --- a/packages/storage-ui/src/Components/Elements/BucketRow.tsx +++ b/packages/storage-ui/src/Components/Elements/BucketRow.tsx @@ -1,12 +1,27 @@ -import React, { useMemo } from "react" -import { makeStyles, createStyles } from "@chainsafe/common-theme" -import { DeleteSvg, formatBytes, IMenuItem, MenuDropdown, MoreIcon, TableCell, TableRow, useHistory } from "@chainsafe/common-components" -import { Trans } from "@lingui/macro" +import React, { useEffect, useMemo, useRef, useState, useCallback } from "react" +import { makeStyles, createStyles, useOnClickOutside } from "@chainsafe/common-theme" +import { + DeleteSvg, + EditSvg, + formatBytes, + FormikTextInput, + IMenuItem, + Loading, + MenuDropdown, + MoreIcon, + TableCell, + TableRow, + Typography, + useHistory +} from "@chainsafe/common-components" +import { t, Trans } from "@lingui/macro" import { Bucket } from "@chainsafe/files-api-client" import { CSSTheme } from "../../Themes/types" import { desktopGridSettings, mobileGridSettings } from "../Pages/BucketsPage" import { ROUTE_LINKS } from "../StorageRoutes" import clsx from "clsx" +import { Form, FormikProvider, useFormik } from "formik" +import { nameValidator } from "../../Utils/validationSchema" const useStyles = makeStyles(({ animation, constants, breakpoints }: CSSTheme) => createStyles({ @@ -34,15 +49,6 @@ const useStyles = makeStyles(({ animation, constants, breakpoints }: CSSTheme) = fill: constants.fileSystemItemRow.menuIcon } }, - name: { - textAlign: "left", - whiteSpace: "nowrap", - textOverflow: "ellipsis", - overflow: "hidden", - "&.editing": { - overflow: "visible" - } - }, tableRow: { border: "2px solid transparent", transitionDuration: `${animation.transform}ms`, @@ -57,6 +63,36 @@ const useStyles = makeStyles(({ animation, constants, breakpoints }: CSSTheme) = gridTemplateColumns: mobileGridSettings }, cursor: "pointer" + }, + loadingIcon: { + marginLeft: constants.generalUnit, + verticalAlign: "middle" + }, + nameRename: { + display: "flex", + flexDirection: "row", + "& svg": { + width: 20, + height: 20 + } + }, + filename: { + textAlign: "left", + whiteSpace: "nowrap", + textOverflow: "ellipsis", + overflow: "hidden", + "&.editing": { + overflow: "visible" + } + }, + renameInput: { + width: "100%", + [breakpoints.up("md")]: { + margin: 0 + }, + [breakpoints.down("md")]: { + margin: `${constants.generalUnit * 4.2}px 0` + } } }) ) @@ -64,22 +100,73 @@ interface Props { bucket: Bucket onRemoveBucket: (bucket: Bucket) => void handleContextMenu: (e: React.MouseEvent, items?: IMenuItem[]) => void + handleRename: (bucket: Bucket, newName: string) => Promise } -const BucketRow = ({ bucket, onRemoveBucket, handleContextMenu }: Props) => { +const BucketRow = ({ bucket, onRemoveBucket, handleContextMenu, handleRename }: Props) => { const classes = useStyles() const { redirect } = useHistory() - const menuItems = useMemo(() => [{ - contents: ( - <> - - - Delete bucket - - - ), - onClick: () => onRemoveBucket(bucket) - }], [bucket, classes, onRemoveBucket]) + const menuItems = useMemo(() => [ + { + contents: ( + <> + + + Rename + + + ), + onClick: () => setIsRenaming(true) + }, + { + contents: ( + <> + + + Delete bucket + + + ), + onClick: () => onRemoveBucket(bucket) + }], [bucket, classes, onRemoveBucket]) + + const [isEditingLoading, setIsEditingLoading] = useState(false) + const [isRenaming, setIsRenaming] = useState(false) + const renameInputRef = useRef() + const formRef = useRef(null) + + useEffect(() => { + if (isRenaming && renameInputRef?.current) { + renameInputRef.current.focus() + } + }, [isRenaming]) + + const formik = useFormik({ + initialValues:{ name: bucket.name }, + enableReinitialize: true, + validationSchema: nameValidator, + onSubmit:(values, { resetForm }) => { + const newName = values.name?.trim() + + if (newName !== bucket.name && !!newName && handleRename) { + setIsEditingLoading(true) + + handleRename(bucket, newName) + .then(() => setIsEditingLoading(false)) + } else { + stopEditing() + } + setIsRenaming(false) + resetForm() + } + }) + + const stopEditing = useCallback(() => { + setIsRenaming(false) + formik.resetForm() + }, [formik, setIsRenaming]) + + useOnClickOutside(formRef, formik.submitForm) return ( { onContextMenu={(e) => handleContextMenu(e, menuItems)} > redirect(ROUTE_LINKS.Bucket(bucket.id, "/"))}> - {bucket.name || bucket.id} + onClick={() => !isRenaming && redirect(ROUTE_LINKS.Bucket(bucket.id, "/"))} + > + {!isRenaming + ? <> + {bucket.name || bucket.id} + {isEditingLoading && } + + : ( + + + { + if (event.key === "Escape") { + stopEditing() + } + }} + placeholder = {t`Please enter a bucket name`} + autoFocus + ref={renameInputRef} + /> + + + ) + } { const classes = useStyles() - const { storageBuckets, createBucket, refreshBuckets, removeBucket } = useStorage() + const { storageBuckets, createBucket, refreshBuckets, removeBucket, editBucket } = useStorage() const { accountRestricted } = useStorageApi() const [isCreateBucketModalOpen, setIsCreateBucketModalOpen] = useState(false) const [bucketToRemove, setBucketToRemove] = useState() @@ -252,6 +252,14 @@ const BucketsPage = () => { } } + const handleRename = useCallback((bucket: Bucket, newName: string) => { + return editBucket(bucket.id, { + ...bucket, + name: newName + }).then(() => refreshBuckets()) + .catch(console.error) + }, [editBucket, refreshBuckets]) + return (
@@ -340,6 +348,7 @@ const BucketsPage = () => { key={bucket.id} onRemoveBucket={setBucketToRemove} handleContextMenu={handleContextMenu} + handleRename={handleRename} /> )} diff --git a/packages/storage-ui/src/Contexts/StorageContext.tsx b/packages/storage-ui/src/Contexts/StorageContext.tsx index e0053d0533..9c1a2010ea 100644 --- a/packages/storage-ui/src/Contexts/StorageContext.tsx +++ b/packages/storage-ui/src/Contexts/StorageContext.tsx @@ -7,7 +7,8 @@ import { PinStatus, BucketSummaryResponse, PinResult, - FileSystemType + FileSystemType, + UpdateBucketRequest } from "@chainsafe/files-api-client" import React, { useCallback, useEffect } from "react" import { useState } from "react" @@ -65,6 +66,7 @@ type StorageContext = { onSearch: (searchParams: RefreshPinParams) => void pageNumber: number resetPins: () => void + editBucket: (bucketId: string, updateRequest: UpdateBucketRequest) => Promise } // This represents a File or Folder on the @@ -518,9 +520,14 @@ const StorageProvider = ({ children }: StorageContextProps) => { ).catch(console.error) }, [storageApiClient]) + const editBucket = useCallback((bucketId: string, updateRequest: UpdateBucketRequest) => { + return storageApiClient.updateBucket(bucketId, updateRequest).catch(console.error) + }, [storageApiClient]) + return ( Date: Thu, 4 Aug 2022 22:00:58 +0300 Subject: [PATCH 09/14] disable IPFS MFS filesystem creation --- .../src/Components/Pages/BucketsPage.tsx | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/storage-ui/src/Components/Pages/BucketsPage.tsx b/packages/storage-ui/src/Components/Pages/BucketsPage.tsx index 8d5d9fe8d2..6566863513 100644 --- a/packages/storage-ui/src/Components/Pages/BucketsPage.tsx +++ b/packages/storage-ui/src/Components/Pages/BucketsPage.tsx @@ -199,13 +199,13 @@ const BucketsPage = () => { ) const formik = useFormik({ - initialValues:{ + initialValues: { name: "", fileSystemType: "chainsafe" }, enableReinitialize: true, validationSchema: bucketNameValidationSchema, - onSubmit:(values, helpers) => { + onSubmit: (values, helpers) => { helpers.setSubmitting(true) createBucket(values.name.trim(), values.fileSystemType as FileSystemType) .then(() => { @@ -226,7 +226,7 @@ const BucketsPage = () => { const handleContextMenu = useCallback((e: React.MouseEvent, options?: IMenuItem[]) => { e.preventDefault() - if(options){ + if (options) { setContextMenuOptions(options) } else { setContextMenuOptions(generalContextMenuOptions) @@ -419,8 +419,12 @@ const BucketsPage = () => { id='ipfs' label='IPFS' testId="ipfs" + disabled />
+
+ Looking for IPFS MFS support? Email colin@chainsafe.io to inquire. +
@@ -447,19 +451,19 @@ const BucketsPage = () => {
- + setBucketToRemove(undefined)} accept={handleRemoveBucket} requestMessage={t`You are about to delete the bucket ${bucketToRemove?.name}`} - rejectText = {t`Cancel`} - acceptText = {t`Delete`} + rejectText={t`Cancel`} + acceptText={t`Delete`} acceptButtonProps={{ loading: isRemovingBucket, disabled: isRemovingBucket }} rejectButtonProps={{ disabled: isRemovingBucket }} testId={"bucket-deletion"} /> -
+ ) } From e2e2dafc1c63c0300fa8e2864cc62416e4d37478 Mon Sep 17 00:00:00 2001 From: Juan Manuel Spoleti <104365141+juans-chainsafe@users.noreply.github.com> Date: Thu, 4 Aug 2022 16:14:29 -0300 Subject: [PATCH 10/14] errors validations in pin a cid modal - test (#2235) Co-authored-by: Tanmoy Basak Anjan --- .../storage-ui/cypress/tests/cid-management.cy.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/storage-ui/cypress/tests/cid-management.cy.ts b/packages/storage-ui/cypress/tests/cid-management.cy.ts index 64c54fef0a..10c75eb25f 100644 --- a/packages/storage-ui/cypress/tests/cid-management.cy.ts +++ b/packages/storage-ui/cypress/tests/cid-management.cy.ts @@ -11,10 +11,22 @@ describe("CID management", () => { cy.web3Login({ withNewSession: true }) navigationMenu.cidsNavButton().click() - // pin a cid and see it in the pinned items table + // go to pin a cid modal and type the name cidsPage.pinButton().click() addCidModal.body().should("be.visible") addCidModal.nameInput().type(testCidName) + + // ensure can't pin an empty cid + // TODO: uncomment when #2229 issue is fixed + //addCidModal.pinSubmitButton().safeClick() + //addCidModal.nameInput().should("have.class", "error") + + // ensure can't pin an invalid cid + addCidModal.cidInput().type("invalid cid") + addCidModal.cidInput().should("have.class", "error") + addCidModal.cidInput().clear() + + // pin a valid cid and see it in the pinned items table addCidModal.cidInput().type(testCid) addCidModal.pinSubmitButton().safeClick() cidsPage.cidItemRow().should("have.length", 1) From 36af1448c4fffee3fb688c617901b2afaf830291 Mon Sep 17 00:00:00 2001 From: Michael Yankelev Date: Thu, 4 Aug 2022 22:35:34 +0300 Subject: [PATCH 11/14] fix lint --- packages/storage-ui/src/Components/Pages/BucketsPage.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/storage-ui/src/Components/Pages/BucketsPage.tsx b/packages/storage-ui/src/Components/Pages/BucketsPage.tsx index 6566863513..9940493378 100644 --- a/packages/storage-ui/src/Components/Pages/BucketsPage.tsx +++ b/packages/storage-ui/src/Components/Pages/BucketsPage.tsx @@ -423,7 +423,10 @@ const BucketsPage = () => { />
- Looking for IPFS MFS support? Email colin@chainsafe.io to inquire. + Looking for IPFS MFS support? Email colin@chainsafe.io to inquire. +
From 74d51ee6cdb327d2b3bdb3e7cd8bd1ecfee85ad6 Mon Sep 17 00:00:00 2001 From: Michael Yankelev Date: Fri, 5 Aug 2022 11:23:56 +0300 Subject: [PATCH 12/14] comment out IPFS MFS bucket tests --- .../cypress/tests/bucket-management.cy.ts | 126 +++++++++--------- 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/packages/storage-ui/cypress/tests/bucket-management.cy.ts b/packages/storage-ui/cypress/tests/bucket-management.cy.ts index 68997a4710..25b2fa4dbe 100644 --- a/packages/storage-ui/cypress/tests/bucket-management.cy.ts +++ b/packages/storage-ui/cypress/tests/bucket-management.cy.ts @@ -82,69 +82,69 @@ describe("Bucket management", () => { bucketsPage.bucketItemName().should("not.exist") }) - it("can create, upload file and delete an ipfs bucket", () => { - const ipfsBucketName = `ipfs bucket ${Date.now()}` - - cy.web3Login({ clearPins: true, deleteFpsBuckets: true }) - navigationMenu.bucketsNavButton().click() - - // go to create bucket modal - bucketsPage.createBucketButton().click() - createBucketModal.body().should("be.visible") - - // ensure can't create an empty bucket - createBucketModal.submitButton().click() - createBucketModal.bucketNameInput().should("have.class", "error") - - // ensure can't create a bucket with only spaces - createBucketModal.bucketNameInput().type(" ") - createBucketModal.submitButton().click() - createBucketModal.bucketNameInput().should("have.class", "error") - - // create a bucket and see it in the bucket table - createBucketModal.bucketNameInput().type(ipfsBucketName) - createBucketModal.ipfsRadioInput().click() - createBucketModal.submitButton().click() - bucketsPage.bucketItemRow().should("have.length", 1) - bucketsPage.bucketItemName().should("have.text", ipfsBucketName) - bucketsPage.bucketFileSystemType().should("have.text", "IPFS MFS") - - // ensure can't create a bucket with the same name - bucketsPage.createBucketButton().click() - createBucketModal.bucketNameInput().type(ipfsBucketName) - createBucketModal.submitButton().click() - createBucketModal.bucketNameInput().should("have.class", "error") - createBucketModal.cancelButton().click() - - // open bucket and ensure header matches the expected value - bucketsPage.bucketItemName().dblclick() - bucketContentsPage.bucketHeaderLabel() - .should("be.visible") - .should("contain.text", ipfsBucketName) - - // upload a file to the bucket - bucketContentsPage.uploadButton().click() - fileUploadModal.body().attachFile("../fixtures/uploadedFiles/logo.png") - fileUploadModal.fileList().should("have.length", 1) - fileUploadModal.uploadButton().safeClick() - fileUploadModal.body().should("not.exist") - bucketContentsPage.awaitBucketRefresh() - uploadCompleteToast.body().should("be.visible") - uploadCompleteToast.closeButton().click() - bucketContentsPage.fileItemRow().should("have.length", 1) - - // delete ipfs bucket - navigationMenu.bucketsNavButton().click() - bucketsPage.bucketRowKebabButton() - .should("be.visible") - .click() - bucketsPage.deleteBucketMenuOption().click() - deleteBucketModal.body().should("be.visible") - deleteBucketModal.confirmButton().safeClick() - deleteBucketModal.body().should("not.exist") - bucketsPage.bucketItemRow().should("not.exist") - bucketsPage.bucketItemName().should("not.exist") - }) + // it("can create, upload file and delete an ipfs bucket", () => { + // const ipfsBucketName = `ipfs bucket ${Date.now()}` + + // cy.web3Login({ clearPins: true, deleteFpsBuckets: true }) + // navigationMenu.bucketsNavButton().click() + + // // go to create bucket modal + // bucketsPage.createBucketButton().click() + // createBucketModal.body().should("be.visible") + + // // ensure can't create an empty bucket + // createBucketModal.submitButton().click() + // createBucketModal.bucketNameInput().should("have.class", "error") + + // // ensure can't create a bucket with only spaces + // createBucketModal.bucketNameInput().type(" ") + // createBucketModal.submitButton().click() + // createBucketModal.bucketNameInput().should("have.class", "error") + + // // create a bucket and see it in the bucket table + // createBucketModal.bucketNameInput().type(ipfsBucketName) + // createBucketModal.ipfsRadioInput().click() + // createBucketModal.submitButton().click() + // bucketsPage.bucketItemRow().should("have.length", 1) + // bucketsPage.bucketItemName().should("have.text", ipfsBucketName) + // bucketsPage.bucketFileSystemType().should("have.text", "IPFS MFS") + + // // ensure can't create a bucket with the same name + // bucketsPage.createBucketButton().click() + // createBucketModal.bucketNameInput().type(ipfsBucketName) + // createBucketModal.submitButton().click() + // createBucketModal.bucketNameInput().should("have.class", "error") + // createBucketModal.cancelButton().click() + + // // open bucket and ensure header matches the expected value + // bucketsPage.bucketItemName().dblclick() + // bucketContentsPage.bucketHeaderLabel() + // .should("be.visible") + // .should("contain.text", ipfsBucketName) + + // // upload a file to the bucket + // bucketContentsPage.uploadButton().click() + // fileUploadModal.body().attachFile("../fixtures/uploadedFiles/logo.png") + // fileUploadModal.fileList().should("have.length", 1) + // fileUploadModal.uploadButton().safeClick() + // fileUploadModal.body().should("not.exist") + // bucketContentsPage.awaitBucketRefresh() + // uploadCompleteToast.body().should("be.visible") + // uploadCompleteToast.closeButton().click() + // bucketContentsPage.fileItemRow().should("have.length", 1) + + // // delete ipfs bucket + // navigationMenu.bucketsNavButton().click() + // bucketsPage.bucketRowKebabButton() + // .should("be.visible") + // .click() + // bucketsPage.deleteBucketMenuOption().click() + // deleteBucketModal.body().should("be.visible") + // deleteBucketModal.confirmButton().safeClick() + // deleteBucketModal.body().should("not.exist") + // bucketsPage.bucketItemRow().should("not.exist") + // bucketsPage.bucketItemName().should("not.exist") + // }) it("can sort by name or file system in buckets table", () => { const chainSafeBucketName = `cs bucket ${Date.now()}` From 3113a0bac83f78d5b24325e2ffcebfb466f04b60 Mon Sep 17 00:00:00 2001 From: Juan Manuel Spoleti Date: Mon, 8 Aug 2022 15:47:52 -0300 Subject: [PATCH 13/14] comment all IPFS tests --- .../cypress/tests/bucket-management.cy.ts | 270 +++++++++--------- 1 file changed, 135 insertions(+), 135 deletions(-) diff --git a/packages/storage-ui/cypress/tests/bucket-management.cy.ts b/packages/storage-ui/cypress/tests/bucket-management.cy.ts index 25b2fa4dbe..ec049c7d5b 100644 --- a/packages/storage-ui/cypress/tests/bucket-management.cy.ts +++ b/packages/storage-ui/cypress/tests/bucket-management.cy.ts @@ -180,41 +180,41 @@ describe("Bucket management", () => { bucketsPage.bucketItemName().eq(1).should("have.text", ipfsBucketName) }) - it("can rename a folder inside the ipfs bucket", () => { - const ipfsBucketName = `ipfs bucket ${Date.now()}` - const folderName = `folder ${Date.now()}` - const newFolderName = `new folder name ${Date.now()}` + // it("can rename a folder inside the ipfs bucket", () => { + // const ipfsBucketName = `ipfs bucket ${Date.now()}` + // const folderName = `folder ${Date.now()}` + // const newFolderName = `new folder name ${Date.now()}` - cy.web3Login({ deleteFpsBuckets: true, createFpsBuckets: [{ name: ipfsBucketName, type: "ipfs" }] }) - navigationMenu.bucketsNavButton().click() + // cy.web3Login({ deleteFpsBuckets: true, createFpsBuckets: [{ name: ipfsBucketName, type: "ipfs" }] }) + // navigationMenu.bucketsNavButton().click() - // go inside the bucket - bucketsPage.bucketItemRow().should("have.length", 1) - bucketsPage.bucketItemName().dblclick() + // // go inside the bucket + // bucketsPage.bucketItemRow().should("have.length", 1) + // bucketsPage.bucketItemName().dblclick() - // create a folder inside the bucket - bucketContentsPage.createNewFolder(folderName) + // // create a folder inside the bucket + // bucketContentsPage.createNewFolder(folderName) - // ensure an error is displayed if the edited name of the folder is blank - bucketContentsPage.renameFileOrFolder("{selectall}{del}") - bucketContentsPage.fileRenameErrorLabel().should("be.visible") + // // ensure an error is displayed if the edited name of the folder is blank + // bucketContentsPage.renameFileOrFolder("{selectall}{del}") + // bucketContentsPage.fileRenameErrorLabel().should("be.visible") - // ensure the original name of the folder persists if the rename submission is blank - bucketContentsPage.renameFileOrFolder("{selectall}{del}{esc}") - bucketContentsPage.fileRenameInput().should("not.exist") - bucketContentsPage.fileItemName().contains(folderName) + // // ensure the original name of the folder persists if the rename submission is blank + // bucketContentsPage.renameFileOrFolder("{selectall}{del}{esc}") + // bucketContentsPage.fileRenameInput().should("not.exist") + // bucketContentsPage.fileItemName().contains(folderName) - // rename a folder - bucketContentsPage.renameFileOrFolder(`{selectall}${newFolderName}{enter}`) - bucketContentsPage.fileItemName().contains(newFolderName) + // // rename a folder + // bucketContentsPage.renameFileOrFolder(`{selectall}${newFolderName}{enter}`) + // bucketContentsPage.fileItemName().contains(newFolderName) - // ensure that the name of the folder is reset when renaming is canceled - bucketContentsPage.renameFileOrFolder("{selectall}{del}}abc{esc}") - bucketContentsPage.fileRenameInput().should("not.exist") - bucketContentsPage.fileItemKebabButton().click() - bucketContentsPage.renameMenuOption().click() - bucketContentsPage.fileRenameInput().should("have.value", newFolderName) - }) + // // ensure that the name of the folder is reset when renaming is canceled + // bucketContentsPage.renameFileOrFolder("{selectall}{del}}abc{esc}") + // bucketContentsPage.fileRenameInput().should("not.exist") + // bucketContentsPage.fileItemKebabButton().click() + // bucketContentsPage.renameMenuOption().click() + // bucketContentsPage.fileRenameInput().should("have.value", newFolderName) + // }) it("can rename a folder inside the chainsafe bucket", () => { const chainsafeBucketName = `chainsafe bucket ${Date.now()}` @@ -251,40 +251,40 @@ describe("Bucket management", () => { bucketContentsPage.fileRenameInput().should("have.value", newFolderName) }) - it("can rename a file inside the ipfs bucket", () => { - const ipfsBucketName = `ipfs bucket ${Date.now()}` - const newFileName = `new file name ${Date.now()}` + // it("can rename a file inside the ipfs bucket", () => { + // const ipfsBucketName = `ipfs bucket ${Date.now()}` + // const newFileName = `new file name ${Date.now()}` - cy.web3Login({ deleteFpsBuckets: true, createFpsBuckets: [{ name: ipfsBucketName, type: "ipfs" }] }) - navigationMenu.bucketsNavButton().click() + // cy.web3Login({ deleteFpsBuckets: true, createFpsBuckets: [{ name: ipfsBucketName, type: "ipfs" }] }) + // navigationMenu.bucketsNavButton().click() - // go inside the bucket - bucketsPage.bucketItemRow().should("have.length", 1) - bucketsPage.bucketItemName().dblclick() + // // go inside the bucket + // bucketsPage.bucketItemRow().should("have.length", 1) + // bucketsPage.bucketItemName().dblclick() - // upload a file - bucketContentsPage.uploadFileToBucket("../fixtures/uploadedFiles/logo.png") + // // upload a file + // bucketContentsPage.uploadFileToBucket("../fixtures/uploadedFiles/logo.png") - // ensure an error is displayed if the edited name of the file is blank - bucketContentsPage.renameFileOrFolder("{selectall}{del}") - bucketContentsPage.fileRenameErrorLabel().should("be.visible") + // // ensure an error is displayed if the edited name of the file is blank + // bucketContentsPage.renameFileOrFolder("{selectall}{del}") + // bucketContentsPage.fileRenameErrorLabel().should("be.visible") - // ensure the original name of the file persists if the rename submission is blank - bucketContentsPage.renameFileOrFolder("{selectall}{del}{esc}") - bucketContentsPage.fileRenameInput().should("not.exist") - bucketContentsPage.fileItemName().contains("logo.png") + // // ensure the original name of the file persists if the rename submission is blank + // bucketContentsPage.renameFileOrFolder("{selectall}{del}{esc}") + // bucketContentsPage.fileRenameInput().should("not.exist") + // bucketContentsPage.fileItemName().contains("logo.png") - // rename the file - bucketContentsPage.renameFileOrFolder(`{selectall}${newFileName}{enter}`) - bucketContentsPage.fileItemName().contains(newFileName) + // // rename the file + // bucketContentsPage.renameFileOrFolder(`{selectall}${newFileName}{enter}`) + // bucketContentsPage.fileItemName().contains(newFileName) - // ensure that the name of the file is reset when renaming is canceled - bucketContentsPage.renameFileOrFolder("{selectall}{del}abc{esc}") - bucketContentsPage.fileRenameInput().should("not.exist") - bucketContentsPage.fileItemKebabButton().click() - bucketContentsPage.renameMenuOption().click() - bucketContentsPage.fileRenameInput().should("have.value", newFileName) - }) + // // ensure that the name of the file is reset when renaming is canceled + // bucketContentsPage.renameFileOrFolder("{selectall}{del}abc{esc}") + // bucketContentsPage.fileRenameInput().should("not.exist") + // bucketContentsPage.fileItemKebabButton().click() + // bucketContentsPage.renameMenuOption().click() + // bucketContentsPage.fileRenameInput().should("have.value", newFileName) + // }) it("can rename a file inside the chainsafe bucket", () => { const chainsafeBucketName = `chainsafe bucket ${Date.now()}` @@ -361,61 +361,61 @@ describe("Bucket management", () => { }) }) - it("can download a file from ipfs bucket", () => { - const ipfsBucketName = `ipfs bucket ${Date.now()}` - const fileName = "text-file.txt" - const downloadsFolder = Cypress.config("downloadsFolder") - const fileFixturePath = `uploadedFiles/${fileName}` - - cy.web3Login({ deleteFpsBuckets: true, createFpsBuckets: [{ name: ipfsBucketName, type: "ipfs" }] }) - - // upload a file and store file content - navigationMenu.bucketsNavButton().click() - bucketsPage.bucketItemName().dblclick() - - // upload a file to the bucket - bucketContentsPage.uploadFileToBucket(`../fixtures/uploadedFiles/${fileName}`) - cy.fixture(fileFixturePath).as("fileContent") - - // download file from kebab menu - bucketContentsPage.fileItemKebabButton().click() + // it("can download a file from ipfs bucket", () => { + // const ipfsBucketName = `ipfs bucket ${Date.now()}` + // const fileName = "text-file.txt" + // const downloadsFolder = Cypress.config("downloadsFolder") + // const fileFixturePath = `uploadedFiles/${fileName}` - // intercept POST to ensure the request was successful - cy.intercept("POST", "**/bucket/*/download") - .as("downloadRequest") - .then(() => { - bucketContentsPage.downloadMenuOption().click() + // cy.web3Login({ deleteFpsBuckets: true, createFpsBuckets: [{ name: ipfsBucketName, type: "ipfs" }] }) - cy.wait("@downloadRequest").should((download) => { - expect(download.response).to.have.property("statusCode", 200) - }) - }) + // // upload a file and store file content + // navigationMenu.bucketsNavButton().click() + // bucketsPage.bucketItemName().dblclick() - // ensure the file was downloaded - downloadCompleteToast.body().should("be.visible") - downloadCompleteToast.closeButton().click() - cy.get("@fileContent").then((fileContent) => { - cy.readFile(`${downloadsFolder}/${fileName}`) - .should("exist") - .should("eq", fileContent) - }) - }) + // // upload a file to the bucket + // bucketContentsPage.uploadFileToBucket(`../fixtures/uploadedFiles/${fileName}`) + // cy.fixture(fileFixturePath).as("fileContent") + + // // download file from kebab menu + // bucketContentsPage.fileItemKebabButton().click() + + // // intercept POST to ensure the request was successful + // cy.intercept("POST", "**/bucket/*/download") + // .as("downloadRequest") + // .then(() => { + // bucketContentsPage.downloadMenuOption().click() + + // cy.wait("@downloadRequest").should((download) => { + // expect(download.response).to.have.property("statusCode", 200) + // }) + // }) + + // // ensure the file was downloaded + // downloadCompleteToast.body().should("be.visible") + // downloadCompleteToast.closeButton().click() + // cy.get("@fileContent").then((fileContent) => { + // cy.readFile(`${downloadsFolder}/${fileName}`) + // .should("exist") + // .should("eq", fileContent) + // }) + // }) - it("can copy to clipboard the cid inside the ipfs bucket", () => { - const ipfsBucketName = `ipfs bucket ${Date.now()}` + // it("can copy to clipboard the cid inside the ipfs bucket", () => { + // const ipfsBucketName = `ipfs bucket ${Date.now()}` - cy.web3Login({ deleteFpsBuckets: true, createFpsBuckets: [{ name: ipfsBucketName, type: "ipfs" }] }) - // upload a file to the bucket - navigationMenu.bucketsNavButton().click() - bucketsPage.bucketItemName().dblclick() - bucketContentsPage.uploadFileToBucket("../fixtures/uploadedFiles/text-file.txt") + // cy.web3Login({ deleteFpsBuckets: true, createFpsBuckets: [{ name: ipfsBucketName, type: "ipfs" }] }) + // // upload a file to the bucket + // navigationMenu.bucketsNavButton().click() + // bucketsPage.bucketItemName().dblclick() + // bucketContentsPage.uploadFileToBucket("../fixtures/uploadedFiles/text-file.txt") - // ensure the correct cid is being copied to the clipboard - bucketContentsPage.fileItemCid().click() - cy.window().its("navigator.clipboard").invoke("readText").then((text) => { - bucketContentsPage.fileItemCid().should("have.text", text) - }) - }) + // // ensure the correct cid is being copied to the clipboard + // bucketContentsPage.fileItemCid().click() + // cy.window().its("navigator.clipboard").invoke("readText").then((text) => { + // bucketContentsPage.fileItemCid().should("have.text", text) + // }) + // }) it("can copy to clipboard the cid inside the chainsafe bucket", () => { const chainsafeBucketName = `chainsafe bucket ${Date.now()}` @@ -474,45 +474,45 @@ describe("Bucket management", () => { bucketContentsPage.fileItemRow().should("have.length", 0) }) - it("can delete a file inside the ipfs bucket", () => { - const ipfsBucketName = `ipfs bucket ${Date.now()}` + // it("can delete a file inside the ipfs bucket", () => { + // const ipfsBucketName = `ipfs bucket ${Date.now()}` - cy.web3Login({ deleteFpsBuckets: true, createFpsBuckets: [{ name: ipfsBucketName, type: "ipfs" }] }) - navigationMenu.bucketsNavButton().click() + // cy.web3Login({ deleteFpsBuckets: true, createFpsBuckets: [{ name: ipfsBucketName, type: "ipfs" }] }) + // navigationMenu.bucketsNavButton().click() - // go inside the bucket - bucketsPage.bucketItemRow().should("have.length", 1) - bucketsPage.bucketItemName().dblclick() + // // go inside the bucket + // bucketsPage.bucketItemRow().should("have.length", 1) + // bucketsPage.bucketItemName().dblclick() - // upload a file - bucketContentsPage.uploadFileToBucket("../fixtures/uploadedFiles/logo.png") + // // upload a file + // bucketContentsPage.uploadFileToBucket("../fixtures/uploadedFiles/logo.png") - // delete the file - bucketContentsPage.deleteFileOrFolder() - deletionCompleteToast.body().should("be.visible") - deletionCompleteToast.closeButton().click() - bucketContentsPage.fileItemRow().should("have.length", 0) - }) + // // delete the file + // bucketContentsPage.deleteFileOrFolder() + // deletionCompleteToast.body().should("be.visible") + // deletionCompleteToast.closeButton().click() + // bucketContentsPage.fileItemRow().should("have.length", 0) + // }) - it("can delete a folder inside the ipfs bucket", () => { - const ipfsBucketName = `ipfs bucket ${Date.now()}` - const folderName = `folder ${Date.now()}` + // it("can delete a folder inside the ipfs bucket", () => { + // const ipfsBucketName = `ipfs bucket ${Date.now()}` + // const folderName = `folder ${Date.now()}` - cy.web3Login({ deleteFpsBuckets: true, createFpsBuckets: [{ name: ipfsBucketName, type: "ipfs" }] }) - navigationMenu.bucketsNavButton().click() + // cy.web3Login({ deleteFpsBuckets: true, createFpsBuckets: [{ name: ipfsBucketName, type: "ipfs" }] }) + // navigationMenu.bucketsNavButton().click() - // go inside the bucket - bucketsPage.bucketItemRow().should("have.length", 1) - bucketsPage.bucketItemName().dblclick() + // // go inside the bucket + // bucketsPage.bucketItemRow().should("have.length", 1) + // bucketsPage.bucketItemName().dblclick() - // create a folder inside the bucket - bucketContentsPage.createNewFolder(folderName) + // // create a folder inside the bucket + // bucketContentsPage.createNewFolder(folderName) - // delete the folder - bucketContentsPage.deleteFileOrFolder() - deletionCompleteToast.body().should("be.visible") - deletionCompleteToast.closeButton().click() - bucketContentsPage.fileItemRow().should("have.length", 0) - }) + // // delete the folder + // bucketContentsPage.deleteFileOrFolder() + // deletionCompleteToast.body().should("be.visible") + // deletionCompleteToast.closeButton().click() + // bucketContentsPage.fileItemRow().should("have.length", 0) + // }) }) }) From 3a134893d5a072f115e99621bedd6758bc2efa26 Mon Sep 17 00:00:00 2001 From: Michael Yankelev <12774278+FSM1@users.noreply.github.com> Date: Fri, 12 Aug 2022 12:28:39 +0300 Subject: [PATCH 14/14] Hide NFTs page by default (#2244) * hdie nfts page by default * Update packages/storage-ui/src/Components/Layouts/AppNav.tsx Co-authored-by: Thibaut Sardan <33178835+Tbaut@users.noreply.github.com> Co-authored-by: Thibaut Sardan <33178835+Tbaut@users.noreply.github.com> --- packages/storage-ui/src/Components/Layouts/AppNav.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/storage-ui/src/Components/Layouts/AppNav.tsx b/packages/storage-ui/src/Components/Layouts/AppNav.tsx index 4cc4d30471..d6e3fb80d4 100644 --- a/packages/storage-ui/src/Components/Layouts/AppNav.tsx +++ b/packages/storage-ui/src/Components/Layouts/AppNav.tsx @@ -17,8 +17,8 @@ import { PowerDownIcon, useLocation, KeySvg, - CreditCardOutlinedSvg, - FileWithImageSvg + CreditCardOutlinedSvg + // FileWithImageSvg } from "@chainsafe/common-components" import { ROUTE_LINKS } from "../StorageRoutes" import { Trans } from "@lingui/macro" @@ -344,7 +344,7 @@ const AppNav: React.FC = ({ navOpen, setNavOpen }: IAppNav) => { CIDs - = ({ navOpen, setNavOpen }: IAppNav) => { > NFTs - + */}