Collection handling.
This commit is contained in:
parent
8f27d7636f
commit
ba24e8ee46
5 changed files with 217 additions and 58 deletions
23
src/components/SadFace.tsx
Normal file
23
src/components/SadFace.tsx
Normal 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',
|
||||
}
|
|
@ -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
src/pages/gallery/components/Collections.tsx
Normal file
64
src/pages/gallery/components/Collections.tsx
Normal 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>;
|
||||
}
|
|
@ -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>
|
||||
}
|
||||
</>);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue