diff --git a/src/app/hooks/useInfinityScroll.ts b/src/app/hooks/useInfinityScroll.ts index 5134b03cf..72d68170a 100644 --- a/src/app/hooks/useInfinityScroll.ts +++ b/src/app/hooks/useInfinityScroll.ts @@ -8,8 +8,8 @@ export const useInfinityScroll = ( //handle infinity scroll with IntersectionObserver api const observer = new IntersectionObserver( - (entries) => { - if (entries[0].isIntersecting) { + ([entry]) => { + if (entry.isIntersecting) { setIsObserved(true); } else { setIsObserved(false); diff --git a/src/app/hooks/useSearchMediaSources.tsx b/src/app/hooks/useSearchMediaSources.tsx index ef3518e81..3e01d0d3f 100644 --- a/src/app/hooks/useSearchMediaSources.tsx +++ b/src/app/hooks/useSearchMediaSources.tsx @@ -133,6 +133,12 @@ export function useSearchMediaSources(source: string, elementType: string) { }); }; + const reset = () => { + setData([]); + setPage(1); + setPageToken(""); + }; + const getUnsplashImages = async (q: string, nextPage: boolean) => { await axios .get( @@ -178,7 +184,7 @@ export function useSearchMediaSources(source: string, elementType: string) { const search = async (q: string, nextPage: boolean = false) => { if ((elementType === "video" || elementType === "image") && token) { if (!nextPage) { - setData([]); + reset(); } setLoading(true); const sources = sourceGetter[elementType as keyof typeof sourceGetter]; diff --git a/src/app/modules/report-module/__test__/reportRightPanelCreateView.test.tsx b/src/app/modules/report-module/__test__/reportRightPanelCreateView.test.tsx index 05e37c59c..ea45991c8 100644 --- a/src/app/modules/report-module/__test__/reportRightPanelCreateView.test.tsx +++ b/src/app/modules/report-module/__test__/reportRightPanelCreateView.test.tsx @@ -41,6 +41,8 @@ import { import { mockChartList } from "app/modules/report-module/__test__/data"; import axios, { AxiosResponse } from "axios"; import { ChartsAppliedFiltersState } from "app/state/api/action-reducers/sync/charts/filters"; +import { setupIntersectionObserverMock } from "./setupIntersectionObserver"; +setupIntersectionObserverMock(); interface MockProps { showHeaderItem: boolean; diff --git a/src/app/modules/report-module/__test__/setupIntersectionObserver.ts b/src/app/modules/report-module/__test__/setupIntersectionObserver.ts new file mode 100644 index 000000000..2086ca65d --- /dev/null +++ b/src/app/modules/report-module/__test__/setupIntersectionObserver.ts @@ -0,0 +1,38 @@ +/** + * Utility function that mocks the `IntersectionObserver` API. Necessary for components that rely + * on it, otherwise the tests will crash. Recommended to execute inside `beforeEach`. + * @param intersectionObserverMock - Parameter that is sent to the `Object.defineProperty` + * overwrite method. `jest.fn()` mock functions can be passed here if the goal is to not only + * mock the intersection observer, but its methods. + */ +export function setupIntersectionObserverMock({ + root = null, + rootMargin = "", + thresholds = [], + disconnect = () => null, + observe = () => null, + takeRecords = () => [], + unobserve = () => null, +} = {}): void { + class MockIntersectionObserver implements IntersectionObserver { + readonly root: Element | null = root; + readonly rootMargin: string = rootMargin; + readonly thresholds: ReadonlyArray = thresholds; + disconnect: () => void = disconnect; + observe: (target: Element) => void = observe; + takeRecords: () => IntersectionObserverEntry[] = takeRecords; + unobserve: (target: Element) => void = unobserve; + } + + Object.defineProperty(window, "IntersectionObserver", { + writable: true, + configurable: true, + value: MockIntersectionObserver, + }); + + Object.defineProperty(global, "IntersectionObserver", { + writable: true, + configurable: true, + value: MockIntersectionObserver, + }); +} diff --git a/src/app/modules/report-module/components/right-panel-create-view/index.tsx b/src/app/modules/report-module/components/right-panel-create-view/index.tsx index e787eb9b8..f1af6bb0a 100644 --- a/src/app/modules/report-module/components/right-panel-create-view/index.tsx +++ b/src/app/modules/report-module/components/right-panel-create-view/index.tsx @@ -58,6 +58,7 @@ import { get } from "lodash"; import { useSearchMediaSources } from "app/hooks/useSearchMediaSources"; import { useDebounce } from "react-use"; import Skeleton from "@material-ui/lab/Skeleton"; +import { useInfinityScroll } from "app/hooks/useInfinityScroll"; interface IHeaderDetails { title: string; @@ -177,14 +178,14 @@ const videoSources = [ const imageSources = [ { value: "unsplash", label: "Unsplash" }, - { value: "shutterstock", label: "Shutterstock" }, + // { value: "shutterstock", label: "Shutterstock" }, ]; export function ReportRightPanelCreateView(props: Readonly) { const [currentView, setCurrentView] = useRecoilState( reportRightPanelViewAtom ); - const [chartFromReport, setChartFromReport] = + const [_chartFromReport, setChartFromReport] = useRecoilState(chartFromReportAtom); const whiteBackgroundOnly = "background-color: #fff;"; const whiteBackgroundRoundedBottomRight = @@ -848,6 +849,16 @@ function ElementItem(props: { props.elementType ); + const observerTarget = React.useRef(null); + const { isObserved } = useInfinityScroll(observerTarget); + + // Pagination on scroll + React.useEffect(() => { + if (isObserved && data.length > 0) { + search(searchValue, true); + } + }, [isObserved]); + const [{ isDragging }, drag] = useDrag(() => ({ type: props.elementType, item: { @@ -970,156 +981,165 @@ function ElementItem(props: { {isImageElement || isVideoElement ? ( - <> - {dropDown ? ( +
+
-
setSearchValue(e.target.value)} + value={searchValue} + css={` + outline: none; + height: 34px; + width: 100%; + border: none; `} - > - setSearchValue(e.target.value)} - value={searchValue} - css={` - outline: none; - height: 34px; - width: 100%; - border: none; - `} - /> - -
+ /> + +
-
+ - - {get(currentSourceOptions, props.elementType, [{}]).map( - (option: any) => ( - { - setSource(option); - handleClose(); - }} - > - {option.label} - - ) - )} - -
+ {source.label} + + + + + {get(currentSourceOptions, props.elementType, [{}]).map( + (option: any) => ( + { + setSource(option); + handleClose(); + }} + > + {option.label} + + ) + )} + +
+
+ {data?.map((d, i) => + props.elementType === "video" ? ( + + ) : ( + + ) + )} + {loading + ? Array(4) + .fill(null) + .map((_d, index: number) => ( + + )) + : null}
- {loading - ? Array(4) - .fill(null) - .map((_d, index: number) => ( - - )) - : data.map((d, i) => - props.elementType === "video" ? ( - - ) : ( - - ) - )} -
+ ref={observerTarget} + />
- ) : null} - +
+ ) : null} );