Skip to content
Open
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
199 changes: 135 additions & 64 deletions src/components/Libraries/AddToLibraryModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 { 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';

Expand All @@ -57,58 +64,40 @@ export const AddToLibraryModal = ({

const toast = useToast();

const handleAddToLibrary = (id: 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);
}
},
},
);
} 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),
});
}
},
},
);
}
const handleAddToLibrary = (ids: LibraryIdentifier[]) => {
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 (
Expand All @@ -121,13 +110,13 @@ export const AddToLibraryModal = ({
{bibcodes ? bibcodes.length : selectedDocs && selectedDocs.length !== 0 ? selectedDocs.length : 'all'}{' '}
paper(s)
</Text>{' '}
to Library
to:
</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Tabs variant="soft-rounded">
<TabList>
<Tab>Existing Library</Tab>
<Tab>Existing Libraries</Tab>
<Tab>New Library</Tab>
</TabList>
<TabPanels>
Expand Down Expand Up @@ -159,24 +148,106 @@ const AddToExistingLibraryPane = ({
isLoading,
}: {
onClose: (added: boolean) => void;
onSubmit: (id: LibraryIdentifier) => void;
onSubmit: (ids: LibraryIdentifier[]) => void;
isLoading: boolean;
}) => {
const [library, setLibrary] = useState<LibraryIdentifier>(null);
const [pageSize, setPageSize] = useState<NumPerPageType>(10);

const [pageIndex, setPageIndex] = useState(0);

const [sort, setSort] = useState<ILibraryListTableSort>({ col: 'date_last_modified', dir: 'desc' });

const [selectedLibs, setSelectedLibs] = useState<LibraryIdentifier[]>([]);

const { data: librariesData, isLoading: isLoadingLibraries } = useGetLibraries(
{
start: pageIndex * pageSize,
rows: pageSize,
sort: sort.col,
order: sort.dir,
},
{ staleTime: 0, cacheTime: 0 },
);

const libraries = librariesData?.libraries ?? [];

const entries = librariesData?.count ?? 0;

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 (
<>
<LibrarySelector isMultiple={false} onSelect={setLibrary} onDeselect={() => setLibrary(null)} />
{isLoading || isLoadingLibraries ? (
<TableSkeleton r={pageSize} h="30px" />
) : (
<>
<Text fontSize="sm">
{selectedLibs.length === 0
? 'No libraries selected'
: `${selectedLibs.length} ${selectedLibs.length > 1 ? 'libraries' : 'library'} selected`}{' '}
{selectedLibs.length > 0 && (
<Button size="sm" ml={2} variant="link" onClick={() => setSelectedLibs([])}>
remove all
</Button>
)}
</Text>
<LibraryListTable
libraries={libraries}
entries={entries}
sort={sort}
pageSize={pageSize}
pageIndex={pageIndex}
showIndex={false}
showSettings={false}
showDescription={false}
hideCols={['public', 'num_users', 'permission', 'date_created']}
selectable
selected={selectedLibs}
onChangeSort={handleSortChange}
onChangePageIndex={handlePageIndexChange}
onChangePageSize={handlePageSizeChange}
onLibrarySelect={handleSelectLibrary}
/>
</>
)}

<HStack mt={4} justifyContent="end">
<Button onClick={handleOnSubmit} isDisabled={!library} isLoading={isLoading}>
<Button
onClick={handleOnSubmit}
isDisabled={selectedLibs.length === 0}
isLoading={isLoading || isLoadingLibraries}
>
Submit
</Button>
<Button variant="outline" onClick={handleCancel}>
Expand All @@ -193,7 +264,7 @@ const AddToNewLibraryPane = ({
isLoading,
}: {
onClose: (added: boolean) => void;
onSubmit: (id: LibraryIdentifier) => void;
onSubmit: (ids: LibraryIdentifier[]) => void;
isLoading: boolean;
}) => {
interface FormValues {
Expand Down Expand Up @@ -241,7 +312,7 @@ const AddToNewLibraryPane = ({
{
onSettled(data, error) {
if (data) {
onSubmit(data.id);
onSubmit([data.id]);
} else {
toast({ status: 'error', title: parseAPIError(error) });
}
Expand Down
25 changes: 21 additions & 4 deletions src/components/Libraries/LibraryListTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
Box,
Button,
Center,
Checkbox,
Flex,
IconButton,
Menu,
Expand Down Expand Up @@ -107,6 +108,8 @@ export interface ILibraryListTableProps extends TableProps {
showSettings?: boolean;
hideCols?: Column[];
showDescription?: boolean;
selected?: LibraryIdentifier[];
selectable?: boolean;
onChangeSort: (sort: ILibraryListTableSort) => void;
onChangePageIndex: (index: number) => void;
onChangePageSize: (size: NumPerPageType) => void;
Expand All @@ -125,6 +128,8 @@ export const LibraryListTable = (props: ILibraryListTableProps) => {
showSettings = true,
hideCols = [],
showDescription = true,
selected = [],
selectable = false,
onChangeSort,
onChangePageIndex,
onChangePageSize,
Expand All @@ -141,7 +146,7 @@ export const LibraryListTable = (props: ILibraryListTableProps) => {

const allHiddenCols = useMemo(() => {
return isMobile ? uniq([...hideColsSmallDisplay, ...hideCols]) : [...hideCols];
}, [isMobile]);
}, [hideCols, isMobile]);

const { mutate: deleteLibrary } = useDeleteLibrary();

Expand Down Expand Up @@ -187,7 +192,7 @@ export const LibraryListTable = (props: ILibraryListTableProps) => {
<Table variant="simple" {...tableProps} data-testid="libraries-table">
<Thead>
<Tr>
{showIndex && !isMobile && <Th aria-label="index"></Th>}
{!isMobile && (selectable || showIndex) && <Th aria-label="index"></Th>}
{columns.map((column) => (
<Fragment key={`col-${column.id}`}>
{allHiddenCols.indexOf(column.id) === -1 && (
Expand Down Expand Up @@ -250,12 +255,24 @@ export const LibraryListTable = (props: ILibraryListTableProps) => {
onClick={() => onLibrarySelect(id)}
tabIndex={0}
onKeyDown={(e) => {
if (e.key === 'Enter') {
if (e.key === 'Enter' || e.key === ' ') {
onLibrarySelect(id);
}
}}
role="row"
aria-selected={selected.includes(id)}
backgroundColor={selected.includes(id) ? colors.highlightBackground : 'transparent'}
color={selected.includes(id) ? colors.highlightForeground : colors.text}
style={{ backgroundColor: colors.highlightBackground, color: colors.highlightForeground }}
>
{showIndex && !isMobile && <Td>{pageSize * pageIndex + index + 1}</Td>}
{!isMobile && (selectable || showIndex) && (
<Td>
{showIndex && `${pageSize * pageIndex + index + 1} `}
{selectable && (
<Checkbox isChecked={selected.includes(id)} pointerEvents="none" tabIndex={-1} />
)}
</Td>
)}
{allHiddenCols.indexOf('public') === -1 && (
<Td>
{isPublic ? (
Expand Down
Loading