Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create paginator component #3195

Merged
merged 25 commits into from
May 3, 2023
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c703170
Create paginator component
CarolineDenis Mar 15, 2023
44ac47a
Move Paginator component to molecule and delete uncessary libraries
CarolineDenis Mar 16, 2023
928be8b
Simplify usage of usePaginator()
maxpatiiuk Mar 17, 2023
08bb7ba
Sort Record Sets on the back-end
maxpatiiuk Mar 17, 2023
5cfa38e
Fix sort config causing infinite fetch loop
maxpatiiuk Mar 17, 2023
1ec1f25
Fix wrong sizing for the paginator <select>
maxpatiiuk Mar 17, 2023
bf23b48
Remember user's page size preference
maxpatiiuk Mar 17, 2023
88137e7
Implement pagination in Query overlay and refactor it
CarolineDenis Mar 20, 2023
05a0c4a
Delete duplicate file
maxpatiiuk Apr 19, 2023
93babd6
Fix wrong typing for fetchRows
maxpatiiuk Apr 19, 2023
0360179
Merge remote-tracking branch 'origin/xml-editor' into issue-1398
maxpatiiuk Apr 19, 2023
3f73d6c
Lint code with ESLint and Prettier
maxpatiiuk Apr 19, 2023
d0f8a15
Fix dialog size when navigating between pages in RecordSets overlay
CarolineDenis Apr 20, 2023
62147ef
Fix the size of dialog when changing page in Query Overlay
CarolineDenis Apr 20, 2023
0d08ab3
Remove unnecessary import
CarolineDenis Apr 20, 2023
edb9802
Remove full-height from ResourceView Dialog
CarolineDenis Apr 20, 2023
2032722
Simplify code
CarolineDenis Apr 21, 2023
e9aed7f
Fix failing test
maxpatiiuk Apr 21, 2023
17d5982
Provide simple mock for resize observer
maxpatiiuk Apr 21, 2023
0e47c2b
Work arround DevTools UX issue
maxpatiiuk Apr 27, 2023
73cc56a
Always apply padding right to numeric fields
maxpatiiuk Apr 27, 2023
b397f5a
Fix slider positioning in Interactions Dialog
maxpatiiuk Apr 27, 2023
4763ca4
Lint code with ESLint and Prettier
maxpatiiuk Apr 27, 2023
c428f14
Merge branch 'xml-editor' into issue-1398
CarolineDenis May 3, 2023
949a08a
Fix failing tests
CarolineDenis May 3, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ export const fetchRows = async <
TABLE_NAME extends keyof Tables,
SCHEMA extends Tables[TABLE_NAME],
FIELDS extends RR<
string | keyof SCHEMA['fields'],
string | Exclude<keyof SCHEMA['fields'], 'fields'>,
RA<'boolean' | 'null' | 'number' | 'string'>
>
>(
Expand All @@ -169,7 +169,7 @@ export const fetchRows = async <
fields,
distinct = false,
...filters
}: CollectionFetchFilters<SCHEMA> & {
}: Omit<CollectionFetchFilters<SCHEMA>, 'fields'> & {
readonly fields: FIELDS;
readonly distinct?: boolean;
},
Expand Down
10 changes: 4 additions & 6 deletions specifyweb/frontend/js_src/lib/components/FormEditor/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';

import { useLiveState } from '../../hooks/useLiveState';
import {f} from '../../utils/functools';
import { f } from '../../utils/functools';
import type { RA } from '../../utils/types';
import { defined } from '../../utils/types';
import type { AppResourceTabProps } from '../AppResources/TabDefinitions';
Expand Down Expand Up @@ -40,7 +40,7 @@ export function FormEditorWrapper(): JSX.Element {
parsed: [initialParsed],
syncer: { deserializer },
onChange: handleChange,
onSetCleanup:handleSetCleanup
onSetCleanup: handleSetCleanup,
} = React.useContext(FormEditorContext)!;

const originalParsed = React.useRef<ViewSets | undefined>(undefined);
Expand All @@ -61,10 +61,8 @@ export function FormEditorWrapper(): JSX.Element {
(parsed, changedViewNames): void => {
setChanged((changed) => new Set([...changed, ...changedViewNames]));
setParsed(parsed);
handleChange(() =>
updateXml(deserializer(parsed))
);
handleSetCleanup(async ()=>
handleChange(() => updateXml(deserializer(parsed)));
handleSetCleanup(async () =>
Promise.all(
Array.from(changed, async (viewName) =>
clearUrlCache(getViewSetApiUrl(viewName))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,8 +276,6 @@ export function ResourceView<SCHEMA extends AnySchema>({
* navigation buttons don't jump around a lot as you navigate between
* records
*/
const isFullHeight =
dialog === 'modal' && typeof headerButtons === 'function' && !isSubForm;

return (
<Dialog
Expand All @@ -300,9 +298,7 @@ export function ResourceView<SCHEMA extends AnySchema>({
)
}
className={{
container: `${dialogClassNames.normalContainer} ${
isFullHeight ? 'h-full' : ''
}`,
container: dialogClassNames.normalContainer,
content: `${className.formStyles} ${dialogClassNames.flexContent}`,
}}
dimensionsKey={viewName ?? resource?.specifyTable.view}
Expand Down
44 changes: 44 additions & 0 deletions specifyweb/frontend/js_src/lib/components/Molecules/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,8 @@ export function Dialog({
[positionKey]
);

useFreezeDialogSize(container, dimensionsKey);

const isFullScreen = containerClassName.includes(dialogClassNames.fullScreen);

const draggableContainer: Props['contentElement'] = React.useCallback(
Expand Down Expand Up @@ -569,3 +571,45 @@ function useTitleChangeNotice(dimensionKey: string | undefined): void {
);
}, [dimensionKey]);
}

function useFreezeDialogSize(
containerSizeRef: HTMLDivElement | null,
dimensionKey: string | undefined
): void {
React.useEffect(() => {
if (dimensionKey === undefined) return;
if (containerSizeRef === null) return undefined;
let oldHeight = containerSizeRef.offsetHeight;
let oldWidth = containerSizeRef.offsetWidth;
const resizeObserver = new ResizeObserver(() => {
const newHeight = containerSizeRef.offsetHeight;
const newWidth = containerSizeRef.offsetWidth;

const width = f.parseInt(containerSizeRef.style.width);
const height = f.parseInt(containerSizeRef.style.height);
const hasBeenChanged =
typeof width === 'number' && typeof height === 'number';

if (oldHeight !== undefined && newHeight < oldHeight && !hasBeenChanged) {
containerSizeRef.style.minHeight = `${oldHeight}px`;
} else oldHeight = newHeight;

if (oldWidth !== undefined && newWidth < oldWidth && !hasBeenChanged) {
containerSizeRef.style.minWidth = `${oldWidth}px`;
} else oldWidth = newWidth;

if (hasBeenChanged) {
containerSizeRef.style.minHeight = '';
containerSizeRef.style.minWidth = '';
}
});

resizeObserver.observe(containerSizeRef);

return () => {
resizeObserver.disconnect();
containerSizeRef.style.minHeight = '';
containerSizeRef.style.minWidth = '';
};
}, [containerSizeRef, dimensionKey]);
}
76 changes: 76 additions & 0 deletions specifyweb/frontend/js_src/lib/components/Molecules/Paginator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React from 'react';

import { useCachedState } from '../../hooks/useCachedState';
import { commonText } from '../../localization/common';
import type { GetSet } from '../../utils/types';
import { Select } from '../Atoms/Form';
import { formatNumber } from '../Atoms/Internationalization';
import { Slider } from '../FormSliders/Slider';

/**
* Infinity hitting the practical limits
*/
const infinity = 500;
export const pageSizes = [10, 50, 100, infinity];

export type Paginators = 'recordSets' | 'queryBuilder';

export function usePaginator(
cacheName: Paginators,
defaultRowsPerPage: typeof pageSizes[number] = 10
): {
readonly paginator: (totalCount: number | undefined) => JSX.Element;
readonly currentPage: GetSet<number>;
readonly limit: number;
readonly offset: number;
} {
const getSetPage = React.useState<number>(0);
const [currentPage, setCurrentPage] = getSetPage;
const [pageSize = defaultRowsPerPage, setPageSize] = useCachedState(
'pageSizes',
cacheName
);
return {
paginator(totalCount): JSX.Element {
const pageCount =
totalCount === undefined ? 0 : Math.ceil(totalCount / pageSize);
return (
<div className="flex flex-wrap gap-2">
<span className="flex-1" />
{pageSize === infinity ? undefined : (
<div>
<Slider
count={pageCount ?? 0}
value={currentPage}
onChange={setCurrentPage}
/>
</div>
)}
<div className="flex flex-1 justify-end">
<Select
className={pageSize === infinity ? 'w-auto' : 'w-16'}
value={pageSize}
onValueChange={(rawNewPageSize): void => {
const newPageSize = Number.parseInt(rawNewPageSize);
const currentIndex = currentPage * pageSize;
setPageSize(newPageSize);
setCurrentPage(Math.floor(currentIndex / newPageSize));
}}
>
{pageSizes.map((size) => (
<option key={size} value={size}>
{size === infinity
? commonText.unlimited()
: formatNumber(size)}
</option>
))}
</Select>
</div>
</div>
);
},
currentPage: getSetPage,
limit: pageSize,
offset: currentPage * pageSize,
};
}
10 changes: 8 additions & 2 deletions specifyweb/frontend/js_src/lib/components/Molecules/Sorting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,14 @@ export function useSortConfig<NAME extends keyof SortConfigs>(
mapper: (item: T) => boolean | number | string | null | undefined
) => RA<T>
] {
const [sortConfig = { sortField: defaultField, ascending }, setSortConfig] =
useCachedState('sortConfig', cacheKey);
const defaultValue = React.useMemo(
() => ({ sortField: defaultField, ascending }),
[defaultField, ascending]
);
const [sortConfig = defaultValue, setSortConfig] = useCachedState(
'sortConfig',
cacheKey
);
const handleClick = React.useCallback(
(sortField: SortConfigs[NAME]) => {
const newSortConfig: SortConfig<SortConfigs[NAME]> = {
Expand Down
34 changes: 21 additions & 13 deletions specifyweb/frontend/js_src/lib/components/QueryBuilder/Import.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';

import { useAsyncState } from '../../hooks/useAsyncState';
import { commonText } from '../../localization/common';
import { queryText } from '../../localization/query';
import { wbPlanText } from '../../localization/wbPlan';
Expand All @@ -14,29 +15,25 @@ import { Form } from '../Atoms/Form';
import { icons } from '../Atoms/Icons';
import { Submit } from '../Atoms/Submit';
import { LoadingContext } from '../Core/Contexts';
import { fetchRows } from '../DataModel/collection';
import { getField } from '../DataModel/helpers';
import type {
SerializedRecord,
SerializedResource,
} from '../DataModel/helperTypes';
import type { SerializedRecord } from '../DataModel/helperTypes';
import type { SpecifyResource } from '../DataModel/legacyTypes';
import { getResourceApiUrl } from '../DataModel/resource';
import { tables } from '../DataModel/tables';
import type { SpQuery } from '../DataModel/types';
import { userInformation } from '../InitialContext/userInformation';
import { Dialog, LoadingScreen } from '../Molecules/Dialog';
import { Dialog } from '../Molecules/Dialog';
import { FilePicker, fileToText } from '../Molecules/FilePicker';
import { TableIcon } from '../Molecules/TableIcon';
import { generateMappingPathPreview } from '../WbPlanView/mappingPreview';
import { QueryFieldSpec } from './fieldSpec';

export function QueryImport({
onClose: handleClose,
queries,
}: {
readonly onClose: () => void;
readonly queries: RA<SerializedResource<SpQuery>> | undefined;
}): JSX.Element {
}): JSX.Element | null {
const loading = React.useContext(LoadingContext);
const navigate = useNavigate();

Expand All @@ -53,7 +50,20 @@ export function QueryImport({
navigate(`/specify/query/${queryResource.id}/`);
}, [queryResource, hiddenFields]);

return typeof queries === 'object' ? (
const [queriesNames] = useAsyncState(
React.useCallback(
async () =>
fetchRows('SpQuery', {
fields: { name: ['string'] },
distinct: true,
limit: 0,
}),
[]
),
true
);

return typeof queriesNames === 'object' ? (
<Dialog
buttons={commonText.cancel()}
header={commonText.import()}
Expand Down Expand Up @@ -123,7 +133,7 @@ export function QueryImport({
'name',
getUniqueName(
queryResource.get('name'),
queries.map(({ name }) => name),
queriesNames.map(({ name }) => name),
getField(tables.SpQuery, 'name').length
)
)
Expand Down Expand Up @@ -164,7 +174,5 @@ export function QueryImport({
)}
</>
</Dialog>
) : (
<LoadingScreen />
);
) : null;
}
Loading