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
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 @@ -308,38 +295,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={({
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 @@ -367,15 +352,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
@@ -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 { QueryFormView } from './ToForms';

export function QueryBuilderResults({
table,
Expand Down Expand Up @@ -71,40 +70,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 @@ -155,8 +120,31 @@ export function QueryBuilderResults({
isSplit={isSplit}
splitContainerRef={splitViewRef}
splitHorizontal={isHorizontal}
splitPane={recordPreview}
splitPrimaryPaneMaxWidth={`${maximumPrimaryPaneWidth}px`}
renderSplitPane={({
results,
selectedRows: resultSelection,
totalCount,
onFetchMore,
onDelete,
}) => (
<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}
/>
)}
table={table}
onReRun={handleReRun}
onResults={handleResults}
Expand Down
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 scrollRef?: React.MutableRefObject<HTMLDivElement | null>;
readonly restoreScrollTopRef?: React.MutableRefObject<number | undefined>;
readonly refreshToken?: number;
Expand All @@ -99,6 +104,16 @@ export type QueryResultsProps = {
>;
};

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 {
const {
table,
Expand All @@ -119,6 +134,10 @@ export function QueryResults(props: QueryResultsProps): JSX.Element {
tableClassName = '',
selectedRows: [selectedRows, setSelectedRows],
onResults: handleResults,
renderSplitPane,
isSplit,
splitHorizontal,
splitPrimaryPaneMaxWidth,
scrollRef,
restoreScrollTopRef,
refreshToken,
Expand Down Expand Up @@ -456,7 +475,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 @@ -688,6 +707,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 ? (
renderSplitPane({
results: results ?? [],
selectedRows,
totalCount,
onFetchMore: canFetchMore ? handleFetchMore : undefined,
onDelete: handleDelete,
})
) : (
<></>
)
}
secondaryPaneKey="split-pane"
/>
);
}

function TableHeaderCell({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
queryFieldsToFieldSpecs,
unParseQueryFields,
} from './helpers';
import type { QueryResultRow } from './Results';
import type { QueryResultRow, QueryResultsSplitPaneProps } from './Results';
import { QueryResults } from './Results';
import { SplitView } from './SplitView';

Expand All @@ -35,6 +35,7 @@ export function QueryResultsWrapper({
onSelected: handleSelected,
onResults: handleResults,
onReRun: handleReRun,
renderSplitPane,
refreshToken,
splitPane,
splitContainerRef,
Expand All @@ -56,6 +57,7 @@ export function QueryResultsWrapper({
readonly splitPrimaryPaneMaxWidth?: string;
readonly isSplit?: boolean;
readonly onReRun: () => void;
readonly renderSplitPane?: (props: QueryResultsSplitPaneProps) => JSX.Element;
}): JSX.Element | null {
const newProps = useQueryResultsWrapper(props);

Expand All @@ -65,14 +67,21 @@ export function QueryResultsWrapper({
);

const queryResults = (
<div className="flex flex-1 snap-start overflow-hidden">
<div
className="flex flex-1 snap-start overflow-hidden"
ref={renderSplitPane === undefined ? undefined : splitContainerRef}
>
<ErrorBoundary dismissible>
<QueryResults
{...newProps}
onResults={handleResults}
createRecordSet={createRecordSet}
extraButtons={extraButtons}
onReRun={handleReRun}
renderSplitPane={renderSplitPane}
isSplit={isSplit}
splitHorizontal={splitHorizontal}
splitPrimaryPaneMaxWidth={splitPrimaryPaneMaxWidth}
onSelected={handleSelected}
refreshToken={refreshToken}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,14 @@ export function SplitView({
isSplit ? '' : '[&_.handle-bar]:hidden'
}`}
position={isHorizontal ? 'vertical' : 'horizontal'}
primaryPaneHeight={isSplit ? '50%' : '100%'}
primaryPaneHeight={isSplit && !isHorizontal ? '50%' : '100%'}
primaryPaneMaxHeight={isSplit ? '80%' : '100%'}
primaryPaneMaxWidth={isSplit ? (primaryPaneMaxWidth ?? '80%') : '100%'}
primaryPaneMaxWidth={
isSplit && isHorizontal ? (primaryPaneMaxWidth ?? '80%') : '100%'
}
primaryPaneMinHeight={1}
primaryPaneMinWidth={1}
primaryPaneWidth={isSplit ? '50%' : '100%'}
primaryPaneWidth={isSplit && isHorizontal ? '50%' : '100%'}
ref={splitterRef}
>
<div
Expand Down
Loading
Loading