refactoring and style updates
This commit is contained in:
parent
9ca78c9bc2
commit
40777d109a
|
@ -55,10 +55,8 @@ export const Value = styled.div<{ width?: string }>`
|
|||
`;
|
||||
|
||||
export const FlexWrapper = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
`;
|
||||
|
||||
export const FreeFlowText = styled.div`
|
||||
|
@ -66,3 +64,9 @@ export const FreeFlowText = styled.div`
|
|||
min-width: 30%;
|
||||
text-align: left;
|
||||
`;
|
||||
|
||||
export const TwoScreenSpacedOptions = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
`;
|
||||
|
|
37
src/components/collection/CollectionCard.tsx
Normal file
37
src/components/collection/CollectionCard.tsx
Normal file
|
@ -0,0 +1,37 @@
|
|||
import React from 'react';
|
||||
import { GalleryContext } from 'pages/gallery';
|
||||
import { useState, useContext, useEffect } from 'react';
|
||||
import downloadManager from 'services/downloadManager';
|
||||
import { EnteFile } from 'types/file';
|
||||
import { CollectionTile } from '.';
|
||||
|
||||
export default function CollectionCard(props: {
|
||||
children?: any;
|
||||
latestFile: EnteFile;
|
||||
onClick: () => void;
|
||||
customCollectionTile?: any;
|
||||
}) {
|
||||
const { latestFile: file, onClick, children, customCollectionTile } = props;
|
||||
|
||||
const [coverImageURL, setCoverImageURL] = useState(null);
|
||||
const galleryContext = useContext(GalleryContext);
|
||||
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]);
|
||||
const UsedCollectionTile = customCollectionTile ?? CollectionTile;
|
||||
return (
|
||||
<UsedCollectionTile coverImgURL={coverImageURL} onClick={onClick}>
|
||||
{children}
|
||||
</UsedCollectionTile>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import React from 'react';
|
||||
import { EnteFile } from 'types/file';
|
||||
import { CollectionTileWrapper, ActiveIndicator } from '.';
|
||||
import CollectionCard from './CollectionCard';
|
||||
|
||||
const CollectionCardWithActiveIndicator = React.forwardRef(
|
||||
(
|
||||
props: {
|
||||
children;
|
||||
active: boolean;
|
||||
latestFile: EnteFile;
|
||||
onClick: () => void;
|
||||
},
|
||||
ref: any
|
||||
) => {
|
||||
const { active, ...others } = props;
|
||||
|
||||
return (
|
||||
<CollectionTileWrapper ref={ref}>
|
||||
<CollectionCard {...others} />
|
||||
{active && <ActiveIndicator />}
|
||||
</CollectionTileWrapper>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default CollectionCardWithActiveIndicator;
|
|
@ -1,3 +1,4 @@
|
|||
import { TwoScreenSpacedOptions } from 'components/Container';
|
||||
import { IMAGE_CONTAINER_MAX_WIDTH } from 'constants/gallery';
|
||||
import styled from 'styled-components';
|
||||
|
||||
|
@ -11,10 +12,9 @@ export const CollectionBarWrapper = styled.div`
|
|||
border-bottom: 1px solid ${({ theme }) => theme.palette.grey.A400};
|
||||
`;
|
||||
|
||||
export const Header = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
export const TwoScreenSpacedOptionsWithBodyPadding = styled(
|
||||
TwoScreenSpacedOptions
|
||||
)`
|
||||
margin-bottom: 8px;
|
||||
margin-top: 16px;
|
||||
padding: 0 24px;
|
||||
|
@ -39,7 +39,9 @@ export const ScrollContainer = styled.div`
|
|||
display: flex;
|
||||
`;
|
||||
|
||||
export const EmptyCollectionTile = styled.div`
|
||||
export const CollectionTile = styled.div<{
|
||||
coverImgURL?: string;
|
||||
}>`
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
width: 80px;
|
||||
|
@ -47,15 +49,9 @@ export const EmptyCollectionTile = styled.div`
|
|||
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;
|
||||
`;
|
||||
|
||||
export const CollectionTile = styled(EmptyCollectionTile)<{
|
||||
coverImgURL: string;
|
||||
}>`
|
||||
background-image: url(${({ coverImgURL }) => coverImgURL});
|
||||
background-size: cover;
|
||||
border: none;
|
||||
|
|
|
@ -8,7 +8,10 @@ export default function SortIcon(props) {
|
|||
viewBox={props.viewBox}
|
||||
width={props.width}>
|
||||
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||
<path d="M3 18h6v-2H3v2zM3 6v2h18V6H3zm0 7h12v-2H3v2z" />
|
||||
<path
|
||||
d="M3 18h6v-2H3v2zM3 6v2h18V6H3zm0 7h12v-2H3v2z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
149
src/components/pages/gallery/CollectionBar.tsx
Normal file
149
src/components/pages/gallery/CollectionBar.tsx
Normal file
|
@ -0,0 +1,149 @@
|
|||
import NavigationButton, {
|
||||
SCROLL_DIRECTION,
|
||||
} from 'components/NavigationButton';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Collection, CollectionSummaries } from 'types/collection';
|
||||
import constants from 'utils/strings/constants';
|
||||
import { ALL_SECTION } from 'constants/collection';
|
||||
import { Link, Typography } from '@mui/material';
|
||||
import {
|
||||
Hider,
|
||||
CollectionBarWrapper,
|
||||
CollectionWithNavigationContainer,
|
||||
ScrollContainer,
|
||||
TwoScreenSpacedOptionsWithBodyPadding,
|
||||
CollectionTile,
|
||||
} from 'components/collection';
|
||||
import CollectionCardWithActiveIndicator from 'components/collection/CollectionCardWithActiveIndicator';
|
||||
import styled from 'styled-components';
|
||||
|
||||
interface IProps {
|
||||
collections: Collection[];
|
||||
activeCollection?: number;
|
||||
setActiveCollection: (id?: number) => void;
|
||||
isInSearchMode: boolean;
|
||||
collectionSummaries: CollectionSummaries;
|
||||
showAllCollections: () => void;
|
||||
}
|
||||
|
||||
const CollectionTitleWithDashedBorder = styled(CollectionTile)`
|
||||
border: 1px dashed ${({ theme }) => theme.palette.grey.A200};
|
||||
`;
|
||||
const CreateNewCollectionTile = () => {
|
||||
return (
|
||||
<CollectionTitleWithDashedBorder>
|
||||
<div>{constants.NEW} </div>
|
||||
<div>{'+'}</div>
|
||||
</CollectionTitleWithDashedBorder>
|
||||
);
|
||||
};
|
||||
|
||||
export default function CollectionBar(props: IProps) {
|
||||
const {
|
||||
activeCollection,
|
||||
collections,
|
||||
setActiveCollection,
|
||||
collectionSummaries,
|
||||
showAllCollections,
|
||||
} = props;
|
||||
const collectionWrapperRef = useRef<HTMLDivElement>(null);
|
||||
const collectionChipsRef = props.collections.reduce(
|
||||
(refMap, collection) => {
|
||||
refMap[collection.id] = React.createRef();
|
||||
return refMap;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
const [scrollObj, setScrollObj] = useState<{
|
||||
scrollLeft?: number;
|
||||
scrollWidth?: number;
|
||||
clientWidth?: number;
|
||||
}>({});
|
||||
|
||||
const updateScrollObj = () => {
|
||||
if (collectionWrapperRef.current) {
|
||||
const { scrollLeft, scrollWidth, clientWidth } =
|
||||
collectionWrapperRef.current;
|
||||
setScrollObj({ scrollLeft, scrollWidth, clientWidth });
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
updateScrollObj();
|
||||
}, [collectionWrapperRef.current, props.isInSearchMode, collections]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!collectionWrapperRef?.current) {
|
||||
return;
|
||||
}
|
||||
collectionWrapperRef.current.scrollLeft = 0;
|
||||
}, [collections]);
|
||||
|
||||
useEffect(() => {
|
||||
collectionChipsRef[activeCollection]?.current.scrollIntoView({
|
||||
inline: 'center',
|
||||
});
|
||||
}, [activeCollection]);
|
||||
|
||||
const clickHandler = (collectionID?: number) => () => {
|
||||
setActiveCollection(collectionID ?? ALL_SECTION);
|
||||
};
|
||||
|
||||
const scrollCollection = (direction: SCROLL_DIRECTION) => () => {
|
||||
collectionWrapperRef.current.scrollBy(250 * direction, 0);
|
||||
};
|
||||
|
||||
return (
|
||||
<Hider hide={props.isInSearchMode}>
|
||||
<TwoScreenSpacedOptionsWithBodyPadding>
|
||||
<Typography>{constants.ALBUMS}</Typography>
|
||||
{scrollObj.scrollWidth > scrollObj.clientWidth && (
|
||||
<Link component="button" onClick={showAllCollections}>
|
||||
{constants.VIEW_ALL_ALBUMS}
|
||||
</Link>
|
||||
)}
|
||||
</TwoScreenSpacedOptionsWithBodyPadding>
|
||||
<CollectionBarWrapper>
|
||||
<CollectionWithNavigationContainer>
|
||||
{scrollObj.scrollLeft > 0 && (
|
||||
<NavigationButton
|
||||
scrollDirection={SCROLL_DIRECTION.LEFT}
|
||||
onClick={scrollCollection(SCROLL_DIRECTION.LEFT)}
|
||||
/>
|
||||
)}
|
||||
<ScrollContainer
|
||||
ref={collectionWrapperRef}
|
||||
onScroll={updateScrollObj}>
|
||||
<CollectionCardWithActiveIndicator
|
||||
latestFile={null}
|
||||
active={activeCollection === ALL_SECTION}
|
||||
onClick={clickHandler(ALL_SECTION)}>
|
||||
{constants.ALL_SECTION_NAME}
|
||||
</CollectionCardWithActiveIndicator>
|
||||
{collections.map((item) => (
|
||||
<CollectionCardWithActiveIndicator
|
||||
key={item.id}
|
||||
latestFile={
|
||||
collectionSummaries.get(item.id)?.latestFile
|
||||
}
|
||||
ref={collectionChipsRef[item.id]}
|
||||
active={activeCollection === item.id}
|
||||
onClick={clickHandler(item.id)}>
|
||||
{item.name}
|
||||
</CollectionCardWithActiveIndicator>
|
||||
))}
|
||||
<CreateNewCollectionTile />
|
||||
</ScrollContainer>
|
||||
{scrollObj.scrollLeft <
|
||||
scrollObj.scrollWidth - scrollObj.clientWidth && (
|
||||
<NavigationButton
|
||||
scrollDirection={SCROLL_DIRECTION.RIGHT}
|
||||
onClick={scrollCollection(SCROLL_DIRECTION.RIGHT)}
|
||||
/>
|
||||
)}
|
||||
</CollectionWithNavigationContainer>
|
||||
</CollectionBarWrapper>
|
||||
</Hider>
|
||||
);
|
||||
}
|
|
@ -1,196 +1,41 @@
|
|||
import NavigationButton, {
|
||||
SCROLL_DIRECTION,
|
||||
} from 'components/NavigationButton';
|
||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||
import { Collection, CollectionSummaries } from 'types/collection';
|
||||
import constants from 'utils/strings/constants';
|
||||
import { ALL_SECTION } from 'constants/collection';
|
||||
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 {
|
||||
import CollectionBar from './CollectionBar';
|
||||
import React, { useState } from 'react';
|
||||
import AllCollections from 'components/collection/AllCollections';
|
||||
interface Iprops {
|
||||
collections: Collection[];
|
||||
activeCollection?: number;
|
||||
setActiveCollection: (id?: number) => void;
|
||||
isInSearchMode: boolean;
|
||||
collectionSummaries: CollectionSummaries;
|
||||
}
|
||||
|
||||
const CollectionTileWithActiveIndicator = React.forwardRef(
|
||||
(
|
||||
props: {
|
||||
children;
|
||||
active: boolean;
|
||||
latestFile: EnteFile;
|
||||
onClick: () => void;
|
||||
},
|
||||
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 (
|
||||
<CollectionTileWrapper ref={ref}>
|
||||
<CollectionTile coverImgURL={coverImageURL} onClick={onClick}>
|
||||
{children}
|
||||
</CollectionTile>
|
||||
{active && <ActiveIndicator />}
|
||||
</CollectionTileWrapper>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const CreateNewCollectionHookTile = () => {
|
||||
return (
|
||||
<EmptyCollectionTile>
|
||||
<div>{constants.NEW} </div>
|
||||
<div>{'+'}</div>
|
||||
</EmptyCollectionTile>
|
||||
);
|
||||
};
|
||||
|
||||
export default function CollectionBar(props: CollectionProps) {
|
||||
export function Collections(props: Iprops) {
|
||||
const {
|
||||
activeCollection,
|
||||
collections,
|
||||
isInSearchMode,
|
||||
activeCollection,
|
||||
setActiveCollection,
|
||||
collectionSummaries,
|
||||
} = props;
|
||||
const collectionWrapperRef = useRef<HTMLDivElement>(null);
|
||||
const collectionChipsRef = props.collections.reduce(
|
||||
(refMap, collection) => {
|
||||
refMap[collection.id] = React.createRef();
|
||||
return refMap;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
const [scrollObj, setScrollObj] = useState<{
|
||||
scrollLeft?: number;
|
||||
scrollWidth?: number;
|
||||
clientWidth?: number;
|
||||
}>({});
|
||||
|
||||
const updateScrollObj = () => {
|
||||
if (collectionWrapperRef.current) {
|
||||
const { scrollLeft, scrollWidth, clientWidth } =
|
||||
collectionWrapperRef.current;
|
||||
setScrollObj({ scrollLeft, scrollWidth, clientWidth });
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
updateScrollObj();
|
||||
}, [collectionWrapperRef.current, props.isInSearchMode, collections]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!collectionWrapperRef?.current) {
|
||||
return;
|
||||
}
|
||||
collectionWrapperRef.current.scrollLeft = 0;
|
||||
}, [collections]);
|
||||
|
||||
useEffect(() => {
|
||||
collectionChipsRef[activeCollection]?.current.scrollIntoView({
|
||||
inline: 'center',
|
||||
});
|
||||
}, [activeCollection]);
|
||||
|
||||
const clickHandler = (collectionID?: number) => () => {
|
||||
setActiveCollection(collectionID ?? ALL_SECTION);
|
||||
};
|
||||
|
||||
const scrollCollection = (direction: SCROLL_DIRECTION) => () => {
|
||||
collectionWrapperRef.current.scrollBy(250 * direction, 0);
|
||||
};
|
||||
|
||||
const [allCollectionView, setAllCollectionView] = useState(false);
|
||||
return (
|
||||
<Hider hide={props.isInSearchMode}>
|
||||
<Header>
|
||||
<Typography>{constants.ALBUMS}</Typography>
|
||||
{scrollObj.scrollWidth > scrollObj.clientWidth && (
|
||||
<Link component="button">{constants.ALL_ALBUMS}</Link>
|
||||
)}
|
||||
</Header>
|
||||
<CollectionBarWrapper>
|
||||
<CollectionWithNavigationContainer>
|
||||
{scrollObj.scrollLeft > 0 && (
|
||||
<NavigationButton
|
||||
scrollDirection={SCROLL_DIRECTION.LEFT}
|
||||
onClick={scrollCollection(SCROLL_DIRECTION.LEFT)}
|
||||
/>
|
||||
)}
|
||||
<ScrollContainer
|
||||
ref={collectionWrapperRef}
|
||||
onScroll={updateScrollObj}>
|
||||
<CollectionTileWithActiveIndicator
|
||||
latestFile={null}
|
||||
active={activeCollection === ALL_SECTION}
|
||||
onClick={clickHandler(ALL_SECTION)}>
|
||||
{constants.ALL_SECTION_NAME}
|
||||
</CollectionTileWithActiveIndicator>
|
||||
{[
|
||||
...collections,
|
||||
...collections,
|
||||
...collections,
|
||||
...collections,
|
||||
...collections,
|
||||
...collections,
|
||||
...collections,
|
||||
...collections,
|
||||
...collections,
|
||||
...collections,
|
||||
...collections,
|
||||
...collections,
|
||||
].map((item) => (
|
||||
<CollectionTileWithActiveIndicator
|
||||
key={item.id}
|
||||
latestFile={
|
||||
collectionSummaries.get(item.id)?.latestFile
|
||||
}
|
||||
ref={collectionChipsRef[item.id]}
|
||||
active={activeCollection === item.id}
|
||||
onClick={clickHandler(item.id)}>
|
||||
{item.name}
|
||||
</CollectionTileWithActiveIndicator>
|
||||
))}
|
||||
<CreateNewCollectionHookTile />
|
||||
</ScrollContainer>
|
||||
{scrollObj.scrollLeft <
|
||||
scrollObj.scrollWidth - scrollObj.clientWidth && (
|
||||
<NavigationButton
|
||||
scrollDirection={SCROLL_DIRECTION.RIGHT}
|
||||
onClick={scrollCollection(SCROLL_DIRECTION.RIGHT)}
|
||||
/>
|
||||
)}
|
||||
</CollectionWithNavigationContainer>
|
||||
</CollectionBarWrapper>
|
||||
</Hider>
|
||||
<>
|
||||
<CollectionBar
|
||||
collections={collections}
|
||||
isInSearchMode={isInSearchMode}
|
||||
activeCollection={activeCollection}
|
||||
setActiveCollection={setActiveCollection}
|
||||
collectionSummaries={collectionSummaries}
|
||||
showAllCollections={() => setAllCollectionView(true)}
|
||||
/>
|
||||
|
||||
<AllCollections
|
||||
isOpen={allCollectionView}
|
||||
close={() => setAllCollectionView(false)}
|
||||
collectionSummaries={collectionSummaries}
|
||||
setActiveCollection={setActiveCollection}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
import React from 'react';
|
||||
import { Typography } from '@mui/material';
|
||||
import constants from 'utils/strings/constants';
|
||||
import { Header } from 'components/collection';
|
||||
import { IconButton } from 'components/Container';
|
||||
import OptionIcon from 'components/icons/OptionIcon-2';
|
||||
import { CollectionSummary } from 'types/collection';
|
||||
import { TwoScreenSpacedOptionsWithBodyPadding } from 'components/collection';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const InvertedIconButton = styled(IconButton)`
|
||||
background-color: ${({ theme }) => theme.palette.primary.main};
|
||||
color: ${({ theme }) => theme.palette.background.default};
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => theme.palette.grey.A100};
|
||||
}
|
||||
`;
|
||||
interface Iprops {
|
||||
collectionSummary: CollectionSummary;
|
||||
}
|
||||
|
@ -16,7 +24,7 @@ export default function collectionInfo(props: Iprops) {
|
|||
}
|
||||
|
||||
return (
|
||||
<Header>
|
||||
<TwoScreenSpacedOptionsWithBodyPadding>
|
||||
<div>
|
||||
<Typography variant="h5">
|
||||
<strong>{collectionSummary.collectionName}</strong>
|
||||
|
@ -25,9 +33,12 @@ export default function collectionInfo(props: Iprops) {
|
|||
{collectionSummary.fileCount} {constants.PHOTOS}
|
||||
</Typography>
|
||||
</div>
|
||||
<IconButton style={{ transform: 'rotate(90deg)' }}>
|
||||
<InvertedIconButton
|
||||
style={{
|
||||
transform: 'rotate(90deg)',
|
||||
}}>
|
||||
<OptionIcon />
|
||||
</IconButton>
|
||||
</Header>
|
||||
</InvertedIconButton>
|
||||
</TwoScreenSpacedOptionsWithBodyPadding>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -11,8 +11,9 @@ const darkThemeOptions = createTheme({
|
|||
primary: '#fff',
|
||||
secondary: '#777',
|
||||
},
|
||||
background: { default: '#191919', paper: '#202020' },
|
||||
background: { default: '#191919', paper: '#191919' },
|
||||
grey: {
|
||||
A100: '#ccc',
|
||||
A200: '#777',
|
||||
A400: '#4E4E4E',
|
||||
},
|
||||
|
|
|
@ -358,9 +358,7 @@ a:hover {
|
|||
text-decoration: none;
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
hr{
|
||||
border-top: 1rem solid #444 !important;
|
||||
}
|
||||
|
||||
.list-group-item:hover{
|
||||
background-color:#343434 !important;
|
||||
}
|
||||
|
|
|
@ -714,7 +714,8 @@ const englishConstants = {
|
|||
YES_STOP_UPLOADS: 'yes, stop uploads',
|
||||
ALBUMS: 'Albums',
|
||||
NEW: 'New',
|
||||
ALL_ALBUMS: 'View all Albums',
|
||||
VIEW_ALL_ALBUMS: 'View all Albums',
|
||||
ALL_ALBUMS: 'All Albums',
|
||||
PHOTOS: 'Photos',
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue