diff --git a/packages/common-components/src/Router/ConditionalRoute.tsx b/packages/common-components/src/Router/ConditionalRoute.tsx index 004c2131a4..5237a38d92 100644 --- a/packages/common-components/src/Router/ConditionalRoute.tsx +++ b/packages/common-components/src/Router/ConditionalRoute.tsx @@ -21,7 +21,7 @@ const ConditionalRoute: React.FC = ({ exact, ...rest }) => { - const { state, pathname } = useLocation<{from?: string} | undefined>() + const { state, pathname, hash } = useLocation<{from?: string} | undefined>() const from = (state as any)?.from return = ({ ? // this may be converted into loading diff --git a/packages/files-ui/cypress/fixtures/filesTestData.ts b/packages/files-ui/cypress/fixtures/filesTestData.ts index 4b21f7812e..df4bec6e18 100644 --- a/packages/files-ui/cypress/fixtures/filesTestData.ts +++ b/packages/files-ui/cypress/fixtures/filesTestData.ts @@ -1,2 +1,3 @@ export const folderName = "Group" -export const folderPath = `/${folderName}` \ No newline at end of file +export const folderPath = `/${folderName}` +export const profileCreatedDate = "2021-05-20T21:26:36.598924Z" diff --git a/packages/files-ui/cypress/support/page-objects/homePage.ts b/packages/files-ui/cypress/support/page-objects/homePage.ts index cbfd7d260c..337e56f6a4 100644 --- a/packages/files-ui/cypress/support/page-objects/homePage.ts +++ b/packages/files-ui/cypress/support/page-objects/homePage.ts @@ -8,6 +8,8 @@ export const homePage = { ...fileBrowser, // home page specific file browser elements + closeBannerButton: () => cy.get("[data-cy=button-close-banner"), + surveyBanner: () => cy.get("[data-cy=banner-survey]"), newFolderButton: () => cy.get("[data-cy=button-new-folder]"), uploadButton: () => cy.get("[data-cy=button-upload-file]"), moveSelectedButton: () => cy.get("[data-testId=button-move-selected-file]"), diff --git a/packages/files-ui/cypress/tests/file-preview-spec.ts b/packages/files-ui/cypress/tests/file-preview-spec.ts index 8a7507789a..ffe19a8ba7 100644 --- a/packages/files-ui/cypress/tests/file-preview-spec.ts +++ b/packages/files-ui/cypress/tests/file-preview-spec.ts @@ -73,13 +73,22 @@ describe("File Preview", () => { homePage.fileItemName().dblclick() previewModal.unsupportedFileLabel().should("exist") previewModal.downloadUnsupportedFileButton().should("be.visible") + // ensure that the file download does not start until the download button is clicked + cy.get("@downloadRequest").then(($request) => { + // retrieving the alias (spy) should yield null because posts should not have been made yet + expect($request).to.be.null + }) + + // begin the file download + previewModal.downloadUnsupportedFileButton().click() + // ensure the download request contains the correct file - cy.get("@downloadRequest").its("request.body").should("contain", { + cy.wait("@downloadRequest").its("request.body").should("contain", { path: "/file.zip" }) }) - // return to the home and ensure preview menu option is not shown for unsupported file + // return to home, ensure the preview menu option is not shown for an unsupported file previewModal.closeButton().click() homePage.fileItemKebabButton().click() homePage.previewMenuOption().should("not.exist") diff --git a/packages/files-ui/cypress/tests/survey-banner-spec.ts b/packages/files-ui/cypress/tests/survey-banner-spec.ts new file mode 100644 index 0000000000..ba13369114 --- /dev/null +++ b/packages/files-ui/cypress/tests/survey-banner-spec.ts @@ -0,0 +1,72 @@ +import { profileCreatedDate } from "../fixtures/filesTestData" +import { homePage } from "../support/page-objects/homePage" + +describe("Survey Banner", () => { + + context("desktop", () => { + + it("User can view and dismiss the survey banner", () => { + // intercept and stub the account creation date to be > 7 days + cy.intercept("GET", "https://stage.imploy.site/api/v1/user/profile", (req) => { + req.on("response", (res) => { + res.body.created_at = profileCreatedDate + }) + }) + + // intercept and stub the response to ensure the banner is displayed + cy.intercept("GET", "https://stage.imploy.site/api/v1/user/store", { + body: [{ "csf.dismissedSurveyBannerV3": "false" }] + }) + + cy.web3Login() + homePage.surveyBanner().should("be.visible") + + // set up a spy for the POST response + cy.intercept("POST", "https://stage.imploy.site/api/v1/user/store").as("storePost").then(() => { + + // dismiss the survey banner + homePage.closeBannerButton().click() + homePage.surveyBanner().should("not.exist") + + // intercept POST to ensure the key was updated after the banner is dismissed + cy.wait("@storePost").its("request.body").should("contain", { + "csf.dismissedSurveyBannerV3": "true" + }) + }) + }) + + it("User should not see the survey banner if previously dismissed", () => { + cy.intercept("GET", "https://stage.imploy.site/api/v1/user/store", { + body: [{ "csf.dismissedSurveyBannerV3": "true" }] + }) + + cy.web3Login() + homePage.surveyBanner().should("not.exist") + }) + + it("User should see banner if account age is greater than 7 days and api response is empty", () => { + cy.intercept("GET", "https://stage.imploy.site/api/v1/user/store", { + body: [{}] + }) + + cy.web3Login() + homePage.surveyBanner().should("be.visible") + }) + + it("User should not see banner if account age is less than 7 days and api response is empty", () => { + // intercept and stub the account creation date to make it less than 7 days + cy.intercept("GET", "https://stage.imploy.site/api/v1/user/profile", (req) => { + req.on("response", (res) => { + res.body.created_at = res.body.updated_at + }) + }) + + cy.intercept("GET", "https://stage.imploy.site/api/v1/user/store", { + body: [{}] + }) + + cy.web3Login() + homePage.surveyBanner().should("not.exist") + }) + }) +}) diff --git a/packages/files-ui/package.json b/packages/files-ui/package.json index 42b49f7131..47de55c414 100644 --- a/packages/files-ui/package.json +++ b/packages/files-ui/package.json @@ -31,6 +31,9 @@ "ethers": "^5.4.3", "fflate": "^0.7.1", "formik": "^2.2.5", + "jsrsasign": "^10.4.1", + "key-encoder": "^2.0.3", + "heic-convert": "^1.2.4", "mime-matcher": "^1.0.5", "posthog-js": "^1.13.10", "react": "^16.14.0", @@ -61,6 +64,7 @@ "@testing-library/react": "^11.2.2", "@testing-library/user-event": "^12.5.0", "@types/jest": "^26.0.16", + "@types/jsrsasign": "^8.0.13", "@types/node": "^14.14.10", "@types/react": "^17.0.0", "@types/react-beforeunload": "^2.1.0", @@ -77,7 +81,7 @@ "scripts": { "postinstall": "yarn compile", "start": "yarn compile && craco --max_old_space_size=4096 start", - "build": "craco --max_old_space_size=4096 build", + "build": "craco --max_old_space_size=4096 --openssl-legacy-provider build ", "sentry": "(export REACT_APP_SENTRY_RELEASE=$(sentry-cli releases propose-version); node scripts/sentry.js)", "release": "(export REACT_APP_SENTRY_RELEASE=$(sentry-cli releases propose-version); yarn compile && yarn build && node scripts/sentry.js)", "test": "cypress open", diff --git a/packages/files-ui/src/Components/FilesRoutes.tsx b/packages/files-ui/src/Components/FilesRoutes.tsx index 74b404c6fa..70ae37185a 100644 --- a/packages/files-ui/src/Components/FilesRoutes.tsx +++ b/packages/files-ui/src/Components/FilesRoutes.tsx @@ -10,8 +10,12 @@ import PurchasePlanPage from "./Pages/PurchasePlanPage" import { useThresholdKey } from "../Contexts/ThresholdKeyContext" import ShareFilesPage from "./Pages/SharedFilesPage" import SharedFoldersOverview from "./Modules/FileBrowsers/SharedFoldersOverview" +import { NonceResponsePermission } from "@chainsafe/files-api-client" +import LinkSharingLanding from "./Pages/LinkSharingLanding" export const SETTINGS_BASE = "/settings" +export const LINK_SHARING_BASE = "/link-sharing" + export const ROUTE_LINKS = { Landing: "/", PrivacyPolicy: "https://files.chainsafe.io/privacy-policy", @@ -27,6 +31,8 @@ export const ROUTE_LINKS = { UserSurvey: "https://calendly.com/colinschwarz/chainsafe-files-chat", SharedFolders: "/shared-overview", SharedFolderBrowserRoot: "/shared", + SharingLink: (permission: NonceResponsePermission, jwt: string, bucketEncryptionKey: string) => + `${LINK_SHARING_BASE}/${permissionPath(permission)}/${encodeURIComponent(jwt)}#${encodeURIComponent(bucketEncryptionKey)}`, SharedFolderExplorer: (bucketId: string, rawCurrentPath: string) => { // bucketId should not have a / at the end // rawCurrentPath can be empty, or / @@ -37,6 +43,7 @@ export const ROUTE_LINKS = { TeamSignup: "https://shrl.ink/cgQy" } +export const permissionPath = (permission: NonceResponsePermission) => permission === "read" ? "read" : "edit" export const SETTINGS_PATHS = ["profile", "plan", "security"] as const export type SettingsPath = typeof SETTINGS_PATHS[number] @@ -48,6 +55,12 @@ const FilesRoutes = () => { [isLoggedIn, isNewDevice, publicKey, secured, shouldInitializeAccount]) return ( + { redirectPath={ROUTE_LINKS.Landing} /> { @@ -102,6 +104,9 @@ const useStyles = makeStyles( errorText: { marginLeft: constants.generalUnit * 1.5, color: palette.error.main + }, + sharingLink: { + padding: constants.generalUnit * 1.25 } }) } @@ -275,6 +280,17 @@ const CreateOrEditSharedFolderModal = ({ mode, isModalOpen, onClose, bucketToEdi noOptionsMessage={t`No user found for this query.`} /> + {mode === "edit" && !!bucketToEdit && ( +
+ + Sharing link + + +
+ )} { + return createStyles({ + root: { + }, + options: { + backgroundColor: constants.header.optionsBackground, + color: constants.header.optionsTextColor, + border: `1px solid ${constants.header.optionsBorder}`, + minWidth: 145 + }, + menuItem: { + width: "100%", + display: "flex", + flexDirection: "row", + alignItems: "center", + color: constants.header.menuItemTextColor, + "& svg": { + width: constants.generalUnit * 2, + height: constants.generalUnit * 2, + marginRight: constants.generalUnit, + fill: palette.additional["gray"][7], + stroke: palette.additional["gray"][7] + } + }, + icon: { + "& svg": { + fill: constants.header.iconColor + } + }, + menuIcon: { + display: "flex", + justifyContent: "center", + alignItems: "center", + width: 20, + marginRight: constants.generalUnit * 1.5, + fill: constants.fileSystemItemRow.menuIcon + }, + permissionDropdown: { + padding: `0px ${constants.generalUnit}px`, + backgroundColor: palette.additional["gray"][5], + marginLeft: constants.generalUnit + }, + createLink: { + display: "flex", + alignItems: "center", + margin: `${constants.generalUnit * 2.5}px 0` + }, + createLinkButton: { + marginRight: constants.generalUnit + }, + dropdownTitle: { + padding: `${constants.generalUnit * 0.75}px ${constants.generalUnit}px` + } + }) + } +) + +interface Props { + bucketId: string + bucketEncryptionKey: string +} + +const readRights = t`read rights` +const editRights = t`edit rights` +export const translatedPermission = (permission: NonceResponsePermission) => permission === "read" ? readRights : editRights + +const LinkList = ({ bucketId, bucketEncryptionKey }: Props) => { + const classes = useStyles() + const { filesApiClient } = useFilesApi() + const [nonces, setNonces] = useState([]) + const [isLoading, setIsLoading] = useState(false) + const [newLinkPermission, setNewLinkPermission] = useState("read") + + const refreshNonces = useCallback(() => { + setIsLoading(true) + filesApiClient.getAllNonces() + .then((res) => { + const noncesForCurrentBucket = res.filter(n => n.bucket_id === bucketId) + setNonces(noncesForCurrentBucket) + }) + .catch(console.error) + .finally(() => setIsLoading(false)) + }, [bucketId, filesApiClient]) + + useEffect(() => { + refreshNonces() + }, [filesApiClient, refreshNonces]) + + const onCreateNonce = useCallback(() => { + + setIsLoading(true) + + return filesApiClient + .createNonce({ bucket_id: bucketId, permission: newLinkPermission }) + .catch(console.error) + .finally(() => { + setIsLoading(false) + refreshNonces() + }) + }, [bucketId, filesApiClient, newLinkPermission, refreshNonces]) + + return ( +
+
+ + with + setNewLinkPermission("read"), + contents: ( +
+ {readRights} +
+ ) + }, + { + onClick: () => setNewLinkPermission("write"), + contents: ( +
+ {editRights} +
+ ) + } + ]} + /> +
+ { + isLoading && + } + { + !isLoading && nonces.length > 0 && nonces.map((nonce) => + + ) + } +
+ ) +} + +export default LinkList diff --git a/packages/files-ui/src/Components/Modules/FileBrowsers/LinkSharing/SharingLink.tsx b/packages/files-ui/src/Components/Modules/FileBrowsers/LinkSharing/SharingLink.tsx new file mode 100644 index 0000000000..882f8c64fd --- /dev/null +++ b/packages/files-ui/src/Components/Modules/FileBrowsers/LinkSharing/SharingLink.tsx @@ -0,0 +1,145 @@ + +import { Button, DeleteSvg, Typography } from "@chainsafe/common-components" +import { createStyles, debounce, makeStyles } from "@chainsafe/common-theme" +import { NonceResponse } from "@chainsafe/files-api-client" +import { Trans } from "@lingui/macro" +import React, { useCallback, useEffect, useState } from "react" +import { useFilesApi } from "../../../../Contexts/FilesApiContext" +import { useThresholdKey } from "../../../../Contexts/ThresholdKeyContext" +import { CSFTheme } from "../../../../Themes/types" +import { ROUTE_LINKS } from "../../../FilesRoutes" +import { translatedPermission } from "./LinkList" + +const useStyles = makeStyles( + ({ constants }: CSFTheme) => { + return createStyles({ + root: { + display: "flex", + marginBottom: constants.generalUnit * 0.5 + }, + linkWrapper: { + whiteSpace: "nowrap", + marginRight: constants.generalUnit * 2, + display: "flex", + alignItems: "center", + overflow: "hidden" + }, + permissionWrapper: { + display: "flex", + alignItems: "center", + marginRight: constants.generalUnit, + flex: 1, + whiteSpace: "nowrap" + }, + copyButton: { + flex: 1, + whiteSpace: "nowrap", + marginRight: constants.generalUnit + }, + link: { + textOverflow: "ellipsis", + overflow: "hidden" + }, + menuIcon: { + display: "flex", + justifyContent: "center", + alignItems: "center", + width: 20, + marginRight: constants.generalUnit * 1.5, + fill: constants.fileSystemItemRow.menuIcon + } + }) + } +) + +interface Props { + nonce: NonceResponse + bucketEncryptionKey: string + refreshNonces: () => void +} + +const SharingLink = ({ nonce, bucketEncryptionKey, refreshNonces }: Props) => { + const classes = useStyles() + const { filesApiClient } = useFilesApi() + const [link, setLink] = useState("") + const [jwt, setJwt] = useState("") + const { createJWT } = useThresholdKey() + const [copied, setCopied] = useState(false) + const [isLoading, setIsLoading] = useState(true) + + useEffect(() => { + if(!nonce?.bucket_id || !nonce?.id) { + return + } + + const newJwt = createJWT(nonce.bucket_id, nonce.id, nonce.permission) + newJwt && setJwt(newJwt) + setIsLoading(false) + }, [createJWT, nonce]) + + useEffect(() => { + if(!jwt) { + return + } + + setLink(`${window.location.origin}${ROUTE_LINKS.SharingLink(nonce.permission, jwt, bucketEncryptionKey)}`) + }, [jwt, bucketEncryptionKey, nonce]) + + const debouncedSwitchCopied = debounce(() => setCopied(false), 3000) + + const onCopyInfo = useCallback(() => { + navigator.clipboard.writeText(link) + .then(() => { + setCopied(true) + debouncedSwitchCopied() + }) + .catch(console.error) + }, [debouncedSwitchCopied, link]) + + const onDeleteNonce = useCallback(() => { + setIsLoading(true) + filesApiClient.revokeNonce(nonce.id) + .catch(console.error) + .finally(() => { + refreshNonces() + setIsLoading(false) + }) + }, [filesApiClient, nonce, refreshNonces]) + + return ( +
+
+ + {link} + +
+
+ + {translatedPermission(nonce.permission)} + +
+ + +
+ ) +} + +export default SharingLink diff --git a/packages/files-ui/src/Components/Modules/FileBrowsers/SearchFileBrowser.tsx b/packages/files-ui/src/Components/Modules/FileBrowsers/SearchFileBrowser.tsx index 3fc8cd8c90..392ecc47e3 100644 --- a/packages/files-ui/src/Components/Modules/FileBrowsers/SearchFileBrowser.tsx +++ b/packages/files-ui/src/Components/Modules/FileBrowsers/SearchFileBrowser.tsx @@ -84,7 +84,7 @@ const SearchFileBrowser: React.FC = ({ controls = false const getPath = useCallback((cid: string): string => { const searchEntry = getSearchEntry(cid) // Set like this as look ups should always be using available cids - return searchEntry ? searchEntry.path : "" + return searchEntry ? searchEntry.path.replace(searchEntry.content.name, "") : "" }, [getSearchEntry]) const pathContents: FileSystemItem[] = useMemo(() => diff --git a/packages/files-ui/src/Components/Modules/FileBrowsers/ShareModal.tsx b/packages/files-ui/src/Components/Modules/FileBrowsers/ShareModal.tsx index 3c615971bb..d88b27c568 100644 --- a/packages/files-ui/src/Components/Modules/FileBrowsers/ShareModal.tsx +++ b/packages/files-ui/src/Components/Modules/FileBrowsers/ShareModal.tsx @@ -321,7 +321,6 @@ const ShareModal = ({ close, fileSystemItems }: IShareFileProps) => { } -
{isUsingCurrentBucket ? ( @@ -464,3 +463,4 @@ const ShareModal = ({ close, fileSystemItems }: IShareFileProps) => { } export default ShareModal + diff --git a/packages/files-ui/src/Components/Modules/FileBrowsers/SharedFoldersOverview.tsx b/packages/files-ui/src/Components/Modules/FileBrowsers/SharedFoldersOverview.tsx index f3d3df1afd..2bc948f9dd 100644 --- a/packages/files-ui/src/Components/Modules/FileBrowsers/SharedFoldersOverview.tsx +++ b/packages/files-ui/src/Components/Modules/FileBrowsers/SharedFoldersOverview.tsx @@ -160,6 +160,7 @@ const SharedFolderOverview = () => { const openSharedFolder = useCallback((bucketId: string) => { redirect(ROUTE_LINKS.SharedFolderExplorer(bucketId, "/")) }, [redirect]) + return ( <>
{ setError("") try { - const content = await getFileContent( id, { @@ -69,8 +68,6 @@ export const useGetFile = () => { setError(t`There was an error getting the preview.`) } } - - }, [bucket, getFileContent]) return { getFile, isDownloading, error, downloadProgress } diff --git a/packages/files-ui/src/Components/Modules/FileBrowsers/views/FilesList.tsx b/packages/files-ui/src/Components/Modules/FileBrowsers/views/FilesList.tsx index 78da7bc74e..e2588b2091 100644 --- a/packages/files-ui/src/Components/Modules/FileBrowsers/views/FilesList.tsx +++ b/packages/files-ui/src/Components/Modules/FileBrowsers/views/FilesList.tsx @@ -43,7 +43,6 @@ import { CONTENT_TYPES } from "../../../../Utils/Constants" import { CSFTheme } from "../../../../Themes/types" import MimeMatcher from "../../../../Utils/MimeMatcher" import { useLanguageContext } from "../../../../Contexts/LanguageContext" -import { getPathWithFile } from "../../../../Utils/pathUtils" import SurveyBanner from "../../../SurveyBanner" import { DragPreviewLayer } from "./DragPreviewLayer" import { useFileBrowser } from "../../../../Contexts/FileBrowserContext" @@ -1083,7 +1082,7 @@ const FilesList = ({ isShared = false }: Props) => { closePreview={closePreview} nextFile={fileIndex < files.length - 1 ? setNextPreview : undefined} previousFile={fileIndex > 0 ? setPreviousPreview : undefined} - filePath={isSearch && getPath ? getPath(files[fileIndex].cid) : getPathWithFile(currentPath, files[fileIndex].name)} + filePath={isSearch && getPath ? getPath(files[fileIndex].cid) : currentPath} /> )} { filePath && isReportFileModalOpen && diff --git a/packages/files-ui/src/Components/Modules/FilePreviewModal.tsx b/packages/files-ui/src/Components/Modules/FilePreviewModal.tsx index cf8fac1a96..708a6c1493 100644 --- a/packages/files-ui/src/Components/Modules/FilePreviewModal.tsx +++ b/packages/files-ui/src/Components/Modules/FilePreviewModal.tsx @@ -27,9 +27,11 @@ import { useFileBrowser } from "../../Contexts/FileBrowserContext" import { useGetFile } from "./FileBrowsers/hooks/useGetFile" import { useMemo } from "react" import Menu from "../../UI-components/Menu" +import { getPathWithFile } from "../../Utils/pathUtils" export interface IPreviewRendererProps { contents: Blob + contentType?: string } const SUPPORTED_FILE_TYPES: Record> = { @@ -196,6 +198,13 @@ const FilePreviewModal = ({ file, nextFile, previousFile, closePreview, filePath delta: 20 }) + const previewRendererKey = useMemo(() => content_type && + Object.keys(SUPPORTED_FILE_TYPES).find((type) => { + const matcher = new MimeMatcher(type) + + return matcher.match(content_type) + }), [content_type]) + useEffect(() => { let bucketId // Handle preview in Search where a Bucket is not available, but can be assumed to be a `CSF` bucket @@ -205,24 +214,24 @@ const FilePreviewModal = ({ file, nextFile, previousFile, closePreview, filePath } else { bucketId = bucket.id } - getFile({ file, filePath, bucketId }) - .then(setFileContent) - .catch(console.error) - }, [file, filePath, getFile, bucket, buckets]) - const validRendererMimeType = - content_type && - Object.keys(SUPPORTED_FILE_TYPES).find((type) => { - const matcher = new MimeMatcher(type) + if (previewRendererKey) { + setFileContent(undefined) + getFile({ file, filePath: getPathWithFile(filePath, file.name), bucketId }) + .then((content) => { + setFileContent(content) + }) + .catch(console.error) + } + }, [file, filePath, getFile, bucket, buckets, previewRendererKey]) + - return matcher.match(content_type) - }) const PreviewComponent = - content_type && - fileContent && - validRendererMimeType && - SUPPORTED_FILE_TYPES[validRendererMimeType] + !!content_type && + !!fileContent && + !!previewRendererKey && + SUPPORTED_FILE_TYPES[previewRendererKey] useHotkeys("Esc,Escape", () => { if (file) { @@ -231,15 +240,11 @@ const FilePreviewModal = ({ file, nextFile, previousFile, closePreview, filePath }) useHotkeys("Left,ArrowLeft", () => { - if (file && previousFile) { - previousFile() - } + previousFile && previousFile() }) useHotkeys("Right,ArrowRight", () => { - if (file && nextFile) { - nextFile() - } + nextFile && nextFile() }) const handleDownload = useCallback(() => { @@ -389,7 +394,11 @@ const FilePreviewModal = ({ file, nextFile, previousFile, closePreview, filePath !error && compatibleFilesMatcher.match(content_type) && fileContent && - PreviewComponent && } + PreviewComponent && + + }
{desktop && ( diff --git a/packages/files-ui/src/Components/Modules/LinkSharingModule.tsx b/packages/files-ui/src/Components/Modules/LinkSharingModule.tsx new file mode 100644 index 0000000000..8866079327 --- /dev/null +++ b/packages/files-ui/src/Components/Modules/LinkSharingModule.tsx @@ -0,0 +1,159 @@ +import React, { useCallback, useEffect, useMemo, useState } from "react" +import { Button, CheckCircleIcon, Loading, Typography, useHistory, useLocation } from "@chainsafe/common-components" +import { getBucketDecryptionFromHash, getJWT } from "../../Utils/pathUtils" +import { useFilesApi } from "../../Contexts/FilesApiContext" +import { useThresholdKey } from "../../Contexts/ThresholdKeyContext" +import { Trans } from "@lingui/macro" +import { useFiles } from "../../Contexts/FilesContext" +import jwtDecode from "jwt-decode" +import { createStyles, makeStyles } from "@chainsafe/common-theme" +import { CSFTheme } from "../../Themes/types" +import { ROUTE_LINKS } from "../FilesRoutes" +import { translatedPermission } from "./FileBrowsers/LinkSharing/LinkList" +import { NonceResponsePermission } from "@chainsafe/files-api-client" + +const useStyles = makeStyles( + ({ constants, palette, breakpoints }: CSFTheme) => + createStyles({ + root:{ + display: "flex", + flexDirection: "column", + alignItems: "center" + }, + box: { + backgroundColor: constants.loginModule.background, + border: `1px solid ${constants.landing.border}`, + boxShadow: constants.landing.boxShadow, + borderRadius: 6, + maxWidth: constants.generalUnit * 70, + padding: constants.generalUnit * 5, + [breakpoints.down("md")]: { + justifyContent: "center", + width: "100%" + } + }, + icon : { + display: "flex", + alignItems: "center", + fontSize: constants.generalUnit * 6, + "& svg": { + marginRight: constants.generalUnit, + fill: palette.additional["gray"][7] + } + }, + error: { + color: palette.error.main + }, + messageWrapper: { + display: "flex", + flexDirection: "column", + justifyContent: "center", + alignItems: "center" + }, + browseButton : { + marginTop: constants.generalUnit * 2 + } + }) +) + +interface DecodedJwt { + bucket_id?: string + permission?: NonceResponsePermission +} +const LinkSharingModule = () => { + const { pathname, hash } = useLocation() + const { redirect } = useHistory() + const jwt = useMemo(() => getJWT(pathname), [pathname]) + const bucketDecryptionKey = useMemo(() => getBucketDecryptionFromHash(hash), [hash]) + const { filesApiClient } = useFilesApi() + const { refreshBuckets, buckets } = useFiles() + const { publicKey, encryptForPublicKey } = useThresholdKey() + const [encryptedEncryptionKey, setEncryptedEncryptionKey] = useState("") + const [error, setError] = useState("") + const classes = useStyles() + const { bucket_id: bucketId, permission } = useMemo(() => { + try { + return (jwt && jwtDecode(jwt)) || {} + }catch (e) { + console.error(e) + return {} + } + }, [jwt]) + const newBucket = useMemo(() => buckets.find((b) => b.id === bucketId), [bucketId, buckets]) + + useEffect(() => { + if(!publicKey || !bucketDecryptionKey) return + + encryptForPublicKey(publicKey, bucketDecryptionKey) + .then(setEncryptedEncryptionKey) + .catch(console.error) + + }, [bucketDecryptionKey, encryptForPublicKey, publicKey]) + + useEffect(() => { + if(!jwt || !encryptedEncryptionKey || !!newBucket) return + + filesApiClient.verifyNonce({ jwt, encryption_key: encryptedEncryptionKey }) + .catch((e:any) => { + console.error(e) + setError(e.error.message) + }) + .finally(() => { + refreshBuckets() + }) + }, [encryptedEncryptionKey, error, filesApiClient, jwt, newBucket, refreshBuckets]) + + const onBrowseBucket = useCallback(() => { + newBucket && redirect(ROUTE_LINKS.SharedFolderExplorer(newBucket.id, "/")) + }, [newBucket, redirect]) + + return ( +
+
+
+ {!error && !newBucket && ( + <> + + + Adding you to the shared folder... + + + )} + {!error && newBucket && permission && ( + <> + + + + You were added to the shared folder ({translatedPermission(permission)}): {newBucket.name} + + + + + )} +
+ {!!error && ( + + {error} + + )} +
+
+ ) +} + +export default LinkSharingModule diff --git a/packages/files-ui/src/Components/Modules/LoginModule/InitialScreen.tsx b/packages/files-ui/src/Components/Modules/LoginModule/InitialScreen.tsx index f693f6a190..3dc697007d 100644 --- a/packages/files-ui/src/Components/Modules/LoginModule/InitialScreen.tsx +++ b/packages/files-ui/src/Components/Modules/LoginModule/InitialScreen.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState } from "react" +import React, { useCallback, useMemo, useState } from "react" import { Button, GithubLogoIcon, @@ -7,7 +7,8 @@ import { Loading, Typography, FormikTextInput, - EthereumLogoIcon + EthereumLogoIcon, + useLocation } from "@chainsafe/common-components" import { createStyles, makeStyles, useThemeSwitcher } from "@chainsafe/common-theme" import { CSFTheme } from "../../../Themes/types" @@ -16,7 +17,7 @@ import { useFilesApi } from "../../../Contexts/FilesApiContext" import { useWeb3 } from "@chainsafe/web3-context" import { useThresholdKey } from "../../../Contexts/ThresholdKeyContext" import { LOGIN_TYPE } from "@toruslabs/torus-direct-web-sdk" -import { ROUTE_LINKS } from "../../FilesRoutes" +import { LINK_SHARING_BASE, ROUTE_LINKS } from "../../FilesRoutes" import clsx from "clsx" import { IdentityProvider } from "@chainsafe/files-api-client" import PasswordlessEmail from "./PasswordlessEmail" @@ -91,14 +92,14 @@ const useStyles = makeStyles( maxWidth: 240 }, headerText: { + textAlign: "center", [breakpoints.up("md")]: { paddingTop: constants.generalUnit * 4, paddingBottom: constants.generalUnit * 8 }, [breakpoints.down("md")]: { paddingTop: constants.generalUnit * 3, - paddingBottom: constants.generalUnit * 3, - textAlign: "center" + paddingBottom: constants.generalUnit * 3 } }, footer: { @@ -173,6 +174,8 @@ const InitialScreen = ({ className }: IInitialScreen) => { const [isConnecting, setIsConnecting] = useState(false) const { filesApiClient } = useFilesApi() const [email, setEmail] = useState("") + const { state } = useLocation<{from?: string}>() + const isSharing = useMemo(() => state?.from?.includes(LINK_SHARING_BASE), [state]) const handleSelectWalletAndConnect = async () => { setError(undefined) @@ -375,7 +378,10 @@ const InitialScreen = ({ className }: IInitialScreen) => { component="h1" className={classes.headerText} > - Get Started + {isSharing + ? Sign in/up to access the shared folder + : Get Started + } )} {!error && ( diff --git a/packages/files-ui/src/Components/Modules/PreviewRenderers/ImagePreview.tsx b/packages/files-ui/src/Components/Modules/PreviewRenderers/ImagePreview.tsx index 997a693218..a2217961b1 100644 --- a/packages/files-ui/src/Components/Modules/PreviewRenderers/ImagePreview.tsx +++ b/packages/files-ui/src/Components/Modules/PreviewRenderers/ImagePreview.tsx @@ -1,6 +1,8 @@ import React, { useEffect, useState } from "react" import { IPreviewRendererProps } from "../FilePreviewModal" import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch" +import heicConvert from "heic-convert" + import { makeStyles, ITheme, @@ -11,7 +13,8 @@ import { Button, ZoomInIcon, ZoomOutIcon, - FullscreenIcon + FullscreenIcon, + Loading } from "@chainsafe/common-components" const useStyles = makeStyles( @@ -41,67 +44,82 @@ const useStyles = makeStyles( }) ) -const ImagePreview: React.FC = ({ contents }) => { +const ImagePreview: React.FC = ({ contents, contentType }) => { const [imageUrl, setImageUrl] = useState() - + const [loading, setLoading] = useState(false) useEffect(() => { - setImageUrl(URL.createObjectURL(contents)) - - return () => { - imageUrl && URL.revokeObjectURL(imageUrl) + if (contentType !== "image/heic") { + setImageUrl(URL.createObjectURL(contents)) + } else { + setLoading(true) + contents.arrayBuffer() + .then(b => heicConvert({ + buffer: Buffer.from(b), + format: "JPEG", + quality: 0.5 + })) + .catch(console.error) + .then(c => setImageUrl(URL.createObjectURL(new Blob([c])))) + .finally(() => setLoading(false)) } - // eslint-disable-next-line - }, [contents]) + }, [contents, contentType]) + const classes = useStyles() const { desktop } = useThemeSwitcher() return (
- - { + {loading + ? + : + { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - ({ zoomIn, zoomOut, resetTransform }) => ( - <> - {desktop && ( -
- - - -
- )} - - - - - ) - } -
+ ({ zoomIn, zoomOut, resetTransform }) => ( + <> + {desktop && ( +
+ + + +
+ )} + + imageUrl && URL.revokeObjectURL(imageUrl)} /> + + + ) + } +
+ }
) } diff --git a/packages/files-ui/src/Components/Pages/LinkSharingLanding.tsx b/packages/files-ui/src/Components/Pages/LinkSharingLanding.tsx new file mode 100644 index 0000000000..17a5f585b0 --- /dev/null +++ b/packages/files-ui/src/Components/Pages/LinkSharingLanding.tsx @@ -0,0 +1,11 @@ +import React from "react" +import { usePageTrack } from "../../Contexts/PosthogContext" +import LinkSharingModule from "../Modules/LinkSharingModule" + +const LinkSharingLanding = () => { + usePageTrack() + + return +} + +export default LinkSharingLanding diff --git a/packages/files-ui/src/Components/Pages/LoginPage.tsx b/packages/files-ui/src/Components/Pages/LoginPage.tsx index 8b32ef849e..0a5f26bb13 100644 --- a/packages/files-ui/src/Components/Pages/LoginPage.tsx +++ b/packages/files-ui/src/Components/Pages/LoginPage.tsx @@ -12,7 +12,6 @@ import BottomDarkSVG from "../../Media/landing/layers/dark/Bottom.dark.svg" import TopDarkSVG from "../../Media/landing/layers/dark/Top.dark.svg" import BottomLightSVG from "../../Media/landing/layers/light/Bottom.light.svg" import TopLightSVG from "../../Media/landing/layers/light/Top.light.svg" -// import { ForegroundSVG } from "../../Media/landing/layers/ForegroundSVG" import MigrateAccount from "../Modules/LoginModule/MigrateAccount" import InitializeAccount from "../Modules/LoginModule/InitializeAccount" import { useFilesApi } from "../../Contexts/FilesApiContext" @@ -167,8 +166,6 @@ const LoginPage = () => { ChainSafe Files - <> - { themeKey === "dark" ? <> diff --git a/packages/files-ui/src/Components/SurveyBanner.tsx b/packages/files-ui/src/Components/SurveyBanner.tsx index 92778c79c9..07ec39da8b 100644 --- a/packages/files-ui/src/Components/SurveyBanner.tsx +++ b/packages/files-ui/src/Components/SurveyBanner.tsx @@ -68,7 +68,10 @@ const SurveyBanner = ({ onHide }: Props) => { }, [onClose]) return ( -
+
@@ -85,6 +88,7 @@ const SurveyBanner = ({ onHide }: Props) => {
loggedinAs: string + createJWT: (bucketId: string, nonceId: string, nonce: NonceResponsePermission) => string | undefined } type ThresholdKeyProviderProps = { @@ -396,6 +399,31 @@ const ThresholdKeyProvider = ({ children, network = "mainnet", enableLogging = f } }, [userInfo, address]) + const createJWT = useCallback((bucketId: string, nonceId: string, permission: NonceResponsePermission) => { + + if(!privateKey) { + console.error("no private key found") + return + } + + const ke = new keyEncoder("secp256k1") + const pem = ke.encodePrivate(privateKey, "raw", "pem") + const header = { alg: "ES256", typ: "JWT" } + const payload = { + type: "link_sharing", + permission, + iat: KJUR.jws.IntDate.get("now"), + bucket_id: bucketId, + nonce_id: nonceId + } + + const sHeader = JSON.stringify(header) + const sPayload = JSON.stringify(payload) + + const sJWT = KJUR.jws.JWS.sign("ES256", sHeader, sPayload, pem) + return sJWT + }, [privateKey]) + const login = async (loginType: IdentityProvider, tokenInfo?: {token: string; email: string}) => { if (!TKeySdk || maintenanceMode) return try { @@ -828,7 +856,8 @@ const ThresholdKeyProvider = ({ children, network = "mainnet", enableLogging = f resetStatus: () => setStatus("initialized"), getAvailableShareIndices, refreshTKeyMeta, - loggedinAs + loggedinAs, + createJWT }} > {!isNewDevice && pendingShareTransferRequests.length > 0 && process.env.REACT_APP_TEST !== "true" && ( diff --git a/packages/files-ui/src/Themes/DarkTheme.ts b/packages/files-ui/src/Themes/DarkTheme.ts index d2fad329d0..28ca9b9ab5 100644 --- a/packages/files-ui/src/Themes/DarkTheme.ts +++ b/packages/files-ui/src/Themes/DarkTheme.ts @@ -332,9 +332,9 @@ export const darkTheme = createTheme({ ...UI_CONSTANTS, ...({ menu:{ - backgroundColor: "var(--gray1)", + backgroundColor: "var(--gray2)", color: "var(--gray8)", - backgroundOptionHover: "var(--gray2)" + backgroundOptionHover: "var(--gray3)" }, landing: { background: "var(--gray2)", diff --git a/packages/files-ui/src/Utils/contentTypeGuesser.ts b/packages/files-ui/src/Utils/contentTypeGuesser.ts index ae7b19cdd3..49ccbe92f7 100644 --- a/packages/files-ui/src/Utils/contentTypeGuesser.ts +++ b/packages/files-ui/src/Utils/contentTypeGuesser.ts @@ -1,7 +1,7 @@ const guessContentType = (fileName: string) => { const { length, [length - 1]: ext } = fileName.split(".") - switch (ext) { + switch (ext.toLowerCase()) { case "pdf": return "application/pdf" case "jpg": @@ -9,7 +9,8 @@ const guessContentType = (fileName: string) => { case "png": case "gif": case "bmp": - return `image/${ext}` + case "heic": + return `image/${ext.toLowerCase()}` case "mp3": case "m4a": return `audio/${ext}` diff --git a/packages/files-ui/src/Utils/pathUtils.ts b/packages/files-ui/src/Utils/pathUtils.ts index ee3cf3a5f6..5f96cf6bae 100644 --- a/packages/files-ui/src/Utils/pathUtils.ts +++ b/packages/files-ui/src/Utils/pathUtils.ts @@ -103,6 +103,29 @@ export const isSubFolder = (fold1: string, fold2: string) => { return result } +// get the jwt from /link-sharing/permision/jwt +export const getJWT = (pathname: string) => { + const arrayOfPaths = getArrayOfPaths(pathname) + + if(arrayOfPaths.length !== 3){ + console.error("JWT extraction error, unexpected path", pathname) + return + } + + return decodeURIComponent(arrayOfPaths[2]) +} + +// return the hash from #hash +export const getBucketDecryptionFromHash = (hash: string) => { + + if(!hash.startsWith("#")){ + console.error("Bucket encryption key extraction error, unexpected hash", hash) + return + } + + return decodeURIComponent(hash.substr(1)) +} + export const getUrlSafePathWithFile = (path: string, fileName: string) => { let urlSafePath = getURISafePathFromArray(getArrayOfPaths(path)) if (urlSafePath === "/") { diff --git a/packages/files-ui/src/locales/de/messages.po b/packages/files-ui/src/locales/de/messages.po index c03ea80731..61547b73cf 100644 --- a/packages/files-ui/src/locales/de/messages.po +++ b/packages/files-ui/src/locales/de/messages.po @@ -43,6 +43,9 @@ msgstr "Weitere Dateien hinzufügen" msgid "Add viewers and editors by username, sharing id or Ethereum address." msgstr "" +msgid "Adding you to the shared folder..." +msgstr "" + msgid "An error occurred:" msgstr "Es ist ein Fehler aufgetreten:" @@ -58,6 +61,9 @@ msgstr "Der Sicherungsgeheimsatz stimmt nicht mit dem Benutzerkonto überein, bi msgid "Bin" msgstr "Papierkorb" +msgid "Browse {0}" +msgstr "" + msgid "Browser:" msgstr "Browser:" @@ -133,6 +139,9 @@ msgstr "" msgid "Copy info" msgstr "" +msgid "Copy link" +msgstr "" + msgid "Copy over" msgstr "" @@ -160,6 +169,9 @@ msgstr "" msgid "Create folder" msgstr "Ordner erstellen" +msgid "Create new link" +msgstr "" + msgid "Create your public username in <0>Settings!" msgstr "" @@ -643,6 +655,9 @@ msgstr "" msgid "Sharing files" msgstr "" +msgid "Sharing link" +msgstr "" + msgid "Sign Out" msgstr "Abmelden" @@ -652,6 +667,9 @@ msgstr "Anmelden" msgid "Sign in with a different account" msgstr "Mit einem anderen Konto anmelden" +msgid "Sign in/up to access the shared folder" +msgstr "" + msgid "Sign me up!" msgstr "" @@ -844,21 +862,33 @@ msgstr "Sie können keine Ordner in diesen Pfad verschieben" msgid "You haven't set a username yet." msgstr "Sie haben noch keinen Benutzernamen festgelegt." +msgid "You were added to the shared folder ({0}): {1}" +msgstr "" + msgid "You will need to sign a message in your wallet to complete sign in." msgstr "" msgid "Your recovery key can be used to restore your account in place of your backup secret phrase." msgstr "Ihr Wiederherstellungsschlüssel kann zur Wiederherstellung Ihres Kontos anstelle Ihres Sicherungsgeheimsatz verwendet werden." +msgid "edit rights" +msgstr "" + msgid "me" msgstr "ich" msgid "on" msgstr "am" +msgid "read rights" +msgstr "" + msgid "unknown" msgstr "unbekannt" +msgid "with" +msgstr "" + msgid "{0, plural, one {Downloading {1} file} other {Downloading {2} files}}" msgstr "{0, plural, one {{1} Datei wird heruntergeladen} other {{2} Dateien werden heruntergeladen}}" diff --git a/packages/files-ui/src/locales/en/messages.po b/packages/files-ui/src/locales/en/messages.po index cbd244e49d..04a0eccd69 100644 --- a/packages/files-ui/src/locales/en/messages.po +++ b/packages/files-ui/src/locales/en/messages.po @@ -43,6 +43,9 @@ msgstr "Add more files" msgid "Add viewers and editors by username, sharing id or Ethereum address." msgstr "Add viewers and editors by username, sharing id or Ethereum address." +msgid "Adding you to the shared folder..." +msgstr "Adding you to the shared folder..." + msgid "An error occurred:" msgstr "An error occurred:" @@ -58,6 +61,9 @@ msgstr "Backup secret phrase does not match user account, please double-check an msgid "Bin" msgstr "Bin" +msgid "Browse {0}" +msgstr "Browse {0}" + msgid "Browser:" msgstr "Browser:" @@ -133,6 +139,9 @@ msgstr "Copy file" msgid "Copy info" msgstr "Copy info" +msgid "Copy link" +msgstr "Copy link" + msgid "Copy over" msgstr "Copy over" @@ -160,6 +169,9 @@ msgstr "Create a new shared folder" msgid "Create folder" msgstr "Create folder" +msgid "Create new link" +msgstr "Create new link" + msgid "Create your public username in <0>Settings!" msgstr "Create your public username in <0>Settings!" @@ -643,6 +655,9 @@ msgstr "Shared with" msgid "Sharing files" msgstr "Sharing files" +msgid "Sharing link" +msgstr "Sharing link" + msgid "Sign Out" msgstr "Sign Out" @@ -652,6 +667,9 @@ msgstr "Sign in" msgid "Sign in with a different account" msgstr "Sign in with a different account" +msgid "Sign in/up to access the shared folder" +msgstr "Sign in/up to access the shared folder" + msgid "Sign me up!" msgstr "Sign me up!" @@ -844,21 +862,33 @@ msgstr "You can't move folders to this path" msgid "You haven't set a username yet." msgstr "You haven't set a username yet." +msgid "You were added to the shared folder ({0}): {1}" +msgstr "You were added to the shared folder ({0}): {1}" + msgid "You will need to sign a message in your wallet to complete sign in." msgstr "You will need to sign a message in your wallet to complete sign in." msgid "Your recovery key can be used to restore your account in place of your backup secret phrase." msgstr "Your recovery key can be used to restore your account in place of your backup secret phrase." +msgid "edit rights" +msgstr "edit rights" + msgid "me" msgstr "me" msgid "on" msgstr "on" +msgid "read rights" +msgstr "read rights" + msgid "unknown" msgstr "unknown" +msgid "with" +msgstr "with" + msgid "{0, plural, one {Downloading {1} file} other {Downloading {2} files}}" msgstr "{0, plural, one {Downloading {1} file} other {Downloading {2} files}}" diff --git a/packages/files-ui/src/locales/es/messages.po b/packages/files-ui/src/locales/es/messages.po index a7aaf63fd4..98cde9765d 100644 --- a/packages/files-ui/src/locales/es/messages.po +++ b/packages/files-ui/src/locales/es/messages.po @@ -44,6 +44,9 @@ msgstr "Agrega mas archivos" msgid "Add viewers and editors by username, sharing id or Ethereum address." msgstr "" +msgid "Adding you to the shared folder..." +msgstr "" + msgid "An error occurred:" msgstr "" @@ -59,6 +62,9 @@ msgstr "" msgid "Bin" msgstr "Papelera" +msgid "Browse {0}" +msgstr "" + msgid "Browser:" msgstr "Navegador:" @@ -134,6 +140,9 @@ msgstr "" msgid "Copy info" msgstr "" +msgid "Copy link" +msgstr "" + msgid "Copy over" msgstr "" @@ -161,6 +170,9 @@ msgstr "" msgid "Create folder" msgstr "Crear Carpeta" +msgid "Create new link" +msgstr "" + msgid "Create your public username in <0>Settings!" msgstr "" @@ -644,6 +656,9 @@ msgstr "" msgid "Sharing files" msgstr "" +msgid "Sharing link" +msgstr "" + msgid "Sign Out" msgstr "Desconectar" @@ -653,6 +668,9 @@ msgstr "Registrarse" msgid "Sign in with a different account" msgstr "Inicie sesión con una cuenta diferente" +msgid "Sign in/up to access the shared folder" +msgstr "" + msgid "Sign me up!" msgstr "" @@ -845,21 +863,33 @@ msgstr "" msgid "You haven't set a username yet." msgstr "" +msgid "You were added to the shared folder ({0}): {1}" +msgstr "" + msgid "You will need to sign a message in your wallet to complete sign in." msgstr "Deberá firmar un mensaje en su billetera para completar el inicio de sesión." msgid "Your recovery key can be used to restore your account in place of your backup secret phrase." msgstr "" +msgid "edit rights" +msgstr "" + msgid "me" msgstr "" msgid "on" msgstr "en" +msgid "read rights" +msgstr "" + msgid "unknown" msgstr "" +msgid "with" +msgstr "" + msgid "{0, plural, one {Downloading {1} file} other {Downloading {2} files}}" msgstr "" diff --git a/packages/files-ui/src/locales/fr/messages.po b/packages/files-ui/src/locales/fr/messages.po index 27b41850de..c271d9b08a 100644 --- a/packages/files-ui/src/locales/fr/messages.po +++ b/packages/files-ui/src/locales/fr/messages.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-04-23 11:05+0200\n" -"PO-Revision-Date: 2021-10-09 17:23+0000\n" +"PO-Revision-Date: 2021-10-20 00:49+0000\n" "Last-Translator: J. Lavoie \n" "Language-Team: French \n" "Language: fr\n" @@ -44,6 +44,9 @@ msgstr "Ajouter d’autres fichiers" msgid "Add viewers and editors by username, sharing id or Ethereum address." msgstr "Ajoutez des personnes pouvant visualiser ou afficher par nom d'utilisateur, identifiant de partage ou adresse Ethereum." +msgid "Adding you to the shared folder..." +msgstr "" + msgid "An error occurred:" msgstr "Une erreur s'est produite :" @@ -59,6 +62,9 @@ msgstr "La phrase secrète de sauvegarde est incorrecte, merci de vérifier et r msgid "Bin" msgstr "Corbeille" +msgid "Browse {0}" +msgstr "" + msgid "Browser:" msgstr "Explorateur :" @@ -134,6 +140,9 @@ msgstr "Copier le fichier" msgid "Copy info" msgstr "Copier les infos" +msgid "Copy link" +msgstr "" + msgid "Copy over" msgstr "Copier" @@ -147,7 +156,7 @@ msgid "Create" msgstr "Créer" msgid "Create Folder" -msgstr "" +msgstr "Créer un dossier" msgid "Create Shared Folder" msgstr "Créer un dossier partagé" @@ -161,6 +170,9 @@ msgstr "Créer un nouveau dossier partagé" msgid "Create folder" msgstr "Créer un dossier" +msgid "Create new link" +msgstr "" + msgid "Create your public username in <0>Settings!" msgstr "Créez votre nom d'utilisateur public dans <0>Paramètres !" @@ -285,7 +297,7 @@ msgid "First name" msgstr "Prénom" msgid "Folder name is already in use" -msgstr "" +msgstr "Le nom du dossier est déjà utilisé" msgid "Folder uploads are not supported currently" msgstr "Le téléversement de dossiers n'est pas actuellement pris en charge" @@ -450,7 +462,7 @@ msgid "Number of copies (Replication Factor)" msgstr "Nombre de copies (facteur de réplication)" msgid "OK" -msgstr "" +msgstr "OK" msgid "One sec, getting files ready…" msgstr "Un instant, nous préparons les fichiers…" @@ -642,7 +654,10 @@ msgid "Shared with" msgstr "Partagé avec" msgid "Sharing files" -msgstr "Partage des fichiers" +msgstr "" + +msgid "Sharing link" +msgstr "" msgid "Sign Out" msgstr "Se déconnecter" @@ -653,6 +668,9 @@ msgstr "Se connecter" msgid "Sign in with a different account" msgstr "Se connecter avec un autre compte" +msgid "Sign in/up to access the shared folder" +msgstr "" + msgid "Sign me up!" msgstr "S'inscrire !" @@ -720,7 +738,7 @@ msgid "There was an error connecting your wallet" msgstr "Une erreur s’est produite lors de la connexion de votre wallet" msgid "There was an error creating the folder {0}" -msgstr "" +msgstr "Il y a eu une erreur lors de la création du dossier {0}" msgid "There was an error deleting your data" msgstr "Une erreur s'est produite lors de la suppression de vos données" @@ -845,21 +863,33 @@ msgstr "Vous ne pouvez pas déplacer les dossiers vers ce chemin" msgid "You haven't set a username yet." msgstr "Vous n’avez pas encore défini de nom d’utilisateur." +msgid "You were added to the shared folder ({0}): {1}" +msgstr "" + msgid "You will need to sign a message in your wallet to complete sign in." msgstr "Vous devrez signer un message avec votre wallet pour terminer la procédure connexion." msgid "Your recovery key can be used to restore your account in place of your backup secret phrase." msgstr "Votre clé de récupération peut être utilisée pour restaurer votre compte à la place de votre phrase de sauvegarde secrète." +msgid "edit rights" +msgstr "" + msgid "me" msgstr "moi" msgid "on" msgstr "le" +msgid "read rights" +msgstr "" + msgid "unknown" msgstr "inconnu" +msgid "with" +msgstr "" + msgid "{0, plural, one {Downloading {1} file} other {Downloading {2} files}}" msgstr "{0, plural, one {Téléchargement de {1} fichier} other {Téléchargement de {2} fichiers}}" diff --git a/packages/files-ui/src/locales/no/messages.po b/packages/files-ui/src/locales/no/messages.po index 348339e91e..d2f340a862 100644 --- a/packages/files-ui/src/locales/no/messages.po +++ b/packages/files-ui/src/locales/no/messages.po @@ -43,6 +43,9 @@ msgstr "Legg til flere filer" msgid "Add viewers and editors by username, sharing id or Ethereum address." msgstr "" +msgid "Adding you to the shared folder..." +msgstr "" + msgid "An error occurred:" msgstr "" @@ -58,6 +61,9 @@ msgstr "" msgid "Bin" msgstr "" +msgid "Browse {0}" +msgstr "" + msgid "Browser:" msgstr "" @@ -133,6 +139,9 @@ msgstr "" msgid "Copy info" msgstr "" +msgid "Copy link" +msgstr "" + msgid "Copy over" msgstr "" @@ -160,6 +169,9 @@ msgstr "" msgid "Create folder" msgstr "Opprett mappe" +msgid "Create new link" +msgstr "" + msgid "Create your public username in <0>Settings!" msgstr "" @@ -640,6 +652,9 @@ msgstr "" msgid "Sharing files" msgstr "" +msgid "Sharing link" +msgstr "" + msgid "Sign Out" msgstr "Logg ut" @@ -649,6 +664,9 @@ msgstr "Logg inn" msgid "Sign in with a different account" msgstr "Logg inn med en annen konto" +msgid "Sign in/up to access the shared folder" +msgstr "" + msgid "Sign me up!" msgstr "" @@ -841,21 +859,33 @@ msgstr "" msgid "You haven't set a username yet." msgstr "Du har ikke satt noe brukernavn enda." +msgid "You were added to the shared folder ({0}): {1}" +msgstr "" + msgid "You will need to sign a message in your wallet to complete sign in." msgstr "" msgid "Your recovery key can be used to restore your account in place of your backup secret phrase." msgstr "" +msgid "edit rights" +msgstr "" + msgid "me" msgstr "" msgid "on" msgstr "" +msgid "read rights" +msgstr "" + msgid "unknown" msgstr "" +msgid "with" +msgstr "" + msgid "{0, plural, one {Downloading {1} file} other {Downloading {2} files}}" msgstr "" diff --git a/packages/files-ui/src/serviceWorker.ts b/packages/files-ui/src/serviceWorker.ts index 8e951fbfee..8e879f512e 100644 --- a/packages/files-ui/src/serviceWorker.ts +++ b/packages/files-ui/src/serviceWorker.ts @@ -139,7 +139,7 @@ export function unregister() { .then(registration => { registration.unregister() }) - .catch(error => { + .catch((error: any) => { console.error(error.message) }) } diff --git a/packages/files-ui/src/types.d.ts b/packages/files-ui/src/types.d.ts new file mode 100644 index 0000000000..86a0e1233b --- /dev/null +++ b/packages/files-ui/src/types.d.ts @@ -0,0 +1 @@ +declare module "heic-convert"; \ No newline at end of file diff --git a/packages/gaming-ui/package.json b/packages/gaming-ui/package.json index e3029a6be3..275518b256 100644 --- a/packages/gaming-ui/package.json +++ b/packages/gaming-ui/package.json @@ -63,7 +63,7 @@ "scripts": { "postinstall": "yarn compile", "start": "yarn compile && craco --max_old_space_size=4096 start", - "build": "craco --max_old_space_size=4096 build", + "build": "craco --max_old_space_size=4096 --openssl-legacy-provider build ", "sentry": "(export REACT_APP_SENTRY_RELEASE=$(sentry-cli releases propose-version); node scripts/sentry.js)", "release": "(export REACT_APP_SENTRY_RELEASE=$(sentry-cli releases propose-version); yarn compile && yarn build && node scripts/sentry.js)", "test": "cypress open", diff --git a/packages/gaming-ui/src/serviceWorker.ts b/packages/gaming-ui/src/serviceWorker.ts index 8e951fbfee..8e879f512e 100644 --- a/packages/gaming-ui/src/serviceWorker.ts +++ b/packages/gaming-ui/src/serviceWorker.ts @@ -139,7 +139,7 @@ export function unregister() { .then(registration => { registration.unregister() }) - .catch(error => { + .catch((error: any) => { console.error(error.message) }) } diff --git a/packages/storage-ui/package.json b/packages/storage-ui/package.json index b36126a35b..d55c71882d 100644 --- a/packages/storage-ui/package.json +++ b/packages/storage-ui/package.json @@ -71,7 +71,7 @@ "scripts": { "postinstall": "yarn compile", "start": "yarn compile && craco --max_old_space_size=4096 start", - "build": "craco --max_old_space_size=4096 build", + "build": "craco --max_old_space_size=4096 --openssl-legacy-provider build ", "sentry": "(export REACT_APP_SENTRY_RELEASE=$(sentry-cli releases propose-version); node scripts/sentry.js)", "release": "(export REACT_APP_SENTRY_RELEASE=$(sentry-cli releases propose-version); yarn compile && yarn build && node scripts/sentry.js)", "test": "cypress open", diff --git a/packages/storage-ui/src/serviceWorker.ts b/packages/storage-ui/src/serviceWorker.ts index 8e951fbfee..8e879f512e 100644 --- a/packages/storage-ui/src/serviceWorker.ts +++ b/packages/storage-ui/src/serviceWorker.ts @@ -139,7 +139,7 @@ export function unregister() { .then(registration => { registration.unregister() }) - .catch(error => { + .catch((error: any) => { console.error(error.message) }) } diff --git a/yarn.lock b/yarn.lock index 3ab71bfd2f..ab35e87884 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5824,6 +5824,13 @@ dependencies: "@babel/types" "^7.3.0" +"@types/bn.js@*", "@types/bn.js@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.0.tgz#32c5d271503a12653c62cf4d2b45e6eab8cebc68" + integrity sha512-QSSVYj7pYFN49kW77o2s9xTCwZ8F2xLbjLLSEVh8D2F4JUhZtPAGOFLTD+ffqksBx/u4cE/KImFjyhqCjn/LIA== + dependencies: + "@types/node" "*" + "@types/bn.js@4.11.6", "@types/bn.js@^4.11.3", "@types/bn.js@^4.11.5": version "4.11.6" resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.6.tgz#c306c70d9358aaea33cd4eda092a742b9505967c" @@ -5831,13 +5838,6 @@ dependencies: "@types/node" "*" -"@types/bn.js@^5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.0.tgz#32c5d271503a12653c62cf4d2b45e6eab8cebc68" - integrity sha512-QSSVYj7pYFN49kW77o2s9xTCwZ8F2xLbjLLSEVh8D2F4JUhZtPAGOFLTD+ffqksBx/u4cE/KImFjyhqCjn/LIA== - dependencies: - "@types/node" "*" - "@types/color-name@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" @@ -5850,6 +5850,13 @@ dependencies: "@types/node" "*" +"@types/elliptic@^6.4.9": + version "6.4.13" + resolved "https://registry.yarnpkg.com/@types/elliptic/-/elliptic-6.4.13.tgz#7e8ac814f748deb01a712e5147b128caf9dffa2d" + integrity sha512-e8iyLJ8vMLpWxXpVWrIt0ujqsfHWgVe5XAz9IMhBYoDirK6th7J+mHjzp797OLc62ZX419nrlwwzsNAA0a0mKg== + dependencies: + "@types/bn.js" "*" + "@types/eslint-visitor-keys@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" @@ -5967,6 +5974,11 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== +"@types/jsrsasign@^8.0.13": + version "8.0.13" + resolved "https://registry.yarnpkg.com/@types/jsrsasign/-/jsrsasign-8.0.13.tgz#770c1e429107dfb0cc4f5b78b472584511a55a28" + integrity sha512-+0Ij59D6NXP48KkeLhPXeQKOyLjvA9CD7zacc0Svy2IWHdl62BmDeTvGSIwKaGZSoamLJOo+on1AG/wPRLsd7A== + "@types/lodash@^4.14.165": version "4.14.165" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.165.tgz#74d55d947452e2de0742bad65270433b63a8c30f" @@ -7420,7 +7432,7 @@ asap@~2.0.6: resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= -asn1.js@^5.2.0: +asn1.js@^5.0.1, asn1.js@^5.2.0: version "5.4.1" resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== @@ -14264,6 +14276,22 @@ he@^1.1.0, he@^1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +heic-convert@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/heic-convert/-/heic-convert-1.2.4.tgz#605820f98ace3949a40fc7b263ee0bc573a0176b" + integrity sha512-klJHyv+BqbgKiCQvCqI9IKIvweCcohDuDl0Jphearj8+16+v8eff2piVevHqq4dW9TK0r1onTR6PKHP1I4hdbA== + dependencies: + heic-decode "^1.1.2" + jpeg-js "^0.4.1" + pngjs "^3.4.0" + +heic-decode@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/heic-decode/-/heic-decode-1.1.2.tgz#974701666432e31ed64b2263a1ece7cff5218209" + integrity sha512-UF8teegxvzQPdSTcx5frIUhitNDliz/9Pui0JFdIqVRE00spVE33DcCYtZqaLNyd4y5RP/QQWZFIc1YWVKKm2A== + dependencies: + libheif-js "^1.10.0" + hex-color-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" @@ -15984,6 +16012,11 @@ jest@24.9.0: import-local "^2.0.0" jest-cli "^24.9.0" +jpeg-js@^0.4.1: + version "0.4.3" + resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.3.tgz#6158e09f1983ad773813704be80680550eff977b" + integrity sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q== + js-levenshtein@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d" @@ -16269,6 +16302,11 @@ jsqr@^1.2.0: resolved "https://registry.yarnpkg.com/jsqr/-/jsqr-1.4.0.tgz#8efb8d0a7cc6863cb6d95116b9069123ce9eb2d1" integrity sha512-dxLob7q65Xg2DvstYkRpkYtmKm2sPJ9oFhrhmudT1dZvNFFTlroai3AWSpLey/w5vMcLBXRgOJsbXpdN9HzU/A== +jsrsasign@^10.4.1: + version "10.4.1" + resolved "https://registry.yarnpkg.com/jsrsasign/-/jsrsasign-10.4.1.tgz#3aec10b6201e3c321d4ee2b1e010bc2f808ed4d0" + integrity sha512-g2CP2nb8xKdmfZhuHaJEz1zVYTsZc+lUjLFvgbMX35/cUALK0G15sQfCbCpDg/UivkjCNlq0lV6FxCfPhv0shw== + jss-plugin-camel-case@^10.0.3: version "10.4.0" resolved "https://registry.yarnpkg.com/jss-plugin-camel-case/-/jss-plugin-camel-case-10.4.0.tgz#46c75ff7fd61c304984c21af5817823f0f501ceb" @@ -16456,6 +16494,16 @@ keccak@^3.0.0: node-addon-api "^2.0.0" node-gyp-build "^4.2.0" +key-encoder@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/key-encoder/-/key-encoder-2.0.3.tgz#77073bb48ff1fe2173bb2088b83b91152c8fa4ba" + integrity sha512-fgBtpAGIr/Fy5/+ZLQZIPPhsZEcbSlYu/Wu96tNDFNSjSACw5lEIOFeaVdQ/iwrb8oxjlWi6wmWdH76hV6GZjg== + dependencies: + "@types/elliptic" "^6.4.9" + asn1.js "^5.0.1" + bn.js "^4.11.8" + elliptic "^6.4.1" + keyvaluestorage-interface@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/keyvaluestorage-interface/-/keyvaluestorage-interface-1.0.0.tgz#13ebdf71f5284ad54be94bd1ad9ed79adad515ff" @@ -16632,6 +16680,11 @@ levn@^0.3.0, levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +libheif-js@^1.10.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/libheif-js/-/libheif-js-1.12.0.tgz#9ad1ed16a8e6412b4d3d83565d285465a00e7305" + integrity sha512-hDs6xQ7028VOwAFwEtM0Q+B2x2NW69Jb2MhQFUbk3rUrHzz4qo5mqS8VrqNgYnSc8TiUGnR691LnO4uIfEE23w== + lines-and-columns@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" @@ -19043,7 +19096,7 @@ pn@^1.1.0: resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA== -pngjs@^3.3.0: +pngjs@^3.3.0, pngjs@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f" integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==