import "./Overview.scss"; import * as React from "react"; import { connect } from "react-redux"; import { Dispatch } from "redux"; import { IAppState } from "~/src/redux/reducers"; import { photosDeleteCancel, photosDeleteStart, photosLoadStart, } from "../redux/photos/actions"; import { TPhotoReqJSON } from "~/src/shared/types"; import { PhotoCard } from "./PhotoCard"; import { Alignment, Button, Classes, H2, H3, Navbar, Overlay, Spinner, } from "@blueprintjs/core"; import { UploadButton } from "./UploadButton"; import { Photo } from "./Photo"; import { showDeletionToast } from "~/src/AppToaster"; export interface IOverviewComponentProps { photos: TPhotoReqJSON[]; triedLoading: boolean; allPhotosLoaded: boolean; overviewFetching: boolean; overviewFetchingError: string | null; overviewFetchingSpinner: boolean; darkMode: boolean; fetchPhotos: () => void; startDeletePhotos: (photos: TPhotoReqJSON[]) => void; cancelDelete: (photos: TPhotoReqJSON[]) => void; } const PhotoCardM = React.memo(PhotoCard); export const OverviewComponent: React.FunctionComponent< IOverviewComponentProps > = (props) => { const [selectedPhoto, setSelectedPhoto] = React.useState(0); const [isOverlayOpened, setOverlayOpen] = React.useState(false); const [selectedPhotos, setSelectedPhotos] = React.useState>( new Set(), ); const selectedPhotosRef = React.useRef>(selectedPhotos); selectedPhotosRef.current = selectedPhotos; const onCardClick = (e: React.MouseEvent, id: number) => { if (e.ctrlKey || e.metaKey) { const newSelectedPhotos = new Set([ ...selectedPhotosRef.current, ]); if (newSelectedPhotos.has(id)) { newSelectedPhotos.delete(id); } else { newSelectedPhotos.add(id); } setSelectedPhotos(newSelectedPhotos); } else { setSelectedPhoto(id); setOverlayOpen(true); } }; if ( props.photos.length === 0 && !props.triedLoading && !props.overviewFetching ) { props.fetchPhotos(); } const dates = props.photos.reduce( ( acc: Record< string, Record> >, photo, ) => { const date = new Date(photo.shotAt); const year = date.toLocaleString("default", { year: "numeric" }); const month = date.toLocaleString("default", { month: "long" }); const day = date.toLocaleString("default", { day: "numeric" }); acc[year] = acc[year] || {}; acc[year][month] = acc[year][month] || {}; acc[year][month][day] = acc[year][month][day] || []; acc[year][month][day].push(photo); return acc; }, {}, ); const onClick = React.useCallback((e: React.MouseEvent) => { onCardClick(e, Number(e.currentTarget.id)); }, []); const photos = Object.keys(dates).reduce( (acc: JSX.Element[], year): JSX.Element[] => { const els = Object.keys(dates[year]).reduce( (accMonths: JSX.Element[], month): JSX.Element[] => { const photos = Object.values(dates[year][month]).reduce( (accDays: TPhotoReqJSON[], day) => { return [...day, ...accDays]; }, [], ); const photosEls = photos.map((photo) => { return ( ); }); return [ ...accMonths,

{month}

{photosEls}
, ]; }, [], ); return [

{year}

, ...els, ...acc, ]; }, [], ); function onLoaderScroll(e: React.UIEvent) { if ( e.currentTarget.scrollTop + e.currentTarget.clientHeight >= e.currentTarget.scrollHeight - 100 ) { if (!props.allPhotosLoaded && !props.overviewFetching) { props.fetchPhotos(); } } } return ( <> { setOverlayOpen(false); }} transitionDuration={300} lazy >
{ setOverlayOpen(false); }} />
0} transitionDuration={300} hasBackdrop={false} >
{photos}
{props.overviewFetching && }
); }; function mapStateToProps(state: IAppState) { return { photos: state.photos.photos, allPhotosLoaded: state.photos.allPhotosLoaded, overviewFetching: state.photos.overviewFetching, overviewFetchingError: state.photos.overviewFetchingError, overviewFetchingSpinner: state.photos.overviewFetchingSpinner, triedLoading: state.photos.triedLoading, darkMode: state.localSettings.darkMode, }; } function mapDispatchToProps(dispatch: Dispatch) { return { fetchPhotos: () => dispatch(photosLoadStart()), startDeletePhotos: (photos: TPhotoReqJSON[]) => dispatch(photosDeleteStart(photos)), cancelDelete: (photos: TPhotoReqJSON[]) => dispatch(photosDeleteCancel(photos)), }; } export const Overview = connect( mapStateToProps, mapDispatchToProps, )(OverviewComponent);