Skip to content

Commit

Permalink
feature/persistant-annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
BrianWhitneyAI committed May 2, 2024
1 parent 9a04f7a commit fe33d2e
Show file tree
Hide file tree
Showing 13 changed files with 113 additions and 53 deletions.
4 changes: 2 additions & 2 deletions packages/core/components/AnnotationPicker/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useSelector } from "react-redux";
import ListPicker, { ListItem } from "../ListPicker";
import { TOP_LEVEL_FILE_ANNOTATION_NAMES } from "../../constants";
import Annotation from "../../entity/Annotation";
import { metadata, selection } from "../../state";
import { selection } from "../../state";

interface Props {
disabledTopLevelAnnotations?: boolean;
Expand All @@ -23,7 +23,7 @@ interface Props {
* downloading a manifest.
*/
export default function AnnotationPicker(props: Props) {
const annotations = useSelector(metadata.selectors.getSortedAnnotations);
const annotations = useSelector(selection.selectors.getSortedAnnotations);
const unavailableAnnotations = useSelector(
selection.selectors.getUnavailableAnnotationsForHierarchy
);
Expand Down
4 changes: 2 additions & 2 deletions packages/core/components/FileDetails/FileAnnotationList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useSelector } from "react-redux";
import FileAnnotationRow from "./FileAnnotationRow";
import Annotation, { AnnotationName } from "../../entity/Annotation";
import FileDetail from "../../entity/FileDetail";
import { interaction, metadata } from "../../state";
import { interaction, selection } from "../../state";

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

Expand All @@ -21,7 +21,7 @@ interface FileAnnotationListProps {
*/
export default function FileAnnotationList(props: FileAnnotationListProps) {
const { className, fileDetails, isLoading } = props;
const annotations = useSelector(metadata.selectors.getSortedAnnotations);
const annotations = useSelector(selection.selectors.getSortedAnnotations);
const { executionEnvService } = useSelector(interaction.selectors.getPlatformDependentServices);

// The path to this file on the host this application is running on
Expand Down
4 changes: 2 additions & 2 deletions packages/core/components/QueryPart/QueryFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import AnnotationPicker from "../AnnotationPicker";
import AnnotationFilterForm from "../AnnotationFilterForm";
import Tutorial from "../../entity/Tutorial";
import FileFilter from "../../entity/FileFilter";
import { metadata, selection } from "../../state";
import { selection } from "../../state";
import Annotation from "../../entity/Annotation";

interface Props {
Expand All @@ -20,7 +20,7 @@ interface Props {
export default function QueryFilter(props: Props) {
const dispatch = useDispatch();

const annotations = useSelector(metadata.selectors.getSortedAnnotations);
const annotations = useSelector(selection.selectors.getSortedAnnotations);
const filtersGroupedByName = useSelector(selection.selectors.getGroupedByFilterName);

return (
Expand Down
4 changes: 2 additions & 2 deletions packages/core/components/QueryPart/QueryGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useDispatch, useSelector } from "react-redux";
import QueryPart from ".";
import AnnotationPicker from "../AnnotationPicker";
import Tutorial from "../../entity/Tutorial";
import { metadata, selection } from "../../state";
import { selection } from "../../state";
import Annotation from "../../entity/Annotation";

interface Props {
Expand All @@ -17,7 +17,7 @@ interface Props {
export default function QueryGroup(props: Props) {
const dispatch = useDispatch();

const annotations = useSelector(metadata.selectors.getSortedAnnotations);
const annotations = useSelector(selection.selectors.getSortedAnnotations);

const selectedAnnotations = props.groups
.map((annotationName) =>
Expand Down
4 changes: 2 additions & 2 deletions packages/core/components/QueryPart/QuerySort.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useDispatch, useSelector } from "react-redux";

import QueryPart from ".";
import AnnotationPicker from "../AnnotationPicker";
import { metadata, selection } from "../../state";
import { selection } from "../../state";
import FileSort, { SortOrder } from "../../entity/FileSort";
import Tutorial from "../../entity/Tutorial";

Expand All @@ -17,7 +17,7 @@ interface Props {
export default function QuerySort(props: Props) {
const dispatch = useDispatch();

const annotations = useSelector(metadata.selectors.getSortedAnnotations);
const annotations = useSelector(selection.selectors.getSortedAnnotations);

return (
<QueryPart
Expand Down
4 changes: 2 additions & 2 deletions packages/core/components/QuerySidebar/Query.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import QueryFilter from "../QueryPart/QueryFilter";
import QueryGroup from "../QueryPart/QueryGroup";
import QuerySort from "../QueryPart/QuerySort";
import FileExplorerURL from "../../entity/FileExplorerURL";
import { metadata, selection } from "../../state";
import { selection } from "../../state";
import { Query as QueryType } from "../../state/selection/actions";

import styles from "./Query.module.css";
Expand All @@ -26,7 +26,7 @@ interface QueryProps {
export default function Query(props: QueryProps) {
const dispatch = useDispatch();
const queries = useSelector(selection.selectors.getQueries);
const annotations = useSelector(metadata.selectors.getSortedAnnotations);
const annotations = useSelector(selection.selectors.getSortedAnnotations);
const currentGlobalURL = useSelector(selection.selectors.getEncodedFileExplorerUrl);

const [isExpanded, setIsExpanded] = React.useState(false);
Expand Down
2 changes: 2 additions & 0 deletions packages/core/services/PersistentConfigService/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export enum PersistedConfigKeys {
HasUsedApplicationBefore = "HAS_USED_APPLICATION_BEFORE",
UserSelectedApplications = "USER_SELECTED_APPLICATIONS",
Queries = "QUERIES",
RecentAnnotations = "RECENT_ANNOTATIONS",
}

export interface UserSelectedApplication {
Expand All @@ -26,6 +27,7 @@ export interface PersistedConfig {
[PersistedConfigKeys.ImageJExecutable]?: string; // Deprecated
[PersistedConfigKeys.HasUsedApplicationBefore]?: boolean;
[PersistedConfigKeys.Queries]?: Query[];
[PersistedConfigKeys.RecentAnnotations]?: string[];
[PersistedConfigKeys.UserSelectedApplications]?: UserSelectedApplication[];
}

Expand Down
3 changes: 3 additions & 0 deletions packages/core/state/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ export function createReduxStore(options: CreateStoreOptions = {}) {
const displayAnnotations = rawDisplayAnnotations
? rawDisplayAnnotations.map((annotation) => new Annotation(annotation))
: [];
const recentAnnotations =
persistedConfig && persistedConfig[PersistedConfigKeys.RecentAnnotations];
const preloadedState: State = mergeState(initialState, {
interaction: {
csvColumns: persistedConfig?.[PersistedConfigKeys.CsvColumns],
Expand All @@ -77,6 +79,7 @@ export function createReduxStore(options: CreateStoreOptions = {}) {
selection: {
displayAnnotations,
queries,
recentAnnotations,
},
});
return configureStore<State>({
Expand Down
22 changes: 0 additions & 22 deletions packages/core/state/metadata/selectors.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,5 @@
import { createSelector } from "reselect";

import { State } from "../";
import Annotation, { AnnotationName } from "../../entity/Annotation";

// BASIC SELECTORS
export const getAnnotations = (state: State) => state.metadata.annotations;
export const getCollections = (state: State) => state.metadata.collections;

// COMPOSED SELECTORS
export const getSortedAnnotations = createSelector(getAnnotations, (annotations: Annotation[]) => {
// Sort annotations by file name first then everything else alphabetically
const fileNameAnnotationIndex = annotations.findIndex(
(annotation) =>
annotation.name === AnnotationName.FILE_NAME || annotation.name === "File Name"
);
if (fileNameAnnotationIndex === -1) {
return Annotation.sort(annotations);
}
return [
annotations[fileNameAnnotationIndex],
...Annotation.sort([
...annotations.slice(0, fileNameAnnotationIndex),
...annotations.slice(fileNameAnnotationIndex + 1),
]),
];
});
65 changes: 47 additions & 18 deletions packages/core/state/selection/reducer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { makeReducer } from "@aics/redux-utils";
import { omit } from "lodash";
import { castArray, omit } from "lodash";

import interaction from "../interaction";
import { THUMBNAIL_SIZE_TO_NUM_COLUMNS } from "../../constants";
Expand Down Expand Up @@ -49,6 +49,7 @@ export interface SelectionStateBranch {
filters: FileFilter[];
isDarkTheme: boolean;
openFileFolders: FileFolder[];
recentAnnotations: string[];
selectedQuery?: Query;
shouldDisplaySmallFont: boolean;
shouldDisplayThumbnailView: boolean;
Expand All @@ -73,6 +74,7 @@ export const initialState = {
fileSelection: new FileSelection(),
filters: [],
openFileFolders: [],
recentAnnotations: [],
shouldDisplaySmallFont: false,
queries: [],
shouldDisplayThumbnailView: false,
Expand All @@ -96,13 +98,26 @@ export default makeReducer<SelectionStateBranch>(
...state,
fileGridColumnCount: action.payload,
}),
[SET_FILE_FILTERS]: (state, action) => ({
...state,
filters: action.payload,
[SET_FILE_FILTERS]: (state, action) => {
console.log("Filter: ", action.payload);

// Reset file selections when file filters change
fileSelection: new FileSelection(),
}),
const recentAnnotations = Array.from(
new Set([
// get annotaionName from each filter and add to recents
...castArray(action.payload.map((filter: any) => filter.annotationName)),
...state.recentAnnotations,
])
);
//let recentAnnotations: string[] = [];

return {
...state,
filters: action.payload,
recentAnnotations,
// Reset file selections when file filters change
fileSelection: new FileSelection(),
};
},
[SORT_COLUMN]: (state, action) => {
if (state.sortColumn?.annotationName === action.payload) {
// If already sorting by this column descending
Expand Down Expand Up @@ -147,10 +162,16 @@ export default makeReducer<SelectionStateBranch>(
...state,
queries: action.payload,
}),
[SET_SORT_COLUMN]: (state, action) => ({
...state,
sortColumn: action.payload,
}),
[SET_SORT_COLUMN]: (state, action) => {
const recentAnnotations = Array.from(
new Set([...castArray(action.payload.annotationName), ...state.recentAnnotations])
);
return {
...state,
recentAnnotations,
sortColumn: action.payload,
};
},
[interaction.actions.REFRESH]: (state) => ({
...state,
availableAnnotationsForHierarchyLoading: true,
Expand All @@ -175,14 +196,22 @@ export default makeReducer<SelectionStateBranch>(
...state,
fileSelection: action.payload,
}),
[SET_ANNOTATION_HIERARCHY]: (state, action) => ({
...state,
annotationHierarchy: action.payload,
availableAnnotationsForHierarchyLoading: true,
[SET_ANNOTATION_HIERARCHY]: (state, action) => {
// combine recent hierarchy annotations with payload
const recentAnnotations = Array.from(
new Set([...castArray(action.payload), ...state.recentAnnotations])
);

// Reset file selections when annotation hierarchy changes
fileSelection: new FileSelection(),
}),
return {
...state,
annotationHierarchy: action.payload,
availableAnnotationsForHierarchyLoading: true,
recentAnnotations,

// Reset file selections when annotation hierarchy changes
fileSelection: new FileSelection(),
};
},
[SET_AVAILABLE_ANNOTATIONS]: (state, action) => ({
...state,
availableAnnotationsForHierarchy: action.payload,
Expand Down
37 changes: 36 additions & 1 deletion packages/core/state/selection/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { groupBy, keyBy, map } from "lodash";
import { createSelector } from "reselect";

import { State } from "../";
import Annotation from "../../entity/Annotation";
import Annotation, { AnnotationName } from "../../entity/Annotation";
import FileExplorerURL from "../../entity/FileExplorerURL";
import FileFilter from "../../entity/FileFilter";
import FileFolder from "../../entity/FileFolder";
Expand All @@ -24,6 +24,7 @@ export const getCollection = (state: State) => state.selection.collection;
export const getFileSelection = (state: State) => state.selection.fileSelection;
export const getIsDarkTheme = (state: State) => state.selection.isDarkTheme;
export const getOpenFileFolders = (state: State) => state.selection.openFileFolders;
export const getRecentAnnotations = (state: State) => state.selection.recentAnnotations;
export const getSelectedQuery = (state: State) => state.selection.selectedQuery;
export const getShouldDisplaySmallFont = (state: State) => state.selection.shouldDisplaySmallFont;
export const getShouldDisplayThumbnailView = (state: State) =>
Expand Down Expand Up @@ -76,3 +77,37 @@ export const getUnavailableAnnotationsForHierarchy = createSelector(
allAnnotations.filter((annotation) => !availableAnnotations.includes(annotation.name))
)
);

// COMPOSED SELECTORS
export const getSortedAnnotations = createSelector(
[getAnnotations, getRecentAnnotations],
(annotations: Annotation[], recentAnnotationNames: string[]) => {
// Create Array of annotations from recentAnnotationNames
const recentAnnotations = annotations.filter((annotation) =>
recentAnnotationNames.includes(annotation.name)
);

// get the File name annotaion in a list (if Present)
const fileNameAnnotation = annotations.filter(
(annotation) =>
annotation.name === AnnotationName.FILE_NAME || annotation.name === "File Name"
);

// combine all annotation lists
const combinedAnnotations = fileNameAnnotation.concat(
recentAnnotations,
...Annotation.sort(annotations)
);

// create map for filtering duplicate annotations.
const combinedAnnotationsMap = new Map();

// Iterate through the combined list and store annotations by their name in the Map.
combinedAnnotations.forEach((annotation) => {
combinedAnnotationsMap.set(annotation.name, annotation);
});

// Convert the Map values (unique annotations) back to an array.
return Array.from(combinedAnnotationsMap.values());
}
);
6 changes: 6 additions & 0 deletions packages/desktop/src/renderer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ store.subscribe(() => {
const csvColumns = interaction.selectors.getCsvColumns(state);
const displayAnnotations = selection.selectors.getAnnotationsToDisplay(state);
const hasUsedApplicationBefore = interaction.selectors.hasUsedApplicationBefore(state);
const recentAnnotations = selection.selectors.getRecentAnnotations(state);
const userSelectedApplications = interaction.selectors.getUserSelectedApplications(state);

const appState = {
Expand All @@ -91,6 +92,7 @@ store.subscribe(() => {
})),
[PersistedConfigKeys.HasUsedApplicationBefore]: hasUsedApplicationBefore,
[PersistedConfigKeys.Queries]: queries,
[PersistedConfigKeys.RecentAnnotations]: recentAnnotations,
[PersistedConfigKeys.UserSelectedApplications]: userSelectedApplications,
};

Expand All @@ -102,6 +104,10 @@ store.subscribe(() => {
{}
);
if (JSON.stringify(appState) !== JSON.stringify(oldAppState)) {
if (appState.RECENT_ANNOTATIONS[0] == undefined) {
appState.RECENT_ANNOTATIONS = [];
}

persistentConfigService.persist(appState);
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ const OPTIONS: Options<Record<string, unknown>> = {
},
},
},
[PersistedConfigKeys.RecentAnnotations]: {
type: "array",
items: {
type: "string",
},
},

[PersistedConfigKeys.HasUsedApplicationBefore]: {
type: "boolean",
},
Expand Down

0 comments on commit fe33d2e

Please sign in to comment.