Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 162 additions & 0 deletions projects/bp-gallery/src/components/common/ScrollNavigationArrows.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import { FastForward, FastRewind } from '@mui/icons-material';
import { IconButton } from '@mui/material';
import { useContext, useEffect, useRef, useState } from 'react';
import { MobileContext } from '../provider/MobileProvider';

const ScrollNavigationArrows = ({
onClickLeft,
onClickRight,
onLongPressLeft,
onLongPressRight,
longPressTimeoutLeft = 1000,
longPressTimeoutRight = 1000,
isVisibleLeft = true,
isVisibleRight = true,
allowLongPressLeft = true,
allowLongPressRight = true,
showOnMobile = true,
}: {
onClickLeft: () => void;
onClickRight: () => void;
onLongPressLeft?: () => void;
onLongPressRight?: () => void;
longPressTimeoutLeft?: number;
longPressTimeoutRight?: number;
isVisibleLeft?: boolean;
isVisibleRight?: boolean;
allowLongPressLeft?: boolean;
allowLongPressRight?: boolean;
showOnMobile?: boolean;
}) => {
const { isMobile } = useContext(MobileContext);
const [isPressedLeft, setPressedLeft] = useState<boolean>(false);
const [isPressedRight, setPressedRight] = useState<boolean>(false);
const disallowClickLeft = useRef<boolean>(false);
const disallowClickRight = useRef<boolean>(false);

const leftPressTimeout = useRef<NodeJS.Timeout>();
const rightPressTimeout = useRef<NodeJS.Timeout>();

const onPressLeft = (e: any) => {
if (e?.type === 'click') {
if (!disallowClickLeft.current) {
onClickLeft();
} else {
disallowClickLeft.current = false;
}
} else {
if (!e) {
// e is undefined if this function is called via timeout and therefore is accessed via a long press instead of a click
disallowClickLeft.current = true;
}
if (disallowClickLeft.current) {
onLongPressLeft ? onLongPressLeft() : onClickLeft();
}
if (allowLongPressLeft) {
leftPressTimeout.current = setTimeout(onPressLeft, longPressTimeoutLeft);
}
}
};

const onPressRight = (e: any) => {
if (e?.type === 'click') {
if (!disallowClickRight.current) {
onClickRight();
} else {
disallowClickRight.current = false;
}
} else {
if (!e) {
// e is undefined if this function is called via timeout and therefore is accessed via a long press instead of a click
disallowClickRight.current = true;
}
if (disallowClickRight.current) {
onLongPressRight ? onLongPressRight() : onClickRight();
}
if (allowLongPressRight) {
rightPressTimeout.current = setTimeout(onPressRight, longPressTimeoutRight);
}
}
};

useEffect(() => {
if (!isPressedLeft) {
clearTimeout(leftPressTimeout.current);
}
}, [isPressedLeft]);

useEffect(() => {
if (!isPressedRight) {
clearTimeout(rightPressTimeout.current);
}
}, [isPressedRight]);

if (!isMobile || showOnMobile) {
return (
<div>
<div
className={`absolute top-0 left-0 h-full flex ${isVisibleLeft ? 'visible' : 'hidden'}`}
>
<IconButton
className='!bg-[#7e241d] !text-white !my-auto !shadow-xl !ml-1'
onClick={e => {
onPressLeft(e);
}}
onMouseDown={e => {
setPressedLeft(true);
onPressLeft(e);
}}
onTouchStart={e => {
setPressedLeft(true);
onPressLeft(e);
}}
onMouseUp={() => {
setPressedLeft(false);
}}
onMouseLeave={() => {
setPressedLeft(false);
}}
onTouchEnd={() => {
setPressedLeft(false);
}}
>
<FastRewind className='icon' />
</IconButton>
</div>
<div
className={`absolute top-0 right-0 h-full flex ${isVisibleRight ? 'visible' : 'hidden'}`}
>
<IconButton
className='!bg-[#7e241d] !text-white !my-auto !shadow-xl !mr-1'
onClick={e => {
onPressRight(e);
}}
onMouseDown={e => {
setPressedRight(true);
onPressRight(e);
}}
onTouchStart={e => {
setPressedRight(true);
onPressRight(e);
}}
onMouseUp={() => {
setPressedRight(false);
}}
onMouseLeave={() => {
setPressedRight(false);
}}
onTouchEnd={() => {
setPressedRight(false);
}}
>
<FastForward className='icon' />
</IconButton>
</div>
</div>
);
} else {
return <></>;
}
};

export default ScrollNavigationArrows;
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { pushHistoryWithoutRouter } from '../../../helpers/history';
import useGetPictures, { TextFilter } from '../../../hooks/get-pictures.hook';
import { FlatPicture, PictureOverviewType } from '../../../types/additionalFlatTypes';
import PictureView from '../../views/picture/PictureView';
import ScrollNavigationArrows from '../ScrollNavigationArrows';
import PicturePreview from './PicturePreview';
import { zoomIntoPicture, zoomOutOfPicture } from './helpers/picture-animations';

Expand Down Expand Up @@ -109,6 +110,8 @@ const HorizontalPictureGrid = ({

const [focusedPicture, setFocusedPicture] = useState<string | undefined>(undefined);
const [transitioning, setTransitioning] = useState<boolean>(false);
const [isVisibleLeft, setVisibleLeft] = useState<boolean>(true);
const [isVisibleRight, setVisibleRight] = useState<boolean>(true);

const selectedPicture = useRef<FlatPicture | undefined>();

Expand Down Expand Up @@ -249,7 +252,10 @@ const HorizontalPictureGrid = ({

const updateCurrentValue = useCallback(() => {
const field = Math.ceil((scrollBarRef.current?.scrollLeft ?? 0) / IMAGE_WIDGET_WIDTH);
const index = field * IMAGES_PER_WIDGET + ((leftPictures?.length ?? 0) % IMAGES_PER_WIDGET);
const index = Math.max(
0,
(field - 1) * IMAGES_PER_WIDGET + ((leftPictures?.length ?? 0) % IMAGES_PER_WIDGET)
);
selectedPicture.current =
pictures.length > index && index >= 0 ? pictures[index] : pictures[pictures.length - 1];
const year = new Date(selectedPicture.current.time_range_tag?.start as Date).getFullYear();
Expand All @@ -270,6 +276,13 @@ const HorizontalPictureGrid = ({
} else if (!allowDateUpdate.current) {
allowDateUpdate.current = true;
}

if (!scrollBarRef.current) return;
setVisibleLeft(scrollBarRef.current.scrollLeft > 0);
setVisibleRight(
scrollBarRef.current.scrollLeft <
scrollBarRef.current.scrollWidth - scrollBarRef.current.clientWidth
);
}, [leftPictures?.length, leftResult.loading, pictures, rightResult.loading, setDate]);

useEffect(() => {
Expand All @@ -287,9 +300,17 @@ const HorizontalPictureGrid = ({
pictureLength.current = leftPictures?.length ?? 0;
lastScrollPos.current = Math.max(newWidgetCount - oldWidgetCount, 1) * IMAGE_WIDGET_WIDTH;
scrollBarRef.current.scrollLeft =
Math.max(newWidgetCount - oldWidgetCount, 1) * IMAGE_WIDGET_WIDTH;
Math.max(newWidgetCount - oldWidgetCount, 0) * IMAGE_WIDGET_WIDTH;
}, [leftPictures, leftResult.loading]);

useEffect(() => {
if (!scrollBarRef.current) return;
setVisibleRight(
scrollBarRef.current.scrollLeft <
scrollBarRef.current.scrollWidth - scrollBarRef.current.clientWidth - 5 //offset
);
}, [rightResult.loading]);

useEffect(() => {
if (leftResult.loading || rightResult.loading) return;
const lowerBorder = pictures.length
Expand Down Expand Up @@ -349,6 +370,33 @@ const HorizontalPictureGrid = ({
>
{content}
</ScrollBar>
<ScrollNavigationArrows
onClickLeft={() => {
if (scrollBarRef.current) {
scrollBarRef.current.scrollLeft -= IMAGE_WIDGET_WIDTH;
}
}}
onClickRight={() => {
if (scrollBarRef.current) {
scrollBarRef.current.scrollLeft += IMAGE_WIDGET_WIDTH;
}
}}
onLongPressLeft={() => {
if (scrollBarRef.current) {
scrollBarRef.current.scrollLeft -= 100;
}
}}
onLongPressRight={() => {
if (scrollBarRef.current) {
scrollBarRef.current.scrollLeft += 100;
}
}}
longPressTimeoutLeft={200}
longPressTimeoutRight={200}
isVisibleLeft={isVisibleLeft}
isVisibleRight={isVisibleRight}
showOnMobile={false}
/>
</div>
{focusedPicture && !transitioning && (
<Portal container={root}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { ArrowDropDown } from '@mui/icons-material';
import { debounce } from 'lodash';
import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useRef } from 'react';
import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useAnimate } from '../../../hooks/animate.hook';
import ScrollNavigationArrows from '../ScrollNavigationArrows';

// for future work
export const enum TimeStepType {
Expand Down Expand Up @@ -34,6 +35,10 @@ const PictureTimeline = ({
0.06
);

const [drag, setDrag] = useState<boolean>(false);
const [dragged, setDragged] = useState<boolean>(false);
const lastPos = useRef<number | undefined>();

useEffect(() => {
if (scrollBarRef.current) {
scrollBarRef.current.scrollLeft = scrollLeft;
Expand Down Expand Up @@ -62,7 +67,11 @@ const PictureTimeline = ({
key={year}
className='inline cursor-pointer'
onClick={() => {
setDate(year);
if (dragged) {
setDragged(false);
} else {
setDate(year);
}
}}
style={{
padding: `40px ${
Expand All @@ -89,16 +98,58 @@ const PictureTimeline = ({
const updateOnScrollX = useMemo(() => debounce(updateDate, 500), [updateDate]);

return (
<div>
<div
onMouseDown={e => {
setDrag(true);
lastPos.current = e.nativeEvent.offsetX;
}}
onMouseUp={e => {
setDrag(false);
lastPos.current = undefined;
}}
onMouseMove={e => {
if (
scrollBarRef.current &&
drag &&
lastPos.current &&
e.nativeEvent.offsetY > 0 &&
e.nativeEvent.offsetY <= 80
) {
setDragged(true);
scrollBarRef.current.scrollLeft -= e.nativeEvent.offsetX - lastPos.current;
lastPos.current = e.nativeEvent.offsetX;
} else {
setDrag(false);
lastPos.current = undefined;
}
}}
>
<div className='flex'>
<ArrowDropDown className='mx-auto scale-[1.75]' />
</div>
<div className='relative'>
<div className='overflow-x-scroll' ref={scrollBarRef} onScroll={updateOnScrollX}>
<div className='flex'>
<ul className='py-[16px] px-[50%] mt-0 whitespace-nowrap'>{listItems}</ul>
<div className='relative mb-2'>
<div className='overflow-x-scroll z-0' ref={scrollBarRef} onScroll={updateOnScrollX}>
<div className='flex pb-2'>
<ul className='py-[16px] px-[50%] my-0 whitespace-nowrap'>{listItems}</ul>
</div>
</div>
<ScrollNavigationArrows
onClickLeft={() => {
if (scrollBarRef.current) {
scrollBarRef.current.scrollLeft -= singleElementWidth;
}
}}
onClickRight={() => {
if (scrollBarRef.current) {
scrollBarRef.current.scrollLeft += singleElementWidth;
}
}}
longPressTimeoutLeft={250}
longPressTimeoutRight={250}
isVisibleLeft={date > start}
isVisibleRight={date < end}
showOnMobile={false}
/>
</div>
</div>
);
Expand Down