浏览代码

Initial gallary chagnes.

Pushkar Anand 4 年之前
父节点
当前提交
f9ee680c8f

+ 1 - 0
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"

+ 1 - 0
public/image.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white" width="48px" height="48px"><path d="M0 0h24v24H0z" fill="none"/><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/></svg>

+ 2 - 1
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 {

+ 37 - 9
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<string>();
     const { data } = props;
 
-    return (<Card>
-        <Card.Body>
-            <div>ID: {data?.id}</div>
-            <div>MetaData: {JSON.stringify(data?.metadata)}</div>
-        </Card.Body>
-    </Card>);
+    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 <Cont>
+        <img src={imgSrc}/>
+    </Cont>;
 }

+ 35 - 5
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<fileData[]>();
 
-    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 <Spinner animation="border" variant="primary"/>;
+        return <div className="text-center">
+            <Spinner animation="border" variant="primary"/>;
+        </div>
     }
 
-    return (data || []).map(item => <PreviewCard key={item.id} data={item} />);
+    const getThumbnail = (item) => (
+        <PreviewCard data={item}/>
+    )
+
+    return (<Container>
+        <PhotoSwipeGallery
+            items={data.map(item => ({
+                ...item,
+                src: '/image.svg',
+                w: 512,
+                h: 512,
+            }))}
+            thumbnailContent={getThumbnail}
+        />
+    </Container>);
 }

+ 38 - 7
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<fileData>[] = 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;
+}

+ 1 - 1
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);

+ 19 - 0
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);

+ 2 - 5
src/worker/decrypt.worker.js → 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();

+ 22 - 2
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"