Skip to content

Commit 2a25d4c

Browse files
g-barbilalesi
authored andcommitted
Display more than 30 items in simulate and build, improve load more behaviour. (#228)
1 parent 9095d64 commit 2a25d4c

File tree

8 files changed

+248
-110
lines changed

8 files changed

+248
-110
lines changed

src/app/app/virtual-lab/lab/[virtualLabId]/project/[projectId]/build/page.tsx

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
selectedSimTypeFamily,
3333
selectedTabFamily,
3434
} from '@/components/VirtualLab/ScopeSelector/state';
35+
import useInfiniteScroll from '@/hooks/virtual-labs/infinite-scroll';
3536
import Styles from '@/styles/vlabs.module.scss';
3637

3738
type Params = {
@@ -96,41 +97,26 @@ function BrowseModelsTab({ projectId, virtualLabId }: { projectId: string; virtu
9697
const atomKey = 'build' + selectedTab + projectId;
9798
const selectedSimType = useAtomValue(selectedSimTypeFamily(atomKey));
9899

99-
const selectedModelType = SimulationScopeToModelType[selectedSimType];
100+
const selectedModelType = SimulationScopeToModelType[selectedSimType] ?? DataType.CircuitMEModel;
100101

101-
const selectedRows = useAtomValue(
102-
selectedRowsAtom(projectId + 'build' + selectedModelType || DataType.CircuitMEModel)
103-
);
102+
const selectedRows = useAtomValue(selectedRowsAtom(projectId + 'build' + selectedModelType));
104103

105104
const [expanded] = useAtom(scopeSelectorExpandedAtom(atomKey));
106105

107-
// Note: Disabled temporarily until SFN
108-
// const generateCloneUrl = () => {
109-
// const model = selectedRows[0];
110-
// if (model && selectedModelType) {
111-
// const vlProjectUrl = generateVlProjectUrl(params.virtualLabId, params.projectId);
112-
// const baseBuildUrl = `${vlProjectUrl}/${SupportedTypeToTabDetails[selectedModelType].newUrl}`;
113-
// return `${baseBuildUrl}?mode=clone&model=${to64(model._source['@id'])}`;
114-
// }
115-
// };
116-
117-
// const onCloneModel = () => {
118-
// switch (selectedSimulationScope) {
119-
// case SimulationType.Synaptome: {
120-
// return generateCloneUrl();
121-
// }
122-
// default:
123-
// return undefined;
124-
// }
125-
// };
126-
127106
const navigateToDetailPage = (record: ExploreESHit<ExploreSectionResource>) => {
128107
const vlProjectUrl = generateVlProjectUrl(virtualLabId, projectId);
129108
const pathId = `${to64(`${record._source.project.label}!/!${record._id}`)}`;
130109
const baseExploreUrl = `${vlProjectUrl}/${SimTypeURLs[selectedSimType].viewUrl}`;
131110
router.push(`${baseExploreUrl}/${pathId}`);
132111
};
133112

113+
const loadMoreDiv = useInfiniteScroll(
114+
virtualLabId,
115+
projectId,
116+
selectedModelType ?? DataType.CircuitMEModel,
117+
projectId + 'build' + selectedModelType
118+
);
119+
134120
return (
135121
<>
136122
<div className="flex grow flex-col">
@@ -154,8 +140,11 @@ function BrowseModelsTab({ projectId, virtualLabId }: { projectId: string; virtu
154140
containerClass="grow bg-primary-9 flex flex-col"
155141
tableClass={classNames('grow', Styles.table)}
156142
dataKey={projectId + 'build' + selectedModelType || DataType.CircuitMEModel}
143+
showLoadingState={false}
157144
/>
158145

146+
{loadMoreDiv}
147+
159148
{selectedRows.length > 0 && (
160149
<div className="fixed bottom-12 right-[45px] flex items-center justify-end gap-2">
161150
<Btn

src/app/app/virtual-lab/lab/[virtualLabId]/project/[projectId]/simulate/page.tsx

Lines changed: 64 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { useAtom, useAtomValue } from 'jotai';
44
import { useRouter } from 'next/navigation';
5-
import { HTMLProps } from 'react';
5+
import { HTMLProps, useRef, useState } from 'react';
66

77
import {
88
SimulationScopeToDataType,
@@ -34,6 +34,7 @@ import {
3434
ScopeSelectorSmall,
3535
SectionTabs,
3636
} from '@/components/VirtualLab/ScopeSelector';
37+
import useInfiniteScroll, { useIntersectionObserver } from '@/hooks/virtual-labs/infinite-scroll';
3738
import Styles from '@/styles/vlabs.module.scss';
3839

3940
const SimTypeURLParams: Record<string, { view: string; model: string }> = {
@@ -87,6 +88,13 @@ function BrowseSimsTab({ projectId, virtualLabId }: { projectId: string; virtual
8788

8889
const [expanded] = useAtom(scopeSelectorExpandedAtom(atomKey));
8990

91+
const loadMoreDiv = useInfiniteScroll(
92+
virtualLabId,
93+
projectId,
94+
dataType ?? DataType.SingleNeuronSimulation,
95+
projectId + 'simulate' + dataType
96+
);
97+
9098
return (
9199
<>
92100
<div className="flex w-full grow flex-col">
@@ -112,7 +120,9 @@ function BrowseSimsTab({ projectId, virtualLabId }: { projectId: string; virtual
112120
containerClass="flex flex-col grow"
113121
tableClass={classNames('overflow-y-auto grow', Styles.table)}
114122
dataKey={projectId + 'simulate' + dataType}
123+
showLoadingState={false}
115124
/>
125+
{loadMoreDiv}
116126
</div>
117127
{selectedRows.length > 0 && (
118128
<div className="fixed bottom-12 right-[60px] flex h-12 items-center justify-end gap-2">
@@ -165,48 +175,64 @@ function NewSim({ projectId, virtualLabId }: { projectId: string; virtualLabId:
165175

166176
const selectedRows = useAtomValue(selectedRowsAtom(projectId + 'simulate' + modelType));
167177

178+
const tableRef = useRef<HTMLDivElement>(null);
179+
180+
const [buttonsVisible, setButtonsVisible] = useState(false);
181+
182+
const loadMoreDiv = useInfiniteScroll(
183+
virtualLabId,
184+
projectId,
185+
modelType,
186+
projectId + 'simulate' + modelType
187+
);
188+
189+
useIntersectionObserver({
190+
observedRef: tableRef,
191+
onIntersect: setButtonsVisible,
192+
rootMargin: '-300px',
193+
});
194+
168195
return (
169196
<>
170197
<ScopeSelector atomKey={atomKey} section="simulate" />
171198

172-
<div className="flex grow flex-col">
173-
<div className="flex grow flex-col">
174-
{/* TODO: replace this list with items saved in Model Library */}
175-
<div
176-
className="relative mb-5 flex w-full grow flex-col"
177-
id="explore-table-container-for-observable"
178-
>
179-
<ExploreSectionListingView
180-
containerClass="grow bg-primary-9 flex flex-col"
181-
tableClass={classNames('grow', Styles.table)}
182-
tableScrollable={false}
183-
controlsVisible={false}
184-
dataType={modelType}
185-
dataScope={ExploreDataScope.NoScope}
186-
virtualLabInfo={{ virtualLabId, projectId }}
187-
selectionType="radio"
188-
renderButton={() => null}
189-
dataKey={projectId + 'simulate' + modelType}
190-
/>
191-
{selectedRows.length > 0 && (
192-
<div className="absolute bottom-6 right-4 flex items-center justify-end gap-2">
193-
<Btn
194-
type="button"
195-
className="h-12 bg-primary-9 text-white hover:!bg-primary-7"
196-
onClick={() => navigateToDetailPage(selectedRows[0])}
197-
>
198-
View
199-
</Btn>
200-
<Btn
201-
className="h-12 bg-primary-9 text-white hover:!bg-primary-7"
202-
onClick={() => onModelSelected(selectedRows[0])}
203-
>
204-
New Simulation
205-
</Btn>
206-
</div>
207-
)}
199+
{/* TODO: replace this list with items saved in Model Library */}
200+
<div
201+
className="relative mb-5 flex w-full grow flex-col"
202+
id="explore-table-container-for-observable"
203+
ref={tableRef}
204+
>
205+
<ExploreSectionListingView
206+
containerClass="grow bg-primary-9 flex flex-col"
207+
tableClass={classNames('grow', Styles.table)}
208+
tableScrollable={false}
209+
controlsVisible={false}
210+
dataType={modelType}
211+
dataScope={ExploreDataScope.NoScope}
212+
virtualLabInfo={{ virtualLabId, projectId }}
213+
selectionType="radio"
214+
renderButton={() => null}
215+
dataKey={projectId + 'simulate' + modelType}
216+
showLoadingState={false}
217+
/>
218+
{buttonsVisible && selectedRows.length > 0 && (
219+
<div className="fixed bottom-8 right-[50px] flex items-center justify-end gap-2">
220+
<Btn
221+
type="button"
222+
className="h-12 bg-primary-9 text-white hover:!bg-primary-7"
223+
onClick={() => navigateToDetailPage(selectedRows[0])}
224+
>
225+
View
226+
</Btn>
227+
<Btn
228+
className="h-12 bg-primary-9 text-white hover:!bg-primary-7"
229+
onClick={() => onModelSelected(selectedRows[0])}
230+
>
231+
New Simulation
232+
</Btn>
208233
</div>
209-
</div>
234+
)}
235+
{loadMoreDiv}
210236
</div>
211237
</>
212238
);

src/components/explore-section/ExploreSectionListingView/ExploreSectionTable.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ export default function ExploreSectionTable({
319319
visible={controlsVisible}
320320
>
321321
{displayLoadMoreBtn && (
322-
<LoadMoreButton dataContext={dataContext} hide={toggleDisplayMore} />
322+
<LoadMoreButton dataContext={dataContext} dataKey={dataKey} hide={toggleDisplayMore} />
323323
)}
324324
</TableControls>
325325
)}

src/components/explore-section/ExploreSectionListingView/LoadMoreButton.tsx

Lines changed: 74 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
import { HTMLProps } from 'react';
1+
import { HTMLProps, useCallback } from 'react';
22
import { useAtom } from 'jotai';
3-
import { pageSizeAtom, dataAtom } from '@/state/explore-section/list-view-atoms';
3+
import {
4+
dataAtom,
5+
pageNumberAtom,
6+
previousDataAtom,
7+
} from '@/state/explore-section/list-view-atoms';
48
import { classNames } from '@/util/utils';
59
import { ExploreDataScope } from '@/types/explore-section/application';
610
import { DataType, PAGE_SIZE } from '@/constants/explore-section/list-views';
7-
import { useUnwrappedValue } from '@/hooks/hooks';
11+
import { useLoadableValue, useUnwrappedValue } from '@/hooks/hooks';
812
import { VirtualLabInfo } from '@/types/virtual-lab/common';
913

1014
function Btn({ children, className, disabled, onClick }: HTMLProps<HTMLButtonElement>) {
@@ -21,35 +25,86 @@ function Btn({ children, className, disabled, onClick }: HTMLProps<HTMLButtonEle
2125
);
2226
}
2327

24-
export default function LoadMoreButton({
25-
dataContext,
26-
hide,
27-
}: HTMLProps<HTMLButtonElement> & {
28+
export function useData(
2829
dataContext: {
2930
virtualLabInfo?: VirtualLabInfo;
3031
dataScope: ExploreDataScope;
3132
dataType: DataType;
32-
};
33-
hide: () => void;
34-
}) {
35-
const res = useUnwrappedValue(
33+
},
34+
key: string
35+
) {
36+
const [prevData] = useAtom(previousDataAtom({ ...dataContext, key }));
37+
38+
const data = useUnwrappedValue(
3639
dataAtom({
3740
...dataContext,
38-
key: dataContext.virtualLabInfo?.projectId ?? '' + dataContext.dataType,
41+
key,
3942
})
4043
);
4144

42-
const [contentSize, setContentSize] = useAtom(pageSizeAtom);
45+
return [...prevData, ...(data?.hits ?? [])];
46+
}
4347

44-
const onLoadMore = () => {
45-
setContentSize(contentSize + PAGE_SIZE);
46-
hide();
47-
};
48+
export function useLoadMore(
49+
dataContext: {
50+
virtualLabInfo?: VirtualLabInfo;
51+
dataScope: ExploreDataScope;
52+
dataType: DataType;
53+
},
54+
key: string
55+
) {
56+
const [, setPrevData] = useAtom(previousDataAtom({ ...dataContext, key }));
57+
58+
const res = useLoadableValue(
59+
dataAtom({
60+
...dataContext,
61+
key,
62+
})
63+
);
64+
65+
const data = useData(dataContext, key);
4866

49-
if (res?.total && contentSize > res.total.value) return null;
67+
const [pageNumber, setPageNumber] = useAtom(pageNumberAtom(key));
68+
69+
const loadMore = useCallback(
70+
(load: boolean = true) => {
71+
if (res.state === 'loading' || res.state === 'hasError' || !load) return;
72+
73+
if (res.data && res.data.hits.length < PAGE_SIZE) return;
74+
75+
// Store previous hits before fetching next page
76+
setPrevData(data);
77+
setPageNumber(pageNumber + 1);
78+
},
79+
[pageNumber, setPageNumber, res, data, setPrevData]
80+
);
81+
82+
return { loadMore, loading: res.state === 'loading' };
83+
}
84+
85+
export default function LoadMoreButton({
86+
dataContext,
87+
dataKey,
88+
hide,
89+
}: HTMLProps<HTMLButtonElement> & {
90+
dataContext: {
91+
virtualLabInfo?: VirtualLabInfo;
92+
dataScope: ExploreDataScope;
93+
dataType: DataType;
94+
};
95+
dataKey: string;
96+
hide: () => void;
97+
}) {
98+
const { loadMore } = useLoadMore(dataContext, dataKey);
5099

51100
return (
52-
<Btn className="bg-primary-8 text-white" onClick={onLoadMore}>
101+
<Btn
102+
className="bg-primary-8 text-white"
103+
onClick={() => {
104+
loadMore();
105+
hide();
106+
}}
107+
>
53108
<span>Load {PAGE_SIZE} more results...</span>
54109
</Btn>
55110
);

0 commit comments

Comments
 (0)