add cover image logic
This commit is contained in:
parent
a09025ce63
commit
a61cebbde9
|
@ -1,127 +1,87 @@
|
||||||
import { SetDialogMessage } from 'components/MessageDialog';
|
|
||||||
import NavigationButton, {
|
import NavigationButton, {
|
||||||
SCROLL_DIRECTION,
|
SCROLL_DIRECTION,
|
||||||
} from 'components/NavigationButton';
|
} from 'components/NavigationButton';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||||
import styled from 'styled-components';
|
import { Collection, CollectionSummaries } from 'types/collection';
|
||||||
import { IMAGE_CONTAINER_MAX_WIDTH } from 'constants/gallery';
|
|
||||||
import { Collection, CollectionAndItsLatestFile } from 'types/collection';
|
|
||||||
import constants from 'utils/strings/constants';
|
import constants from 'utils/strings/constants';
|
||||||
import { SetCollectionNamerAttributes } from './CollectionNamer';
|
|
||||||
import { ALL_SECTION } from 'constants/collection';
|
import { ALL_SECTION } from 'constants/collection';
|
||||||
import { Link } from '@mui/material';
|
import { Link, Typography } from '@mui/material';
|
||||||
|
import {
|
||||||
|
CollectionTileWrapper,
|
||||||
|
CollectionTile,
|
||||||
|
ActiveIndicator,
|
||||||
|
Hider,
|
||||||
|
CollectionBarWrapper,
|
||||||
|
Header,
|
||||||
|
CollectionWithNavigationContainer,
|
||||||
|
ScrollContainer,
|
||||||
|
EmptyCollectionTile,
|
||||||
|
} from 'components/collection';
|
||||||
|
import { GalleryContext } from 'pages/gallery';
|
||||||
|
import downloadManager from 'services/downloadManager';
|
||||||
|
import { EnteFile } from 'types/file';
|
||||||
|
|
||||||
interface CollectionProps {
|
interface CollectionProps {
|
||||||
collections: Collection[];
|
collections: Collection[];
|
||||||
collectionAndTheirLatestFile: CollectionAndItsLatestFile[];
|
|
||||||
activeCollection?: number;
|
activeCollection?: number;
|
||||||
setActiveCollection: (id?: number) => void;
|
setActiveCollection: (id?: number) => void;
|
||||||
setDialogMessage: SetDialogMessage;
|
|
||||||
syncWithRemote: () => Promise<void>;
|
|
||||||
setCollectionNamerAttributes: SetCollectionNamerAttributes;
|
|
||||||
startLoading: () => void;
|
|
||||||
finishLoading: () => void;
|
|
||||||
isInSearchMode: boolean;
|
isInSearchMode: boolean;
|
||||||
collectionFilesCount: Map<number, number>;
|
collectionSummaries: CollectionSummaries;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SAMPLE_URL =
|
|
||||||
'https://images.unsplash.com/photo-1615789591457-74a63395c990?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MXx8YmFieSUyMGNhdHxlbnwwfHwwfHw%3D&w=1000&q=80';
|
|
||||||
|
|
||||||
const CollectionContainer = styled.div`
|
|
||||||
overflow: hidden;
|
|
||||||
height: 86px;
|
|
||||||
display: flex;
|
|
||||||
position: relative;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ScrollWrapper = styled.div`
|
|
||||||
width: calc(100%- 80px);
|
|
||||||
height: 100px;
|
|
||||||
overflow: auto;
|
|
||||||
max-width: 100%;
|
|
||||||
scroll-behavior: smooth;
|
|
||||||
display: flex;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const CollectionBar = styled.div`
|
|
||||||
width: 100%;
|
|
||||||
margin: 10px auto;
|
|
||||||
padding: 0 24px;
|
|
||||||
@media (max-width: ${IMAGE_CONTAINER_MAX_WIDTH * 4}px) {
|
|
||||||
padding: 0 4px;
|
|
||||||
}
|
|
||||||
border-bottom: 1px solid ${({ theme }) => theme.palette.grey.A400};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const EmptyCollectionTile = styled.div`
|
|
||||||
flex-shrink: 0;
|
|
||||||
display: flex;
|
|
||||||
width: 80px;
|
|
||||||
height: 64px;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 4px 6px;
|
|
||||||
align-items: flex-end;
|
|
||||||
border: 1px dashed ${({ theme }) => theme.palette.grey.A200};
|
|
||||||
justify-content: space-between;
|
|
||||||
user-select: none;
|
|
||||||
cursor: pointer;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const CollectionTile = styled(EmptyCollectionTile)<{ coverImgURL: string }>`
|
|
||||||
background-image: url(${({ coverImgURL }) => coverImgURL});
|
|
||||||
background-size: cover;
|
|
||||||
border: none;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const CollectionTileWrapper = styled.div`
|
|
||||||
margin-right: 6px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ActiveIndicator = styled.div`
|
|
||||||
height: 3px;
|
|
||||||
background-color: ${({ theme }) => theme.palette.text.primary};
|
|
||||||
margin-top: 18px;
|
|
||||||
border-radius: 2px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Hider = styled.div<{ hide: boolean }>`
|
|
||||||
opacity: ${(props) => (props.hide ? '0' : '100')};
|
|
||||||
height: ${(props) => (props.hide ? '0' : 'auto')};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Header = styled.div`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
`;
|
|
||||||
const CollectionTileWithActiveIndicator = React.forwardRef(
|
const CollectionTileWithActiveIndicator = React.forwardRef(
|
||||||
(
|
(
|
||||||
props: {
|
props: {
|
||||||
children;
|
children;
|
||||||
|
|
||||||
active: boolean;
|
active: boolean;
|
||||||
coverImgURL: string;
|
latestFile: EnteFile;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
},
|
},
|
||||||
ref: any
|
ref: any
|
||||||
) => {
|
) => {
|
||||||
|
const [coverImageURL, setCoverImageURL] = useState(null);
|
||||||
|
const galleryContext = useContext(GalleryContext);
|
||||||
|
const { latestFile: file, onClick, active, children } = props;
|
||||||
|
useEffect(() => {
|
||||||
|
const main = async () => {
|
||||||
|
if (!file) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!galleryContext.thumbs.has(file.id)) {
|
||||||
|
const url = await downloadManager.getThumbnail(file);
|
||||||
|
galleryContext.thumbs.set(file.id, url);
|
||||||
|
}
|
||||||
|
setCoverImageURL(galleryContext.thumbs.get(file.id));
|
||||||
|
};
|
||||||
|
main();
|
||||||
|
}, [file]);
|
||||||
return (
|
return (
|
||||||
<CollectionTileWrapper ref={ref}>
|
<CollectionTileWrapper ref={ref}>
|
||||||
<CollectionTile
|
<CollectionTile coverImgURL={coverImageURL} onClick={onClick}>
|
||||||
coverImgURL={props.coverImgURL}
|
{children}
|
||||||
onClick={props.onClick}>
|
|
||||||
{props.children}
|
|
||||||
</CollectionTile>
|
</CollectionTile>
|
||||||
{props.active && <ActiveIndicator />}
|
{active && <ActiveIndicator />}
|
||||||
</CollectionTileWrapper>
|
</CollectionTileWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export default function Collections(props: CollectionProps) {
|
const CreateNewCollectionHookTile = () => {
|
||||||
const { activeCollection, collections, setActiveCollection } = props;
|
return (
|
||||||
|
<EmptyCollectionTile>
|
||||||
|
<div>{constants.NEW} </div>
|
||||||
|
<div>{'+'}</div>
|
||||||
|
</EmptyCollectionTile>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function CollectionBar(props: CollectionProps) {
|
||||||
|
const {
|
||||||
|
activeCollection,
|
||||||
|
collections,
|
||||||
|
setActiveCollection,
|
||||||
|
collectionSummaries,
|
||||||
|
} = props;
|
||||||
const collectionWrapperRef = useRef<HTMLDivElement>(null);
|
const collectionWrapperRef = useRef<HTMLDivElement>(null);
|
||||||
const collectionChipsRef = props.collections.reduce(
|
const collectionChipsRef = props.collections.reduce(
|
||||||
(refMap, collection) => {
|
(refMap, collection) => {
|
||||||
|
@ -172,25 +132,25 @@ export default function Collections(props: CollectionProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Hider hide={props.isInSearchMode}>
|
<Hider hide={props.isInSearchMode}>
|
||||||
<CollectionBar>
|
<CollectionBarWrapper>
|
||||||
<Header>
|
<Header>
|
||||||
{constants.ALBUMS}
|
<Typography>{constants.ALBUMS}</Typography>
|
||||||
{scrollObj.scrollWidth > scrollObj.clientWidth && (
|
{scrollObj.scrollWidth > scrollObj.clientWidth && (
|
||||||
<Link component="button">{constants.ALL_ALBUMS}</Link>
|
<Link component="button">{constants.ALL_ALBUMS}</Link>
|
||||||
)}
|
)}
|
||||||
</Header>
|
</Header>
|
||||||
<CollectionContainer>
|
<CollectionWithNavigationContainer>
|
||||||
{/* {scrollObj.scrollLeft > 0 && ( */}
|
{scrollObj.scrollLeft > 0 && (
|
||||||
<NavigationButton
|
<NavigationButton
|
||||||
scrollDirection={SCROLL_DIRECTION.LEFT}
|
scrollDirection={SCROLL_DIRECTION.LEFT}
|
||||||
onClick={scrollCollection(SCROLL_DIRECTION.LEFT)}
|
onClick={scrollCollection(SCROLL_DIRECTION.LEFT)}
|
||||||
/>
|
/>
|
||||||
{/* )} */}
|
)}
|
||||||
<ScrollWrapper
|
<ScrollContainer
|
||||||
ref={collectionWrapperRef}
|
ref={collectionWrapperRef}
|
||||||
onScroll={updateScrollObj}>
|
onScroll={updateScrollObj}>
|
||||||
<CollectionTileWithActiveIndicator
|
<CollectionTileWithActiveIndicator
|
||||||
coverImgURL={SAMPLE_URL}
|
latestFile={null}
|
||||||
active={activeCollection === ALL_SECTION}
|
active={activeCollection === ALL_SECTION}
|
||||||
onClick={clickHandler(ALL_SECTION)}>
|
onClick={clickHandler(ALL_SECTION)}>
|
||||||
{constants.ALL_SECTION_NAME}
|
{constants.ALL_SECTION_NAME}
|
||||||
|
@ -211,27 +171,27 @@ export default function Collections(props: CollectionProps) {
|
||||||
].map((item) => (
|
].map((item) => (
|
||||||
<CollectionTileWithActiveIndicator
|
<CollectionTileWithActiveIndicator
|
||||||
key={item.id}
|
key={item.id}
|
||||||
|
latestFile={
|
||||||
|
collectionSummaries?.get(item.id)
|
||||||
|
?.latestFile
|
||||||
|
}
|
||||||
ref={collectionChipsRef[item.id]}
|
ref={collectionChipsRef[item.id]}
|
||||||
active={activeCollection === item.id}
|
active={activeCollection === item.id}
|
||||||
onClick={clickHandler(item.id)}
|
onClick={clickHandler(item.id)}>
|
||||||
coverImgURL={SAMPLE_URL}>
|
|
||||||
{item.name}
|
{item.name}
|
||||||
</CollectionTileWithActiveIndicator>
|
</CollectionTileWithActiveIndicator>
|
||||||
))}
|
))}
|
||||||
<EmptyCollectionTile>
|
<CreateNewCollectionHookTile />
|
||||||
<div>{constants.NEW} </div>
|
</ScrollContainer>
|
||||||
<div>{'+'}</div>
|
{scrollObj.scrollLeft <
|
||||||
</EmptyCollectionTile>
|
scrollObj.scrollWidth - scrollObj.clientWidth && (
|
||||||
</ScrollWrapper>
|
<NavigationButton
|
||||||
{/* {scrollObj.scrollLeft < */}
|
scrollDirection={SCROLL_DIRECTION.RIGHT}
|
||||||
{/* scrollObj.scrollWidth - scrollObj.clientWidth && ( */}
|
onClick={scrollCollection(SCROLL_DIRECTION.RIGHT)}
|
||||||
<NavigationButton
|
/>
|
||||||
scrollDirection={SCROLL_DIRECTION.RIGHT}
|
)}
|
||||||
onClick={scrollCollection(SCROLL_DIRECTION.RIGHT)}
|
</CollectionWithNavigationContainer>
|
||||||
/>
|
</CollectionBarWrapper>
|
||||||
{/* )} */}
|
|
||||||
</CollectionContainer>
|
|
||||||
</CollectionBar>
|
|
||||||
</Hider>
|
</Hider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import {
|
||||||
getLocalCollections,
|
getLocalCollections,
|
||||||
getNonEmptyCollections,
|
getNonEmptyCollections,
|
||||||
createCollection,
|
createCollection,
|
||||||
|
getCollectionSummaries,
|
||||||
} from 'services/collectionService';
|
} from 'services/collectionService';
|
||||||
import constants from 'utils/strings/constants';
|
import constants from 'utils/strings/constants';
|
||||||
import billingService from 'services/billingService';
|
import billingService from 'services/billingService';
|
||||||
|
@ -48,7 +49,6 @@ import {
|
||||||
getSelectedFiles,
|
getSelectedFiles,
|
||||||
mergeMetadata,
|
mergeMetadata,
|
||||||
sortFiles,
|
sortFiles,
|
||||||
sortFilesIntoCollections,
|
|
||||||
} from 'utils/file';
|
} from 'utils/file';
|
||||||
import SearchBar from 'components/Search';
|
import SearchBar from 'components/Search';
|
||||||
import SelectedFileOptions from 'components/pages/gallery/SelectedFileOptions/GalleryOptions';
|
import SelectedFileOptions from 'components/pages/gallery/SelectedFileOptions/GalleryOptions';
|
||||||
|
@ -93,7 +93,11 @@ import DeleteBtn from 'components/DeleteBtn';
|
||||||
import FixCreationTime, {
|
import FixCreationTime, {
|
||||||
FixCreationTimeAttributes,
|
FixCreationTimeAttributes,
|
||||||
} from 'components/FixCreationTime';
|
} from 'components/FixCreationTime';
|
||||||
import { Collection, CollectionAndItsLatestFile } from 'types/collection';
|
import {
|
||||||
|
Collection,
|
||||||
|
CollectionAndItsLatestFile,
|
||||||
|
CollectionSummaries,
|
||||||
|
} from 'types/collection';
|
||||||
import { EnteFile } from 'types/file';
|
import { EnteFile } from 'types/file';
|
||||||
import {
|
import {
|
||||||
GalleryContextType,
|
GalleryContextType,
|
||||||
|
@ -101,7 +105,7 @@ import {
|
||||||
Search,
|
Search,
|
||||||
NotificationAttributes,
|
NotificationAttributes,
|
||||||
} from 'types/gallery';
|
} from 'types/gallery';
|
||||||
import Collections from 'components/pages/gallery/Collections';
|
import CollectionBar from 'components/pages/gallery/Collections';
|
||||||
import { VISIBILITY_STATE } from 'types/magicMetadata';
|
import { VISIBILITY_STATE } from 'types/magicMetadata';
|
||||||
import ToastNotification from 'components/ToastNotification';
|
import ToastNotification from 'components/ToastNotification';
|
||||||
import { ElectronFile } from 'types/upload';
|
import { ElectronFile } from 'types/upload';
|
||||||
|
@ -185,8 +189,8 @@ export default function Gallery() {
|
||||||
const [deleted, setDeleted] = useState<number[]>([]);
|
const [deleted, setDeleted] = useState<number[]>([]);
|
||||||
const { startLoading, finishLoading, setDialogMessage, ...appContext } =
|
const { startLoading, finishLoading, setDialogMessage, ...appContext } =
|
||||||
useContext(AppContext);
|
useContext(AppContext);
|
||||||
const [collectionFilesCount, setCollectionFilesCount] =
|
const [collectionSummaries, setCollectionSummaries] =
|
||||||
useState<Map<number, number>>();
|
useState<CollectionSummaries>();
|
||||||
const [activeCollection, setActiveCollection] = useState<number>(undefined);
|
const [activeCollection, setActiveCollection] = useState<number>(undefined);
|
||||||
const [trash, setTrash] = useState<Trash>([]);
|
const [trash, setTrash] = useState<Trash>([]);
|
||||||
const [fixCreationTimeView, setFixCreationTimeView] = useState(false);
|
const [fixCreationTimeView, setFixCreationTimeView] = useState(false);
|
||||||
|
@ -346,12 +350,9 @@ export default function Gallery() {
|
||||||
files
|
files
|
||||||
);
|
);
|
||||||
setCollectionsAndTheirLatestFile(collectionsAndTheirLatestFile);
|
setCollectionsAndTheirLatestFile(collectionsAndTheirLatestFile);
|
||||||
const collectionWiseFiles = sortFilesIntoCollections(files);
|
const collectionSummaries = getCollectionSummaries(collections, files);
|
||||||
const collectionFilesCount = new Map<number, number>();
|
|
||||||
for (const [id, files] of collectionWiseFiles) {
|
setCollectionSummaries(collectionSummaries);
|
||||||
collectionFilesCount.set(id, files.length);
|
|
||||||
}
|
|
||||||
setCollectionFilesCount(collectionFilesCount);
|
|
||||||
|
|
||||||
const archivedCollections = getArchivedCollections(collections);
|
const archivedCollections = getArchivedCollections(collections);
|
||||||
setArchivedCollections(new Set(archivedCollections));
|
setArchivedCollections(new Set(archivedCollections));
|
||||||
|
@ -605,18 +606,12 @@ export default function Gallery() {
|
||||||
setSearch={updateSearch}
|
setSearch={updateSearch}
|
||||||
searchStats={searchStats}
|
searchStats={searchStats}
|
||||||
/>
|
/>
|
||||||
<Collections
|
<CollectionBar
|
||||||
collections={collections}
|
collections={collections}
|
||||||
collectionAndTheirLatestFile={collectionsAndTheirLatestFile}
|
|
||||||
isInSearchMode={isInSearchMode}
|
isInSearchMode={isInSearchMode}
|
||||||
activeCollection={activeCollection}
|
activeCollection={activeCollection}
|
||||||
setActiveCollection={setActiveCollection}
|
setActiveCollection={setActiveCollection}
|
||||||
syncWithRemote={syncWithRemote}
|
collectionSummaries={collectionSummaries}
|
||||||
setDialogMessage={setDialogMessage}
|
|
||||||
setCollectionNamerAttributes={setCollectionNamerAttributes}
|
|
||||||
startLoading={startLoading}
|
|
||||||
finishLoading={finishLoading}
|
|
||||||
collectionFilesCount={collectionFilesCount}
|
|
||||||
/>
|
/>
|
||||||
<CollectionNamer
|
<CollectionNamer
|
||||||
show={collectionNamerView}
|
show={collectionNamerView}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import HTTPService from './HTTPService';
|
||||||
import { EnteFile } from 'types/file';
|
import { EnteFile } from 'types/file';
|
||||||
import { logError } from 'utils/sentry';
|
import { logError } from 'utils/sentry';
|
||||||
import { CustomError } from 'utils/error';
|
import { CustomError } from 'utils/error';
|
||||||
import { sortFiles } from 'utils/file';
|
import { sortFiles, sortFilesIntoCollections } from 'utils/file';
|
||||||
import {
|
import {
|
||||||
Collection,
|
Collection,
|
||||||
CollectionAndItsLatestFile,
|
CollectionAndItsLatestFile,
|
||||||
|
@ -23,6 +23,7 @@ import {
|
||||||
CreatePublicAccessTokenRequest,
|
CreatePublicAccessTokenRequest,
|
||||||
PublicURL,
|
PublicURL,
|
||||||
UpdatePublicURL,
|
UpdatePublicURL,
|
||||||
|
CollectionSummaries,
|
||||||
} from 'types/collection';
|
} from 'types/collection';
|
||||||
import { COLLECTION_SORT_BY, CollectionType } from 'constants/collection';
|
import { COLLECTION_SORT_BY, CollectionType } from 'constants/collection';
|
||||||
import { UpdateMagicMetadataRequest } from 'types/magicMetadata';
|
import { UpdateMagicMetadataRequest } from 'types/magicMetadata';
|
||||||
|
@ -795,3 +796,41 @@ function moveFavCollectionToFront(collections: Collection[]) {
|
||||||
: 0
|
: 0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCollectionSummaries(
|
||||||
|
collections: Collection[],
|
||||||
|
files: EnteFile[]
|
||||||
|
): CollectionSummaries {
|
||||||
|
const CollectionSummaries: CollectionSummaries = new Map();
|
||||||
|
const collectionAndTheirLatestFile = getCollectionsAndTheirLatestFile(
|
||||||
|
collections,
|
||||||
|
files
|
||||||
|
);
|
||||||
|
const collectionAndTheirLatestFileMap = new Map();
|
||||||
|
for (const collectionAndItsLatestFile of collectionAndTheirLatestFile) {
|
||||||
|
collectionAndTheirLatestFileMap.set(
|
||||||
|
collectionAndItsLatestFile.collection.id,
|
||||||
|
collectionAndItsLatestFile.file
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const collectionFilesCount = getCollectionsFileCount(files);
|
||||||
|
|
||||||
|
for (const collection of collections) {
|
||||||
|
CollectionSummaries.set(collection.id, {
|
||||||
|
collectionName: collection.name,
|
||||||
|
latestFile: collectionAndTheirLatestFileMap.get(collection.id),
|
||||||
|
fileCount: collectionFilesCount.get(collection.id),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return CollectionSummaries;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCollectionsFileCount(files: EnteFile[]) {
|
||||||
|
const collectionWiseFiles = sortFilesIntoCollections(files);
|
||||||
|
const collectionFilesCount = new Map<number, number>();
|
||||||
|
for (const [id, files] of collectionWiseFiles) {
|
||||||
|
collectionFilesCount.set(id, files.length);
|
||||||
|
}
|
||||||
|
return collectionFilesCount;
|
||||||
|
}
|
||||||
|
|
|
@ -91,3 +91,10 @@ export interface CollectionMagicMetadata
|
||||||
extends Omit<MagicMetadataCore, 'data'> {
|
extends Omit<MagicMetadataCore, 'data'> {
|
||||||
data: CollectionMagicMetadataProps;
|
data: CollectionMagicMetadataProps;
|
||||||
}
|
}
|
||||||
|
export interface CollectionSummary {
|
||||||
|
collectionName: string;
|
||||||
|
latestFile: EnteFile;
|
||||||
|
fileCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CollectionSummaries = Map<number, CollectionSummary>;
|
||||||
|
|
Loading…
Reference in a new issue