From 46c2f12390596442487940d709e819c8afcd2be2 Mon Sep 17 00:00:00 2001 From: Jennifer Chen Date: Tue, 3 Feb 2026 14:48:55 -0800 Subject: [PATCH 1/3] Improve add to library dialog - Load/Show page 1 of libraries intially - Allow multiple library selections --- .../Libraries/AddToLibraryModal.tsx | 220 +++++++++++++----- src/components/Libraries/LibraryListTable.tsx | 9 +- 2 files changed, 169 insertions(+), 60 deletions(-) diff --git a/src/components/Libraries/AddToLibraryModal.tsx b/src/components/Libraries/AddToLibraryModal.tsx index e3dd3fa59..5625a8ff7 100644 --- a/src/components/Libraries/AddToLibraryModal.tsx +++ b/src/components/Libraries/AddToLibraryModal.tsx @@ -25,14 +25,21 @@ import { import { FormProvider, useForm } from 'react-hook-form'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; -import { LibrarySelector } from './LibrarySelector'; -import { useState } from 'react'; +import { useMemo, useState } from 'react'; import { useStore } from '@/store'; import { parseAPIError } from '@/utils/common/parseAPIError'; import { LibraryIdentifier } from '@/api/biblib/types'; -import { useAddDocumentsByQuery, useAddLibrary, useEditLibraryDocuments } from '@/api/biblib/libraries'; +import { + useAddDocumentsByQuery, + useAddLibrary, + useEditLibraryDocuments, + useGetLibraries, +} from '@/api/biblib/libraries'; +import { ILibraryListTableSort, LibraryListTable } from './LibraryListTable'; +import { NumPerPageType } from '@/types'; +import { TableSkeleton } from './TableSkeleton'; export type SelectionType = 'all' | 'selected'; @@ -57,57 +64,71 @@ export const AddToLibraryModal = ({ const toast = useToast(); - const handleAddToLibrary = (id: LibraryIdentifier) => { + const handleAddToLibrary = (ids: LibraryIdentifier[]) => { if (bibcodes?.length > 0 || selectedDocs?.length > 0) { // add selected - editDocs( - { - id, - bibcode: bibcodes ?? selectedDocs, - action: 'add', - }, - { - onSettled(data, error) { - if (error) { - toast({ - status: 'error', - title: 'Error adding to library', - description: parseAPIError(error), - }); - } else { - toast({ - status: 'success', - title: `${data.number_added} papers added to library`, - }); - clearSelections(); - onClose(true); - } + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + + editDocs( + { + id, + bibcode: bibcodes ?? selectedDocs, + action: 'add', }, - }, - ); + { + onSettled(data, error) { + if (error) { + toast({ + status: 'error', + title: 'Error adding to library', + description: parseAPIError(error), + }); + } else { + toast({ + status: 'success', + title: `${data.number_added} papers added to ${ids.length} ${ + id.length > 1 ? 'libraries' : 'library' + } `, + }); + clearSelections(); + onClose(true); + } + }, + }, + ); + } } else { - addDocsByQuery( - { - id, - params: { q: query.q }, - action: 'add', - }, - { - onSettled(data, error) { - if (data) { - toast({ status: 'success', title: `${data.number_added} papers added to library` }); - clearSelections(); - onClose(true); - } else if (error) { - toast({ - status: 'error', - title: 'Error adding to library', - description: parseAPIError(error), - }); - } + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + addDocsByQuery( + { + id, + params: { q: query.q }, + action: 'add', }, - }, - ); + { + onSettled(data, error) { + if (data) { + toast({ + status: 'success', + title: `${data.number_added} papers added to ${ids.length} ${ + id.length > 1 ? 'libraries' : 'library' + }`, + }); + clearSelections(); + onClose(true); + } else if (error) { + toast({ + status: 'error', + title: 'Error adding to library', + description: parseAPIError(error), + }); + } + }, + }, + ); + } } }; @@ -121,13 +142,13 @@ export const AddToLibraryModal = ({ {bibcodes ? bibcodes.length : selectedDocs && selectedDocs.length !== 0 ? selectedDocs.length : 'all'}{' '} paper(s) {' '} - to Library + to: - Existing Library + Existing Libraries New Library @@ -159,24 +180,105 @@ const AddToExistingLibraryPane = ({ isLoading, }: { onClose: (added: boolean) => void; - onSubmit: (id: LibraryIdentifier) => void; + onSubmit: (ids: LibraryIdentifier[]) => void; isLoading: boolean; }) => { - const [library, setLibrary] = useState(null); + const [pageSize, setPageSize] = useState(10); + + const [pageIndex, setPageIndex] = useState(0); + + const [sort, setSort] = useState({ col: 'date_last_modified', dir: 'desc' }); + + const [selectedLibs, setSelectedLibs] = useState([]); + + const { data: librariesData, isLoading: isLoadingLibraries } = useGetLibraries({ + start: pageIndex * pageSize, + rows: pageSize, + sort: sort.col, + order: sort.dir, + }); + + const libraries = useMemo(() => { + if (librariesData) { + return librariesData.libraries; + } + }, [librariesData]); + + const entries = useMemo(() => { + if (librariesData) { + return librariesData.count; + } + }, [librariesData]); + + const handleSortChange = (sort: ILibraryListTableSort) => { + setSort(sort); + setPageIndex(0); + }; + + const handlePageIndexChange = (index: number) => { + setPageIndex(index); + }; + + const handlePageSizeChange = (size: NumPerPageType) => { + setPageSize(size); + setPageIndex(0); + }; + + const handleSelectLibrary = (id: LibraryIdentifier) => { + if (selectedLibs.includes(id)) { + // deselect + setSelectedLibs((prev) => prev.filter((l) => l !== id)); + } else { + // select + setSelectedLibs((prev) => [...prev, id]); + } + }; const handleOnSubmit = () => { - onSubmit(library); + onSubmit(selectedLibs); }; + const handleCancel = () => { - setLibrary(null); + setSelectedLibs([]); onClose(false); }; return ( <> - setLibrary(null)} /> + {isLoading || isLoadingLibraries ? ( + + ) : ( + <> + + {selectedLibs.length === 0 + ? 'No libraries selected' + : `${selectedLibs.length} ${selectedLibs.length > 1 ? 'libraries' : 'library'} selected`} + + + + )} + - + )} void; onChangePageIndex: (index: number) => void; onChangePageSize: (size: NumPerPageType) => void; @@ -127,6 +129,7 @@ export const LibraryListTable = (props: ILibraryListTableProps) => { hideCols = [], showDescription = true, selected = [], + selectable = false, onChangeSort, onChangePageIndex, onChangePageSize, @@ -189,7 +192,7 @@ export const LibraryListTable = (props: ILibraryListTableProps) => { - {showIndex && !isMobile && } + {!isMobile && (selectable || showIndex) && } {columns.map((column) => ( {allHiddenCols.indexOf(column.id) === -1 && ( @@ -252,7 +255,7 @@ export const LibraryListTable = (props: ILibraryListTableProps) => { onClick={() => onLibrarySelect(id)} tabIndex={0} onKeyDown={(e) => { - if (e.key === 'Enter') { + if (e.key === 'Enter' || e.key === ' ') { onLibrarySelect(id); } }} @@ -262,7 +265,14 @@ export const LibraryListTable = (props: ILibraryListTableProps) => { color={selected.includes(id) ? colors.highlightForeground : colors.text} style={{ backgroundColor: colors.highlightBackground, color: colors.highlightForeground }} > - {showIndex && !isMobile && } + {!isMobile && (selectable || showIndex) && ( + + )} {allHiddenCols.indexOf('public') === -1 && (
{pageSize * pageIndex + index + 1} + {showIndex && `${pageSize * pageIndex + index + 1} `} + {selectable && ( + + )} + {isPublic ? ( From 5fc95fa066bd0c809b620f0b75b33142ae9c4160 Mon Sep 17 00:00:00 2001 From: Jennifer Chen Date: Tue, 10 Feb 2026 11:42:14 -0800 Subject: [PATCH 3/3] fix add to library --- .../Libraries/AddToLibraryModal.tsx | 127 +++++++----------- 1 file changed, 45 insertions(+), 82 deletions(-) diff --git a/src/components/Libraries/AddToLibraryModal.tsx b/src/components/Libraries/AddToLibraryModal.tsx index 9e5bb97a9..21c3f95fe 100644 --- a/src/components/Libraries/AddToLibraryModal.tsx +++ b/src/components/Libraries/AddToLibraryModal.tsx @@ -25,7 +25,7 @@ import { import { FormProvider, useForm } from 'react-hook-form'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; -import { useMemo, useState } from 'react'; +import { useState } from 'react'; import { useStore } from '@/store'; @@ -65,71 +65,39 @@ export const AddToLibraryModal = ({ const toast = useToast(); const handleAddToLibrary = (ids: LibraryIdentifier[]) => { - if (bibcodes?.length > 0 || selectedDocs?.length > 0) { - // add selected - for (let i = 0; i < ids.length; i++) { - const id = ids[i]; - - editDocs( - { - id, - bibcode: bibcodes ?? selectedDocs, - action: 'add', - }, - { - onSettled(data, error) { - if (error) { - toast({ - status: 'error', - title: 'Error adding to library', - description: parseAPIError(error), - }); - } else { - toast({ - status: 'success', - title: `${data.number_added} papers added to ${ids.length} ${ - id.length > 1 ? 'libraries' : 'library' - } `, - }); - clearSelections(); - onClose(true); - } - }, - }, - ); - } - } else { - for (let i = 0; i < ids.length; i++) { - const id = ids[i]; - addDocsByQuery( - { - id, - params: { q: query.q }, - action: 'add', - }, - { - onSettled(data, error) { - if (data) { - toast({ - status: 'success', - title: `${data.number_added} papers added to ${ids.length} ${ - id.length > 1 ? 'libraries' : 'library' - }`, - }); - clearSelections(); - onClose(true); - } else if (error) { - toast({ - status: 'error', - title: 'Error adding to library', - description: parseAPIError(error), - }); - } - }, - }, - ); - } - } + const promises = + bibcodes?.length > 0 || selectedDocs?.length > 0 + ? ids.map((id) => + editDocs({ + id, + bibcode: bibcodes ?? selectedDocs, + action: 'add', + }), + ) + : ids.map((id) => + addDocsByQuery({ + id, + params: { q: query.q }, + action: 'add', + }), + ); + + Promise.all(promises).then( + () => { + toast({ + status: 'success', + title: `Paper(s) added to ${ids.length > 1 ? 'libraries' : 'library'} `, + }); + clearSelections(); + onClose(true); + }, + () => { + toast({ + status: 'error', + title: 'Error adding to library', + }); + }, + ); }; return ( @@ -191,24 +159,19 @@ const AddToExistingLibraryPane = ({ const [selectedLibs, setSelectedLibs] = useState([]); - const { data: librariesData, isLoading: isLoadingLibraries } = useGetLibraries({ - start: pageIndex * pageSize, - rows: pageSize, - sort: sort.col, - order: sort.dir, - }); + const { data: librariesData, isLoading: isLoadingLibraries } = useGetLibraries( + { + start: pageIndex * pageSize, + rows: pageSize, + sort: sort.col, + order: sort.dir, + }, + { staleTime: 0, cacheTime: 0 }, + ); - const libraries = useMemo(() => { - if (librariesData) { - return librariesData.libraries; - } - }, [librariesData]); + const libraries = librariesData?.libraries ?? []; - const entries = useMemo(() => { - if (librariesData) { - return librariesData.count; - } - }, [librariesData]); + const entries = librariesData?.count ?? 0; const handleSortChange = (sort: ILibraryListTableSort) => { setSort(sort);