diff --git a/package.json b/package.json index 654b6f4ed..0268274d4 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "react": "16.13.1", "react-bootstrap": "^1.3.0", "react-dom": "16.13.1", + "react-photoswipe": "^1.3.0", "scrypt-js": "^3.0.1", "styled-components": "^5.2.0", "yup": "^0.29.3" diff --git a/public/image.svg b/public/image.svg new file mode 100644 index 000000000..e247cfe2a --- /dev/null +++ b/public/image.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 5499dc9ab..8759ae2f7 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -2,7 +2,6 @@ import React, { useEffect, useState } from 'react'; import styled, {createGlobalStyle } from 'styled-components'; import Navbar from 'components/Navbar'; import constants from 'utils/strings/constants'; -import 'bootstrap/dist/css/bootstrap.min.css'; import Button from 'react-bootstrap/Button'; import Spinner from 'react-bootstrap/Spinner'; import { clearKeys } from 'utils/storage/sessionStorage'; @@ -11,6 +10,8 @@ import { useRouter } from 'next/router'; import Container from 'components/Container'; import PowerSettings from 'components/power_settings'; import Head from 'next/head'; +import 'bootstrap/dist/css/bootstrap.min.css'; +import 'react-photoswipe/lib/photoswipe.css'; const GlobalStyles = createGlobalStyle` html, body { diff --git a/src/pages/gallery/components/PreviewCard.tsx b/src/pages/gallery/components/PreviewCard.tsx index f542bdcd5..177bdc500 100644 --- a/src/pages/gallery/components/PreviewCard.tsx +++ b/src/pages/gallery/components/PreviewCard.tsx @@ -1,18 +1,46 @@ -import React from 'react'; -import Card from 'react-bootstrap/Card'; -import { fileData } from 'services/fileService'; +import React, { useEffect, useState } from 'react'; +import { fileData, getPreview } from 'services/fileService'; +import { getActualKey } from 'utils/common/key'; +import { getData, LS_KEYS } from 'utils/storage/localStorage'; +import styled from 'styled-components'; interface IProps { data: fileData, } +const Cont = styled.div` + background: #555 url(/image.svg) no-repeat center; + margin: 0 4px; + display: inline-block; + width: 200px; + height: 200px; + overflow: hidden; + + & > img { + object-fit: cover; + max-width: 100%; + min-height: 100%; + } +`; + export default function PreviewCard(props: IProps) { + const [imgSrc, setImgSrc] = useState(); const { data } = props; - return ( - - ID: {data?.id} - MetaData: {JSON.stringify(data?.metadata)} - - ); + useEffect(() => { + if (data) { + const main = async () => { + const token = getData(LS_KEYS.USER).token; + const key = await getActualKey(); + const url = await getPreview(token, data, key); + setImgSrc(url); + data.src = url; + } + main(); + } + }, [data]); + + return + + ; } diff --git a/src/pages/gallery/index.tsx b/src/pages/gallery/index.tsx index 8c104af0a..e4ea70064 100644 --- a/src/pages/gallery/index.tsx +++ b/src/pages/gallery/index.tsx @@ -1,4 +1,4 @@ -import React, { useLayoutEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { useRouter } from 'next/router'; import Spinner from 'react-bootstrap/Spinner'; import { getKey, SESSION_KEYS } from 'utils/storage/sessionStorage'; @@ -6,13 +6,27 @@ import { fileData, getFiles } from 'services/fileService'; import { getData, LS_KEYS } from 'utils/storage/localStorage'; import PreviewCard from './components/PreviewCard'; import { getActualKey } from 'utils/common/key'; +import styled from 'styled-components'; +import { PhotoSwipeGallery } from 'react-photoswipe'; + +const Container = styled.div` + max-width: 1260px; + display: flex; + flex-wrap: wrap; + margin: 0 auto; + + .pswp-thumbnail { + display: inline-block; + cursor: pointer; + } +`; export default function Gallery() { const router = useRouter(); const [loading, setLoading] = useState(false); const [data, setData] = useState(); - useLayoutEffect(() => { + useEffect(() => { const key = getKey(SESSION_KEYS.ENCRYPTION_KEY); const token = getData(LS_KEYS.USER).token; if (!key) { @@ -21,7 +35,7 @@ export default function Gallery() { const main = async () => { setLoading(true); const encryptionKey = await getActualKey(); - const resp = await getFiles("0", token, "100", encryptionKey); + const resp = await getFiles("0", token, "24", encryptionKey); setLoading(false); setData(resp); }; @@ -29,8 +43,24 @@ export default function Gallery() { }, []); if (!data || loading) { - return ; + return + ; + } - return (data || []).map(item => ); + const getThumbnail = (item) => ( + + ) + + return ( + ({ + ...item, + src: '/image.svg', + w: 512, + h: 512, + }))} + thumbnailContent={getThumbnail} + /> + ); } diff --git a/src/services/fileService.ts b/src/services/fileService.ts index 159965ab1..fab840d24 100644 --- a/src/services/fileService.ts +++ b/src/services/fileService.ts @@ -1,4 +1,7 @@ +import aescrypt from "utils/aescrypt"; import { getEndpoint } from "utils/common/apiUtil"; +import { decrypt } from "utils/crypto/aes"; +import { strToUint8 } from "utils/crypto/common"; import HTTPService from "./HTTPService"; const ENDPOINT = getEndpoint(); @@ -6,28 +9,56 @@ const ENDPOINT = getEndpoint(); export interface fileData { id: number; metadata: { - currentTimestamp: number, - }, + currentTime: number; + modificationTime: number; + latitude: number; + longitude: number; + title: string; + deviceFolder: string; + }; + encryptedPassword: string; + encryptedPasswordIV: string; + file?: string; }; const getFileDataUsingWorker = (data: any, key: string) => { return new Promise((resolve) => { - const worker = new Worker('worker/decrypt.worker.js', { type: 'module' }); + const worker = new Worker('worker/decryptMetadata.worker.js', { type: 'module' }); const onWorkerMessage = (event) => resolve(event.data); worker.addEventListener('message', onWorkerMessage); worker.postMessage({ data, key }); }); } -export const getFiles = async (sinceTimestamp: string, token: string, limit: string, key: string) => { +const getFileUsingWorker = (data: any, key: string) => { + return new Promise((resolve) => { + const worker = new Worker('worker/decryptFile.worker.js', { type: 'module' }); + const onWorkerMessage = (event) => resolve(event.data); + worker.addEventListener('message', onWorkerMessage); + worker.postMessage({ data, key }); + }); +} + +export const getFiles = async (sinceTime: string, token: string, limit: string, key: string) => { const resp = await HTTPService.get(`${ENDPOINT}/encrypted-files/diff`, { - sinceTimestamp, token, limit, + sinceTime, token, limit, }); const promises: Promise[] = resp.data.diff.map((data) => getFileDataUsingWorker(data, key)); - console.time('Metadata Parsing'); const decrypted = await Promise.all(promises); - console.timeEnd('Metadata Parsing'); return decrypted; } + +export const getPreview = async (token: string, data: fileData, key: string) => { + const resp = await HTTPService.get( + `${ENDPOINT}/encrypted-files/preview/${data.id}`, + { token }, null, { responseType: 'arraybuffer' }, + ); + const decrypted = await getFileUsingWorker({ + ...data, + file: resp.data, + }, key); + const url = URL.createObjectURL(new Blob([decrypted.file])); + return url; +} diff --git a/src/utils/aescrypt/index.ts b/src/utils/aescrypt/index.ts index 6ab4d37d5..aa7ddc61c 100644 --- a/src/utils/aescrypt/index.ts +++ b/src/utils/aescrypt/index.ts @@ -1,4 +1,3 @@ -import { resolve } from 'path'; import { aescrypt } from './aescrypt'; const decrypt = (file: Uint8Array, password: String, binaryResponse: Boolean = false) => { @@ -6,6 +5,7 @@ const decrypt = (file: Uint8Array, password: String, binaryResponse: Boolean = f try { aescrypt.decrypt(file, password, !binaryResponse, ({ data, error}) => { if (error) { + console.log(error); reject(error); } resolve(data); diff --git a/src/worker/decryptFile.worker.js b/src/worker/decryptFile.worker.js new file mode 100644 index 000000000..51edb427a --- /dev/null +++ b/src/worker/decryptFile.worker.js @@ -0,0 +1,19 @@ +import { decrypt } from "utils/crypto/aes"; +import { strToUint8 } from "utils/crypto/common"; +import aescrypt from 'utils/aescrypt'; + +function decryptFile(event) { + const main = async () => { + const data = event.data.data; + const key = event.data.key; + const password = await decrypt(data.encryptedPassword, key, data.encryptedPasswordIV); + const file = await aescrypt.decrypt(data.file, atob(password), true); + self.postMessage({ + id: data.id, + file: file, + }); + } + main(); +} + +self.addEventListener('message', decryptFile); diff --git a/src/worker/decrypt.worker.js b/src/worker/decryptMetadata.worker.js similarity index 77% rename from src/worker/decrypt.worker.js rename to src/worker/decryptMetadata.worker.js index d73c14ede..df997dd36 100644 --- a/src/worker/decrypt.worker.js +++ b/src/worker/decryptMetadata.worker.js @@ -9,11 +9,8 @@ function decryptFile(event) { const password = await decrypt(data.encryptedPassword, key, data.encryptedPasswordIV); const metadata = await aescrypt.decrypt(base64ToUint8(data.encryptedMetadata), atob(password)); self.postMessage({ - id: data.id, - ownerId: data.ownerId, - updationTime: data.updationTime, - password, - metadata: JSON.parse(metadata), + ...data, + metadata: JSON.parse(metadata) }); } main(); diff --git a/yarn.lock b/yarn.lock index 37eb2e0c1..4292d951f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2020,7 +2020,7 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -classnames@2.2.6, classnames@^2.2.6: +classnames@2.2.6, classnames@^2.2.3, classnames@^2.2.6: version "2.2.6" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== @@ -3502,6 +3502,11 @@ lodash-es@^4.17.11, lodash-es@^4.17.14, lodash-es@^4.17.15: resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78" integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ== +lodash.pick@^4.2.1: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" + integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM= + lodash.sortby@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" @@ -4232,6 +4237,11 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" +photoswipe@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/photoswipe/-/photoswipe-4.1.3.tgz#59f49494eeb9ddab5888d03392926a19bc197550" + integrity sha512-89Z43IRUyw7ycTolo+AaiDn3W1EEIfox54hERmm9bI12IB9cvRfHSHez3XhAyU8XW2EAFrC+2sKMhh7SJwn0bA== + picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: version "2.2.2" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" @@ -4368,7 +4378,7 @@ prop-types-extra@^1.1.0: react-is "^16.3.2" warning "^4.0.0" -prop-types@15.7.2, prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@15.7.2, prop-types@^15.5.10, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -4537,6 +4547,16 @@ react-overlays@^4.1.0: uncontrollable "^7.0.0" warning "^4.0.3" +react-photoswipe@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/react-photoswipe/-/react-photoswipe-1.3.0.tgz#016dd978450a8406776db97511eaf96f2ffb9cfb" + integrity sha512-1ok6vXFAj/rd60KIzF0YwCdq1Tcl+8yKqWJHbPo43lJBuwUi+LBosmBdJmswpiOzMn2496ekU0k/r6aHWQk7PQ== + dependencies: + classnames "^2.2.3" + lodash.pick "^4.2.1" + photoswipe "^4.1.0" + prop-types "^15.5.10" + react-refresh@0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"