Skip to content

Commit

Permalink
Integrate updates from master with new theme and adjust tests
Browse files Browse the repository at this point in the history
  • Loading branch information
SeanLeRoy committed Apr 24, 2024
1 parent 98b58a9 commit 67ca87f
Show file tree
Hide file tree
Showing 78 changed files with 1,107 additions and 1,017 deletions.
1 change: 1 addition & 0 deletions packages/core/App.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
}

.center {
flex: auto;
height: 100%;
margin: var(--margin) 0 var(--margin) var(--margin);
width: calc(70% - var(--margin));
Expand Down
2 changes: 1 addition & 1 deletion packages/core/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import ContextMenu from "./components/ContextMenu";
import Modal from "./components/Modal";
import DirectoryTree from "./components/DirectoryTree";
import FileDetails from "./components/FileDetails";
import GlobalActionButtonRow from "./components/GlobalActionButtonRow";
import StatusMessage from "./components/StatusMessage";
import TutorialTooltip from "./components/TutorialTooltip";
import QuerySidebar from "./components/QuerySidebar";
Expand All @@ -17,7 +18,6 @@ import { PlatformDependentServices } from "./state/interaction/actions";

import "./styles/global.css";
import styles from "./App.module.css";
import GlobalActionButtonRow from "./components/GlobalActionButtonRow";

// Used for mousemove listeners when resizing elements via click and drag (eg. File Details pane)
export const ROOT_ELEMENT_ID = "root";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.loading-container > div > div {
border-color: var(--secondary-text-color) var(--secondary-background-color) var(--secondary-background-color);
}

.loading-container, .picker {
background-color: var(--secondary-background-color);
color: var(--secondary-text-color);
padding: var(--margin);
width: 35vw;
}
187 changes: 91 additions & 96 deletions packages/core/components/AnnotationFilterForm/index.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import { Spinner, SpinnerSize } from "@fluentui/react";
import { find, isNil } from "lodash";
import { isNil } from "lodash";
import * as React from "react";
import { useDispatch, useSelector } from "react-redux";

import useAnnotationValues from "./useAnnotationValues";
import Annotation, { AnnotationName } from "../../entity/Annotation";
import { AnnotationType } from "../../entity/AnnotationFormatter";
import FileFilter from "../../entity/FileFilter";
import ListPicker, { ListItem } from "../ListPicker";
import NumberRangePicker from "../NumberRangePicker";
import SearchBoxForm from "../SearchBoxForm";
import DateRangePicker from "../DateRangePicker";
import { interaction, metadata, selection } from "../../state";
import { interaction, selection } from "../../state";

import styles from "./AnnotationFilterForm.module.css";

interface AnnotationFilterFormProps {
name: string;
annotation: Annotation;
}

/**
Expand All @@ -24,62 +27,48 @@ interface AnnotationFilterFormProps {
*/
export default function AnnotationFilterForm(props: AnnotationFilterFormProps) {
const dispatch = useDispatch();
const annotations = useSelector(metadata.selectors.getSortedAnnotations);
const fileFilters = useSelector(selection.selectors.getFileFilters);
const allFilters = useSelector(selection.selectors.getFileFilters);
const annotationService = useSelector(interaction.selectors.getAnnotationService);
// TODO: annotationService throws an error for annotations that aren't in the API
const [annotationValues, isLoading, errorMessage] = useAnnotationValues(
props.name,
props.annotation.name,
annotationService
);

const annotation = React.useMemo(
() => find(annotations, (annotation) => annotation.name === props.name),
[annotations, props.name]
);

const currentValues = React.useMemo(
() => find(fileFilters, (annotation) => annotation.name === props.name),
[props.name, fileFilters]
const filtersForAnnotation = React.useMemo(
() => allFilters.filter((filter) => filter.name === props.annotation.name),
[allFilters, props.annotation]
);

const items = React.useMemo<ListItem[]>(() => {
const appliedFilters = fileFilters
.filter((filter) => filter.name === annotation?.name)
.map((filter) => filter.value);
const appliedFilters = new Set(filtersForAnnotation.map((filter) => filter.value));

return (annotationValues || []).map((value) => ({
selected: appliedFilters.includes(value),
displayValue: annotation?.getDisplayValue(value) || value,
selected: appliedFilters.has(value),
displayValue: props.annotation.getDisplayValue(value) || value,
value,
}));
}, [annotation, annotationValues, fileFilters]);
}, [props.annotation, annotationValues, filtersForAnnotation]);

const onDeselectAll = () => {
const filters = items.map(
(item) =>
new FileFilter(
props.name,
isNil(annotation?.valueOf(item.value))
? item.value
: annotation?.valueOf(item.value)
)
);
dispatch(selection.actions.removeFileFilter(filters));
dispatch(selection.actions.removeFileFilter(filtersForAnnotation));
};

const onDeselect = (item: ListItem) => {
const fileFilter = new FileFilter(
props.name,
isNil(annotation?.valueOf(item.value)) ? item.value : annotation?.valueOf(item.value)
props.annotation.name,
isNil(props.annotation.valueOf(item.value))
? item.value
: props.annotation.valueOf(item.value)
);
dispatch(selection.actions.removeFileFilter(fileFilter));
};

const onSelect = (item: ListItem) => {
const fileFilter = new FileFilter(
props.name,
isNil(annotation?.valueOf(item.value)) ? item.value : annotation?.valueOf(item.value)
props.annotation.name,
isNil(props.annotation.valueOf(item.value))
? item.value
: props.annotation.valueOf(item.value)
);
dispatch(selection.actions.addFileFilter(fileFilter));
};
Expand All @@ -88,100 +77,106 @@ export default function AnnotationFilterForm(props: AnnotationFilterFormProps) {
const filters = items.map(
(item) =>
new FileFilter(
props.name,
isNil(annotation?.valueOf(item.value))
props.annotation.name,
isNil(props.annotation.valueOf(item.value))
? item.value
: annotation?.valueOf(item.value)
: props.annotation.valueOf(item.value)
)
);
dispatch(selection.actions.addFileFilter(filters));
};

function onSearch(filterValue: string) {
if (filterValue && filterValue.trim()) {
const fileFilter = new FileFilter(props.name, filterValue);
if (currentValues) {
dispatch(selection.actions.removeFileFilter(currentValues));
}
dispatch(selection.actions.addFileFilter(fileFilter));
dispatch(
selection.actions.setFileFilters([
...allFilters.filter((filter) => filter.name !== props.annotation.name),
new FileFilter(props.annotation.name, filterValue),
])
);
}
}

function onReset() {
if (currentValues) {
dispatch(selection.actions.removeFileFilter(currentValues));
}
if (isLoading) {
return (
<div className={styles.loadingContainer}>
<Spinner size={SpinnerSize.small} />
</div>
);
}

const listPicker = () => {
// Use the checkboxes if values exist and are few enough to reasonably scroll through
if (items.length > 0 && items.length <= 100) {
return (
<ListPicker
items={items}
loading={isLoading}
title={`Filter by ${props.annotation.displayName}`}
errorMessage={errorMessage}
onDeselect={onDeselect}
onDeselectAll={onDeselectAll}
onSelect={onSelect}
onSelectAll={onSelectAll}
/>
);
};

if (isLoading) {
return (
<div>
<Spinner size={SpinnerSize.small} />
</div>
);
}

const customInput = () => {
switch (annotation?.type) {
case AnnotationType.DATE:
case AnnotationType.DATETIME:
return (
<DateRangePicker
onSearch={onSearch}
onReset={onReset}
currentRange={currentValues}
/>
);
case AnnotationType.NUMBER:
switch (props.annotation.type) {
case AnnotationType.DATE:
case AnnotationType.DATETIME:
return (
<DateRangePicker
className={styles.picker}
onSearch={onSearch}
title={`Filter by ${props.annotation.displayName}`}
onReset={onDeselectAll}
currentRange={filtersForAnnotation?.[0]}
/>
);
case AnnotationType.NUMBER:
// File size is a special case where we don't have
// the ability to filter by range in the backend yet
// so we'll just let that case fall through to the string below
if (props.annotation.name !== AnnotationName.FILE_SIZE) {
return (
<NumberRangePicker
className={styles.picker}
items={items}
loading={isLoading}
title={`Filter by ${props.annotation.displayName}`}
errorMessage={errorMessage}
onSearch={onSearch}
onReset={onReset}
currentRange={currentValues}
units={annotation?.units}
onReset={onDeselectAll}
currentRange={filtersForAnnotation?.[0]}
units={props.annotation.units}
/>
);
case AnnotationType.DURATION:
case AnnotationType.STRING:
// prettier-ignore
default: // FALL-THROUGH
return (
<> {listPicker()} </>
);
}
};
// Use the checkboxes if values exist and are few enough to reasonably scroll through
if (items.length > 0 && items.length <= 100) {
return <> {listPicker()} </>;
}
// Use a search box if the API does not return values to select
// (e.g., it's not an AICS annotation)
else if (items.length === 0 && annotation?.type === AnnotationType.STRING) {
return (
<SearchBoxForm
onSearch={onSearch}
onReset={onReset}
fieldName={annotation.name}
currentValue={currentValues}
/>
);
}
case AnnotationType.STRING:
return (
<SearchBoxForm
className={styles.picker}
onSearch={onSearch}
onReset={onDeselectAll}
fieldName={props.annotation.displayName}
title={`Filter by ${props.annotation.displayName}`}
currentValue={filtersForAnnotation?.[0]}
/>
);
case AnnotationType.DURATION:
// prettier-ignore
default: // FALL-THROUGH
return (
<ListPicker
items={items}
loading={isLoading}
title={`Filter by ${props.annotation.displayName}`}
errorMessage={errorMessage}
onDeselect={onDeselect}
onDeselectAll={onDeselectAll}
onSelect={onSelect}
onSelectAll={onSelectAll}
/>
);
}
return <> {customInput()} </>;
}
Loading

0 comments on commit 67ca87f

Please sign in to comment.