Collection handling.

This commit is contained in:
Pushkar Anand 2020-11-28 23:41:24 +05:30
parent 8f27d7636f
commit ba24e8ee46
5 changed files with 217 additions and 58 deletions

View file

@ -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',
}

View file

@ -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', () => {

View file

@ -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>;
}

View file

@ -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);
}
return (<ListItem style={style}>
{arr.map(i => getThumbnail(data, i))}
</ListItem>);
}}
</List>
)
}}
</AutoSizer>
<PhotoSwipe
isOpen={open}
items={data}
options={options}
onClose={handleClose}
gettingData={getSlideData}
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}
/>
</Container>);
{
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 (
<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>
)
}}
</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>
}
</>);
}

View file

@ -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;
}