Browse Source

Collection handling.

Pushkar Anand 4 years ago
parent
commit
ba24e8ee46

+ 23 - 0
src/components/SadFace.tsx

@@ -0,0 +1,23 @@
+import React from 'react';
+
+export default function SadFace(props) {
+    return (
+        <svg
+            xmlns="http://www.w3.org/2000/svg"
+            height={props.height}
+            viewBox={props.viewBox}
+            width={props.width}
+        >
+            <path d="M0 0h24v24H0V0z" fill="none"/>
+            <circle cx="15.5" cy="9.5" r="1.5"/>
+            <circle cx="8.5" cy="9.5" r="1.5"/>
+            <path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm0-6c-2.33 0-4.32 1.45-5.12 3.5h1.67c.69-1.19 1.97-2 3.45-2s2.75.81 3.45 2h1.67c-.8-2.05-2.79-3.5-5.12-3.5z"/>
+        </svg>
+    );
+}
+
+SadFace.defaultProps = {
+    height: 24,
+    width: 24,
+    viewBox: '0 0 24 24',
+}

+ 4 - 2
src/pages/_app.tsx

@@ -100,8 +100,10 @@ export default function App({ Component, pageProps }) {
         console.log(`%c${constants.CONSOLE_WARNING_STOP}`, 'color: red; font-size: 52px;');
         console.log(`%c${constants.CONSOLE_WARNING_DESC}`, 'font-size: 20px;');
 
-        router.events.on('routeChangeStart', () => {
-            setLoading(true);
+        router.events.on('routeChangeStart', (url: string) => {
+            if (window.location.pathname !== url.split('?')[0]) {
+                setLoading(true);
+            }
         });
 
         router.events.on('routeChangeComplete', () => {

+ 64 - 0
src/pages/gallery/components/Collections.tsx

@@ -0,0 +1,64 @@
+import React from 'react';
+import { collection } from 'services/fileService';
+import styled from 'styled-components';
+
+interface CollectionProps {
+    collections: collection[];
+    selected?: string;
+    selectCollection: (id?: string) => void;
+}
+
+const Container = styled.div`
+    margin: 0 auto;
+    overflow-y: hidden;
+    height: 40px;
+    display: flex;
+
+    @media (min-width: 1000px) {
+        width: 1000px;
+    }
+
+    @media (min-width: 450px) and (max-width: 1000px) {
+        max-width: 600px;
+    }
+
+    @media (max-width: 450px) {
+        max-width: 100%;
+    }
+`;
+
+const Wrapper = styled.div`
+    height: 70px;
+    flex: 1;
+    white-space: nowrap;
+    overflow: auto;
+    max-width: 100%;
+`
+const Chip = styled.button<{ active: boolean }>`
+    border-radius: 20px;
+    padding: 2px 10px;
+    margin: 2px 5px 2px 2px;
+    border: none;
+    background-color: ${props => props.active ? '#fff' : 'rgba(255, 255, 255, 0.3)'};
+    outline: none !important;
+
+    &:focus {
+        box-shadow : 0 0 0 2px #2666cc;
+        background-color: #eee;
+    }
+`;
+
+export default function Collections(props: CollectionProps) {
+    const { selected, collections, selectCollection } = props;
+    const clickHandler = (id?: string) => () => selectCollection(id);
+
+    return <Container>
+        <Wrapper>
+            <Chip active={!selected} onClick={clickHandler()}>All</Chip>
+            {collections?.map(item => <Chip
+                active={selected === item.id.toString()}
+                onClick={clickHandler(item.id)}
+            >{item.name}</Chip>)}
+        </Wrapper>
+    </Container>;
+}

+ 116 - 52
src/pages/gallery/index.tsx

@@ -2,7 +2,7 @@ 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';
-import { file, getFile, getFiles, getPreview } from 'services/fileService';
+import { collection, fetchCollections, file, getFile, getFiles, getPreview } from 'services/fileService';
 import { getData, LS_KEYS } from 'utils/storage/localStorage';
 import PreviewCard from './components/PreviewCard';
 import { getActualKey } from 'utils/common/key';
@@ -11,6 +11,8 @@ import { PhotoSwipe } from 'react-photoswipe';
 import { Options } from 'photoswipe';
 import AutoSizer from 'react-virtualized-auto-sizer';
 import { FixedSizeList as List } from 'react-window';
+import Collections from './components/Collections';
+import SadFace from 'components/SadFace';
 
 const Container = styled.div`
     display: block;
@@ -26,8 +28,32 @@ const Container = styled.div`
 `;
 
 const ListItem = styled.div`
-   display: flex;
-   justify-content: center;
+    display: flex;
+    justify-content: center;
+`;
+
+const DeadCenter = styled.div`
+    flex: 1;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    color: #fff;
+    text-align: center;
+    flex-direction: column;
+`;
+
+const ListContainer = styled.div`
+    @media (min-width: 1000px) {
+        width: 1000px;
+    }
+
+    @media (min-width: 450px) and (max-width: 1000px) {
+        max-width: 600px;
+    }
+
+    @media (max-width: 450px) {
+        max-width: 100%;
+    }
 `;
 
 const PAGE_SIZE = 12;
@@ -36,6 +62,7 @@ const COLUMNS = 3;
 export default function Gallery() {
     const router = useRouter();
     const [loading, setLoading] = useState(false);
+    const [collections, setCollections] = useState<collection[]>([])
     const [data, setData] = useState<file[]>();
     const [open, setOpen] = useState(false);
     const [options, setOptions] = useState<Options>({
@@ -53,15 +80,17 @@ export default function Gallery() {
         const main = async () => {
             setLoading(true);
             const encryptionKey = await getActualKey();
-            const resp = await getFiles("0", token, "100", encryptionKey);
+            const collections = await fetchCollections(token, encryptionKey);
+            const resp = await getFiles("0", token, "100", encryptionKey, collections);
             setLoading(false);
+            setCollections(collections);
             setData(resp.map(item => ({
                 ...item,
                 w: window.innerWidth,
                 h: window.innerHeight,
             })));
         };
-        main();
+        main(); 
     }, []);
 
     if (!data || loading) {
@@ -125,14 +154,14 @@ export default function Gallery() {
         setOpen(true);
     }
 
-    const getThumbnail = (data: file[], index: number) => (
-        <PreviewCard
-            key={`tile-${index}`}
-            data={data[index]}
-            updateUrl={updateUrl(index)}
+    const getThumbnail = (file: file[], index: number) => {
+        return (<PreviewCard
+            key={`tile-${file[index].id}`}
+            data={file[index]}
+            updateUrl={updateUrl(file[index].dataIndex)}
             onClick={onThumbnailClick(index)}
-        />
-    )
+        />);
+    }
 
     const getSlideData = async (instance: any, index: number, item: file) => {
         const token = getData(LS_KEYS.USER).token;
@@ -176,46 +205,81 @@ export default function Gallery() {
         }
     }
 
-    return (<Container>
-        <AutoSizer>
-            {({ height, width }) => {
-                let columns;
-                if (width >= 1000) {
-                    columns = 5;
-                } else if (width < 1000 && width >= 450) {
-                    columns = 3;
-                } else if (width < 450 && width >= 300) {
-                    columns = 2;
-                } else {
-                    columns = 1;
-                }
-                return (
-                    <List
-                        itemSize={200}
-                        height={height}
-                        width={width}
-                        itemCount={data.length / columns}
-                        
-                    >
-                        {({ index, style }) => {
-                            const arr = [];
-                            for (let i = 0; i < columns; i++) {
-                                arr.push(index * columns + i);
+    const selectCollection = (id?: string) => {
+        const href = `/gallery?collection=${id || ''}`;
+        router.push(href, undefined, { shallow: true });
+    }
+
+    const idSet = new Set();
+    const filteredData = data.map((item, index) => ({
+        ...item,
+        dataIndex: index,
+    })).filter(item => {
+        if (!idSet.has(item.id)) {
+            if (!router.query.collection || router.query.collection === item.collectionID.toString()) {
+                idSet.add(item.id);
+                return true;
+            }
+            return false;
+        }
+        return false;
+    });
+
+    return (<>
+        <Collections
+            collections={collections}
+            selected={router.query.collection?.toString()}
+            selectCollection={selectCollection}
+        />
+        {
+            filteredData.length
+                ? <Container>
+                    <AutoSizer>
+                        {({ height, width }) => {
+                            let columns;
+                            if (width >= 1000) {
+                                columns = 5;
+                            } else if (width < 1000 && width >= 450) {
+                                columns = 3;
+                            } else if (width < 450 && width >= 300) {
+                                columns = 2;
+                            } else {
+                                columns = 1;
                             }
-                            return (<ListItem style={style}>
-                                {arr.map(i => getThumbnail(data, i))}
-                            </ListItem>);
+                            return (
+                                <List
+                                    itemSize={200}
+                                    height={height}
+                                    width={width}
+                                    itemCount={Math.ceil(filteredData.length / columns)}
+                                >
+                                    {({ index, style }) => {
+                                        const arr = [];
+                                        for (let i = 0; i < columns; i++) {
+                                            arr.push(index * columns + i);
+                                        }
+                                        return (<ListItem style={style}>
+                                            <ListContainer>
+                                                {arr.map(i => filteredData[i] && getThumbnail(filteredData, i))}
+                                            </ListContainer>
+                                        </ListItem>);
+                                    }}
+                                </List>
+                            )
                         }}
-                    </List>
-                )
-            }}
-        </AutoSizer>
-        <PhotoSwipe
-            isOpen={open}
-            items={data}
-            options={options}
-            onClose={handleClose}
-            gettingData={getSlideData}
-        />
-    </Container>);
+                    </AutoSizer>
+                    <PhotoSwipe
+                        isOpen={open}
+                        items={filteredData}
+                        options={options}
+                        onClose={handleClose}
+                        gettingData={getSlideData}
+                    />
+                </Container>
+                : <DeadCenter>
+                    <SadFace height={100} width={100} />
+                    <div>No content found!</div>
+                </DeadCenter>
+        }
+    </>);
 }

+ 9 - 3
src/services/fileService.ts

@@ -54,6 +54,8 @@ export interface file {
     html: string;
     w: number;
     h: number;
+    isDeleted: boolean;
+    dataIndex: number;
 };
 
 const getCollectionKey = async (collection: collection, key: Uint8Array) => {
@@ -93,11 +95,14 @@ const getCollections = async (token: string, sinceTime: string, key: Uint8Array)
     return await Promise.all(promises);
 }
 
-export const getFiles = async (sinceTime: string, token: string, limit: string, key: string) => {
+export const fetchCollections = async (token: string, key: string) => {
     const worker = await new CryptoWorker();
+    return getCollections(token, "0", await worker.fromB64(key));
+}
 
-    const collections = await getCollections(token, "0", await worker.fromB64(key));
-    var files: Array<file> = await localForage.getItem<file[]>('files') || [];
+export const getFiles = async (sinceTime: string, token: string, limit: string, key: string, collections: collection[]) => {
+    const worker = await new CryptoWorker();
+    let files: Array<file> = await localForage.getItem<file[]>('files') || [];
     for (const index in collections) {
         const collection = collections[index];
         if (collection.isDeleted) {
@@ -127,6 +132,7 @@ export const getFiles = async (sinceTime: string, token: string, limit: string,
         } while (resp.data.diff.length);
         await localForage.setItem(`${collection.id}-time`, time);
     }
+    files = files.filter(item => !item.isDeleted)
     await localForage.setItem('files', files);
     return files;
 }