Skip to content
Merged
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
71 changes: 24 additions & 47 deletions specifyweb/frontend/js_src/lib/components/DataViews/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { DataEntry } from '../Atoms/DataEntry';
import { getTable } from '../DataModel/tables';
import type { Tables } from '../DataModel/types';
import { raise } from '../Errors/Crash';
import { RecordSelectorFromIds } from '../FormSliders/RecordSelectorFromIds';
import { Dialog } from '../Molecules/Dialog';
import { TableIcon } from '../Molecules/TableIcon';
import { hasPermission } from '../Permissions/helpers';
Expand All @@ -23,11 +22,11 @@ import { parseQueryFields, unParseQueryFields } from '../QueryBuilder/helpers';
import { queryIdField } from '../QueryBuilder/Results';
import { QueryResultsWrapper } from '../QueryBuilder/ResultsWrapper';
import {
SplitView,
SplitViewOrientationButton,
SplitViewToggleButton,
useSplitViewOrientation,
} from '../QueryBuilder/SplitView';
import { QueryFormView } from '../QueryBuilder/ToForms';
import { NotFoundView } from '../Router/NotFoundView';
import type { DataViewQueriesFile } from './queries';
import {
Expand Down Expand Up @@ -103,7 +102,6 @@ function LoadedDataViewFromTable({
const selectedIdsRef = React.useRef(selectedIds);
selectedIdsRef.current = selectedIds;
const resultOrderRef = React.useRef<ReadonlyArray<number>>([]);
const hasSeenNonEmptyResultsRef = React.useRef(false);
const [selectedIndex, setSelectedIndex] = React.useState(0);
const selectedIndexRef = React.useRef(selectedIndex);
selectedIndexRef.current = selectedIndex;
Expand Down Expand Up @@ -153,20 +151,9 @@ function LoadedDataViewFromTable({
const id = getNumericResultId(row?.[queryIdField]);
return id === undefined ? [] : [id];
});
const isInitialResults =
!hasSeenNonEmptyResultsRef.current && orderedIds.length > 0;
if (orderedIds.length > 0) hasSeenNonEmptyResultsRef.current = true;
resultOrderRef.current = orderedIds;

if (selectedIdsRef.current.length === 0) {
if (!isInitialResults) return;
const firstId = orderedIds[0];
if (firstId !== undefined) {
setSelectedIds([firstId]);
setSelectedIndex(0);
}
return;
}
if (selectedIdsRef.current.length === 0) return;

const positions = new Map(
orderedIds.map((id, index) => [id, index] as const)
Expand Down Expand Up @@ -322,38 +309,36 @@ function LoadedDataViewFromTable({
table={table}
onResults={handleResults}
refreshToken={refreshToken}
restoreScrollTopRef={restoreScrollTopRef}
scrollRef={resultsScrollRef}
/>
);
const form = (
<div className="flex min-h-0 min-w-0 flex-1 items-center justify-center overflow-auto bg-[color:var(--form-background)]">
{selectedIds.length === 0 ? (
<p className="m-auto text-neutral-500">{commonText.select()}</p>
) : (
<RecordSelectorFromIds
canRemove={false}
defaultIndex={selectedIndex}
dialog={false}
ids={[...selectedIds]}
isDependent={false}
isInRecordSet={false}
newResource={undefined}
isSplit={isSplit}
splitHorizontal={isHorizontal}
splitPrimaryPaneMaxWidth={`${maximumPrimaryPaneWidth}px`}
renderSplitPane={({
Comment thread
coderabbitai[bot] marked this conversation as resolved.
results,
selectedRows: resultSelection,
totalCount,
onFetchMore,
onDelete,
}) => (
<QueryFormView
results={results}
selectedRows={resultSelection}
selectedIndex={selectedIndex}
table={table}
title={dataViewsText.tableRecords({ tableLabel: table.label })}
totalCount={selectedIds.length}
onAdd={undefined}
onClone={undefined}
totalCount={totalCount}
onClose={(): void => {
setSelectedIds([]);
setSelectedIndex(0);
}}
onDelete={undefined}
onDelete={onDelete}
onFetchMore={onFetchMore}
onSaved={handleRefresh}
onSlide={(index): void => setSelectedIndex(index)}
onSlide={setSelectedIndex}
/>
)}
</div>
restoreScrollTopRef={restoreScrollTopRef}
scrollRef={resultsScrollRef}
/>
);

return (
Expand Down Expand Up @@ -381,15 +366,7 @@ function LoadedDataViewFromTable({
className="flex h-full max-h-full min-h-0 min-w-0 flex-1 overflow-hidden"
ref={splitViewRef}
>
<SplitView
isHorizontal={isHorizontal}
isSplit={isSplit}
primaryPane={results}
primaryPaneKey="query-results"
primaryPaneMaxWidth={`${maximumPrimaryPaneWidth}px`}
secondaryPane={form}
secondaryPaneKey="record-preview"
/>
{results}
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,20 +85,17 @@ export function RecordSelectorFromIds<SCHEMA extends AnySchema>({
ids.map((id) => (id === undefined ? undefined : new table.Resource({ id })))
);

const previousIds = React.useRef(ids);

React.useEffect(() => {
setRecords((records) =>
ids.map((id, index) => {
ids.map((id) => {
if (id === undefined) return undefined;
else if (records[index]?.id === id) return records[index];
else return new table.Resource({ id });
else
return (
records.find((record) => record?.id === id) ??
new table.Resource({ id })
);
})
);

return (): void => {
previousIds.current = ids;
};
}, [ids, table]);

const [rawIndex, setIndex] = useTriggerState(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import React from 'react';

import { commonText } from '../../localization/common';
import { type RA, localized } from '../../utils/types';
import { BatchEditFromQuery } from '../BatchEdit';
import type { SerializedResource } from '../DataModel/helperTypes';
import type { SpecifyResource } from '../DataModel/legacyTypes';
import type { SpecifyTable } from '../DataModel/specifyTable';
import type { RecordSet, SpQuery, SpQueryField } from '../DataModel/types';
import { RecordSelectorFromIds } from '../FormSliders/RecordSelectorFromIds';
import { hasPermission } from '../Permissions/helpers';
import { datasetVariants } from '../WbUtils/datasetVariants';
import { MakeRecordSetButton } from './Components';
Expand All @@ -16,6 +14,7 @@ import type { QueryField } from './helpers';
import type { MainState } from './reducer';
import type { QueryResultRow } from './Results';
import { QueryResultsWrapper } from './ResultsWrapper';
import { hasFetchableRecordIds, QueryFormView } from './ToForms';

export function QueryBuilderResults({
table,
Expand Down Expand Up @@ -73,40 +72,6 @@ export function QueryBuilderResults({
readonly onSortChange: (fields: RA<QueryField>) => void;
}): JSX.Element | null {
const [refreshToken, setRefreshToken] = React.useState(0);
const selectedIds = React.useMemo(
() => Array.from(selectedRows),
[selectedRows]
);
const recordPreview = (
<div className="flex h-full min-h-0 min-w-0 flex-1 items-center justify-center overflow-auto bg-[color:var(--form-background)]">
{selectedIds.length === 0 ? (
<p className="m-auto text-neutral-500">{commonText.select()}</p>
) : (
<RecordSelectorFromIds
canRemove={false}
defaultIndex={selectedIndex}
dialog={false}
ids={selectedIds}
isDependent={false}
isInRecordSet={false}
newResource={undefined}
table={table}
title={localized(query.name)}
totalCount={selectedIds.length}
onAdd={undefined}
onClone={undefined}
onClose={(): void => {
setSelectedRows(new Set());
setSelectedIndex(0);
}}
onDelete={undefined}
onSaved={(): void => setRefreshToken((token) => token + 1)}
onSlide={setSelectedIndex}
/>
)}
</div>
);

return hasPermission('/querybuilder/query', 'execute') ? (
<QueryResultsWrapper
createRecordSet={
Expand Down Expand Up @@ -158,8 +123,37 @@ export function QueryBuilderResults({
isSplit={isSplit}
splitContainerRef={splitViewRef}
splitHorizontal={isHorizontal}
splitPane={recordPreview}
splitPrimaryPaneMaxWidth={`${maximumPrimaryPaneWidth}px`}
renderSplitPane={({
results,
selectedRows: resultSelection,
totalCount,
onFetchMore,
onDelete,
}) => (
<>
{query.selectDistinct !== true &&
!isCountOnly &&
hasFetchableRecordIds(results) ? (
<QueryFormView
results={results}
selectedRows={resultSelection}
selectedIndex={selectedIndex}
table={table}
title={localized(query.name)}
totalCount={totalCount}
onClose={(): void => {
setSelectedRows(new Set());
setSelectedIndex(0);
}}
onDelete={onDelete}
onFetchMore={onFetchMore}
onSaved={(): void => setRefreshToken((token) => token + 1)}
onSlide={setSelectedIndex}
/>
) : null}
</>
)}
table={table}
onReRun={handleReRun}
onResults={handleResults}
Expand Down
53 changes: 51 additions & 2 deletions specifyweb/frontend/js_src/lib/components/QueryBuilder/Results.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { sortTypes } from './helpers';
import { QueryResultsTable } from './ResultsTable';
import { QueryToForms } from './ToForms';
import { QueryToMap } from './ToMap';
import { SplitView } from './SplitView';

export type QueryResultRow = RA<number | string | null>;

Expand Down Expand Up @@ -91,6 +92,10 @@ export type QueryResultsProps = {
readonly tableClassName?: string;
readonly selectedRows: GetSet<ReadonlySet<number>>;
readonly onResults?: (results: RA<QueryResultRow | undefined>) => void;
readonly renderSplitPane?: (props: QueryResultsSplitPaneProps) => JSX.Element;
readonly isSplit?: boolean;
readonly splitHorizontal?: boolean;
readonly splitPrimaryPaneMaxWidth?: string;
readonly onDeleted?: (recordId: number) => void;
readonly onMerged?: () => void;
readonly scrollRef?: React.MutableRefObject<HTMLDivElement | null>;
Expand All @@ -99,6 +104,17 @@ export type QueryResultsProps = {
readonly resultsRef?: React.MutableRefObject<
RA<QueryResultRow | undefined> | undefined
>;
readonly isLoading?: boolean;
};

export type QueryResultsSplitPaneProps = {
readonly results: RA<QueryResultRow | undefined>;
readonly selectedRows: ReadonlySet<number>;
readonly totalCount: number | undefined;
readonly onFetchMore:
| ((index?: number) => Promise<RA<QueryResultRow | undefined> | undefined>)
| undefined;
readonly onDelete: (id: number) => void;
};

export function QueryResults(props: QueryResultsProps): JSX.Element {
Expand All @@ -122,12 +138,17 @@ export function QueryResults(props: QueryResultsProps): JSX.Element {
tableClassName = '',
selectedRows: [selectedRows, setSelectedRows],
onResults: handleResults,
renderSplitPane,
isSplit,
splitHorizontal,
splitPrimaryPaneMaxWidth,
onDeleted: handleDeleted,
scrollRef,
restoreScrollTopRef,
refreshToken,
resultsRef,
displayedFields,
isLoading = false,
} = props;

const {
Expand Down Expand Up @@ -460,7 +481,7 @@ export function QueryResults(props: QueryResultsProps): JSX.Element {
typeof loadedResults?.[0]?.[0] === 'string' && loadedResults !== undefined;
const metaColumns = (showLineNumber ? 1 : 0) + 2;

return (
const queryResults = (
<Container.Base
className={`w-full !bg-[color:var(--form-background)] ${containerClassName}`}
>
Expand Down Expand Up @@ -686,7 +707,9 @@ export function QueryResults(props: QueryResultsProps): JSX.Element {
}}
/>
) : undefined}
{isFetching || (!showResults && Array.isArray(results)) ? (
{isLoading ||
isFetching ||
(!showResults && Array.isArray(results)) ? (
<div className="col-span-full" role="cell">
{loadingGif}
</div>
Expand All @@ -695,6 +718,32 @@ export function QueryResults(props: QueryResultsProps): JSX.Element {
</div>
</Container.Base>
);

return renderSplitPane === undefined ? (
queryResults
) : (
<SplitView
isHorizontal={splitHorizontal ?? true}
isSplit={isSplit}
primaryPane={queryResults}
primaryPaneKey="query-results"
primaryPaneMaxWidth={splitPrimaryPaneMaxWidth}
secondaryPane={
isSplit !== false ? (
renderSplitPane({
results: results ?? [],
selectedRows,
totalCount,
onFetchMore: canFetchMore ? handleFetchMore : undefined,
onDelete: handleDelete,
})
) : (
<></>
)
}
secondaryPaneKey="split-pane"
/>
);
}

function TableHeaderCell({
Expand Down
Loading
Loading