move to Explorer/Search

This commit is contained in:
Brendan Allan 2023-11-10 18:45:49 +08:00
parent fa43998afa
commit 054af931f2
14 changed files with 110 additions and 88 deletions

View file

@ -1,8 +1,9 @@
import { MagnifyingGlass, X } from '@phosphor-icons/react';
import { forwardRef, useMemo } from 'react';
import { SearchFilterArgs } from '@sd/client';
import { tw } from '@sd/ui';
import { useTopBarContext } from '~/app/$libraryId/TopBar/Layout';
import { useSearchContext } from './Context';
import { filterRegistry } from './Filters';
import { getSearchStore, updateFilterArgs, useSearchStore } from './store';
import { RenderIcon } from './util';
@ -30,13 +31,13 @@ export const CloseTab = forwardRef<HTMLDivElement, { onClick: () => void }>(({ o
export const AppliedOptions = () => {
const searchState = useSearchStore();
const { fixedArgs } = useTopBarContext();
const { fixedArgs } = useSearchContext();
const allArgs = useMemo(() => {
if (!fixedArgs) return [];
const value: { arg: SearchFilterArgs; removalIndex: number | null }[] = fixedArgs.map(
(arg, i) => ({
(arg) => ({
arg,
removalIndex: null
})
@ -51,8 +52,8 @@ export const AppliedOptions = () => {
);
if (fixedEquivalentIndex !== -1) {
const merged = filter.merge(
filter.extract(fixedArgs[fixedEquivalentIndex]!)!,
filter.extract(arg)
filter.extract(fixedArgs[fixedEquivalentIndex]!)! as any,
filter.extract(arg)! as any
);
value[fixedEquivalentIndex] = {

View file

@ -0,0 +1,45 @@
import { createContext, PropsWithChildren, useContext, useMemo } from 'react';
import { useTopBarContext } from '../../TopBar/Layout';
import { argsToOptions, getKey, useSearchStore } from './store';
const Context = createContext<ReturnType<typeof useContextValue> | null>(null);
function useContextValue() {
const searchState = useSearchStore();
const { fixedArgs, setFixedArgs } = useTopBarContext();
const fixedArgsAsOptions = useMemo(() => {
return fixedArgs ? argsToOptions(fixedArgs, searchState.filterOptions) : null;
}, [fixedArgs, searchState.filterOptions]);
const fixedArgsKeys = useMemo(() => {
const keys = fixedArgsAsOptions
? new Set(
fixedArgsAsOptions?.map(({ arg, filter }) => {
return getKey({
type: filter.name,
name: arg.name,
value: arg.value
});
})
)
: null;
return keys;
}, [fixedArgsAsOptions]);
return { setFixedArgs, fixedArgs, fixedArgsKeys };
}
export const SearchContextProvider = ({ children }: PropsWithChildren) => {
return <Context.Provider value={useContextValue()}>{children}</Context.Provider>;
};
export function useSearchContext() {
const ctx = useContext(Context);
if (!ctx) throw new Error('SearchContextProvider not found!');
return ctx;
}

View file

@ -2,9 +2,9 @@ import { CircleDashed, Cube, Folder, Icon, SelectionSlash, Textbox } from '@phos
import { useState } from 'react';
import { InOrNotIn, ObjectKind, SearchFilterArgs, TextMatch, useLibraryQuery } from '@sd/client';
import { Button, Input } from '@sd/ui';
import { useTopBarContext } from '~/app/$libraryId/TopBar/Layout';
import { SearchOptionItem, SearchOptionSubMenu } from '.';
import { useSearchContext } from './Context';
import { AllKeys, FilterOption, getKey, updateFilterArgs, useSearchStore } from './store';
import { FilterTypeCondition, filterTypeCondition } from './util';
@ -53,7 +53,7 @@ const FilterOptionList = ({
options: FilterOption[];
}) => {
const store = useSearchStore();
const { fixedArgsKeys } = useTopBarContext();
const { fixedArgsKeys } = useSearchContext();
return (
<SearchOptionSubMenu name={filter.name} icon={filter.icon}>
@ -88,7 +88,7 @@ const FilterOptionList = ({
if (rawArg) filter.applyAdd(arg, option);
} else {
if (!filter.applyRemove(arg, option))
args.splice(rawArgIndex);
args.splice(rawArgIndex, 1);
}
}
@ -109,7 +109,7 @@ const FilterOptionList = ({
const FilterOptionText = ({ filter }: { filter: SearchFilterCRUD }) => {
const [value, setValue] = useState('');
const { fixedArgsKeys } = useTopBarContext();
const { fixedArgsKeys } = useSearchContext();
return (
<SearchOptionSubMenu name={filter.name} icon={filter.icon}>
@ -140,9 +140,9 @@ const FilterOptionText = ({ filter }: { filter: SearchFilterCRUD }) => {
};
const FilterOptionBoolean = ({ filter }: { filter: SearchFilterCRUD }) => {
const { filterArgs, filterArgsKeys } = useSearchStore();
const { filterArgsKeys } = useSearchStore();
const { fixedArgsKeys } = useTopBarContext();
const { fixedArgsKeys } = useSearchContext();
const key = getKey({
type: filter.name,
@ -161,12 +161,10 @@ const FilterOptionBoolean = ({ filter }: { filter: SearchFilterCRUD }) => {
const index = args.findIndex((f) => filter.extract(f) !== undefined);
if (index !== -1) {
args.splice(index);
args.splice(index, 1);
} else {
const arg = filter.create(true);
args.push(arg);
filter.applyAdd(arg, { name: filter.name, value: true });
}
return args;
@ -196,6 +194,7 @@ function createInOrNotInFilter<T extends string | number>(
| 'applyAdd'
| 'applyRemove'
| 'create'
| 'merge'
> & {
create(value: InOrNotIn<T>): SearchFilterArgs;
argsToOptions(values: T[], options: Map<string, FilterOption[]>): FilterOption[];
@ -206,7 +205,7 @@ function createInOrNotInFilter<T extends string | number>(
create: (data) => {
if (typeof data === 'number' || typeof data === 'string')
return filter.create({
in: [data]
in: [data as any]
});
else if (data) return filter.create(data);
else return filter.create({ in: [] });
@ -288,6 +287,7 @@ function createTextMatchFilter(
| 'applyAdd'
| 'applyRemove'
| 'create'
| 'merge'
> & {
create(value: TextMatch): SearchFilterArgs;
}
@ -361,9 +361,7 @@ function createTextMatchFilter(
else if ('equals' in data) return { equals: value };
},
applyRemove: () => undefined,
merge: (left, right) => {
return left;
}
merge: (left) => left
};
}
@ -379,6 +377,7 @@ function createBooleanFilter(
| 'applyAdd'
| 'applyRemove'
| 'create'
| 'merge'
> & {
create(value: boolean): SearchFilterArgs;
}
@ -416,9 +415,7 @@ function createBooleanFilter(
return value;
},
applyRemove: () => undefined,
merge: (left, right) => {
return left;
}
merge: (left) => left
};
}

View file

@ -3,10 +3,10 @@ import { IconTypes } from '@sd/assets/util';
import clsx from 'clsx';
import { memo, PropsWithChildren, useDeferredValue, useState } from 'react';
import { Button, ContextMenuDivItem, DropdownMenu, Input, RadixCheckbox, tw } from '@sd/ui';
import { useTopBarContext } from '~/app/$libraryId/TopBar/Layout';
import { useKeybind } from '~/hooks';
import { AppliedOptions } from './AppliedFilters';
import { useSearchContext } from './Context';
import { filterRegistry } from './Filters';
import { useSavedSearches } from './SavedSearches';
import {
@ -195,7 +195,7 @@ const SearchOptions = () => {
export default SearchOptions;
const SearchResults = memo(({ search }: { search: string }) => {
const { fixedArgsKeys } = useTopBarContext();
const { fixedArgsKeys } = useSearchContext();
const searchState = useSearchStore();
const searchResults = useSearchRegisteredFilters(search);
@ -215,26 +215,23 @@ const SearchResults = memo(({ search }: { search: string }) => {
updateFilterArgs((args) => {
if (fixedArgsKeys?.has(option.key)) return args;
let rawArg = args.find((arg) => filter.extract(arg));
const rawArg = args.find((arg) => filter.extract(arg));
if (!rawArg) {
rawArg = filter.create(option.value);
args.push(rawArg);
}
const arg = filter.create(option.value);
args.push(arg);
} else {
const rawArgIndex = args.findIndex((arg) =>
filter.extract(arg)
)!;
const rawArgIndex = args.findIndex((arg) => filter.extract(arg))!;
const arg = filter.extract(rawArg)! as any;
const arg = filter.extract(rawArg)! as any;
if (!filter.getCondition?.(arg))
filter.setCondition(
arg,
Object.keys(filter.conditions)[0] as any as never
);
if (value) filter.applyAdd(arg, option);
else {
if (!filter.applyRemove(arg, option)) args.splice(rawArgIndex);
if (value) filter.applyAdd(arg, option);
else {
if (!filter.applyRemove(arg, option))
args.splice(rawArgIndex, 1);
}
}
return args;

View file

@ -7,6 +7,7 @@ import { proxyMap } from 'valtio/utils';
import { SearchFilterArgs } from '@sd/client';
import { useTopBarContext } from '~/app/$libraryId/TopBar/Layout';
import { useSearchContext } from './Context';
import { filterRegistry, FilterType, RenderSearchFilter } from './Filters';
import { FilterTypeCondition } from './util';
@ -43,7 +44,7 @@ const searchStore = proxy({
searchQuery: null as string | null,
filterArgs: ref([] as SearchFilterArgs[]),
filterArgsKeys: ref(new Set<string>()),
filterOptions: ref(new Map<string, FilterOption[]>()),
filterOptions: ref(new Map<string, FilterOptionWithType[]>()),
// we register filters so we can search them
registeredFilters: proxyMap() as Map<string, FilterOptionWithType>
});
@ -53,7 +54,7 @@ export function useSearchFilters<T extends SearchType>(
fixedArgs: SearchFilterArgs[]
) {
const { filterArgs } = useSearchStore();
const topBar = useTopBarContext();
const topBar = useSearchContext();
// don't want the search bar to pop in after the top bar has loaded!
useLayoutEffect(() => {

View file

@ -1,6 +1,5 @@
import { FolderNotchOpen } from '@phosphor-icons/react';
import { CSSProperties, type PropsWithChildren, type ReactNode } from 'react';
import { SlideDown } from 'react-slidedown';
import { useKeys } from 'rooks';
import { getExplorerLayoutStore, useExplorerLayoutStore, useLibrarySubscription } from '@sd/client';
import { useKeysMatcher, useOperatingSystem } from '~/hooks';
@ -11,15 +10,15 @@ import ContextMenu from './ContextMenu';
import DismissibleNotice from './DismissibleNotice';
import { Inspector, INSPECTOR_WIDTH } from './Inspector';
import ExplorerContextMenu from './ParentContextMenu';
import SearchOptions from './Search';
import { useExplorerStore } from './store';
import { useKeyRevealFinder } from './useKeyRevealFinder';
import View, { EmptyNotice, ExplorerViewProps } from './View';
import { ExplorerPath, PATH_BAR_HEIGHT } from './View/ExplorerPath';
import SearchOptions from './View/SearchOptions';
import 'react-slidedown/lib/slidedown.css';
import { useSearchStore } from './View/SearchOptions/store';
import { useSearchStore } from './Search/store';
interface Props {
emptyNotice?: ExplorerViewProps['emptyNotice'];

View file

@ -1,4 +1,4 @@
import { ArrowsOutSimple, EjectSimple, X } from '@phosphor-icons/react';
import { EjectSimple, X } from '@phosphor-icons/react';
import { Laptop } from '@sd/assets/icons';
import clsx from 'clsx';
import { useEffect, useState } from 'react';
@ -15,7 +15,7 @@ import { Button, Tooltip } from '@sd/ui';
import { AddLocationButton } from '~/app/$libraryId/settings/library/locations/AddLocationButton';
import { Folder, SubtleButton } from '~/components';
import { useSavedSearches } from '../../Explorer/View/SearchOptions/SavedSearches';
import { useSavedSearches } from '../../Explorer/Search/SavedSearches';
import SidebarLink from './Link';
import LocationsContextMenu from './LocationsContextMenu';
import Section from './Section';

View file

@ -1,37 +1,15 @@
import { createContext, useContext, useMemo, useState } from 'react';
import { createContext, useContext, useState } from 'react';
import { Outlet } from 'react-router';
import { SearchFilterArgs } from '@sd/client';
import TopBar from '.';
import { argsToOptions, getKey, useSearchStore } from '../Explorer/View/SearchOptions/store';
const TopBarContext = createContext<ReturnType<typeof useContextValue> | null>(null);
function useContextValue(props: { left: HTMLDivElement | null; right: HTMLDivElement | null }) {
const [fixedArgs, setFixedArgs] = useState<SearchFilterArgs[] | null>(null);
const searchState = useSearchStore();
const fixedArgsAsOptions = useMemo(() => {
return fixedArgs ? argsToOptions(fixedArgs, searchState.filterOptions) : null;
}, [fixedArgs, searchState.filterOptions]);
const fixedArgsKeys = useMemo(() => {
const keys = fixedArgsAsOptions
? new Set(
fixedArgsAsOptions?.map(({ arg, filter }) => {
return getKey({
type: filter.name,
name: arg.name,
value: arg.value
});
})
)
: null;
return keys;
}, [fixedArgsAsOptions]);
return { ...props, setFixedArgs, fixedArgs, fixedArgsKeys };
return { ...props, fixedArgs, setFixedArgs };
}
export const Component = () => {

View file

@ -1,13 +1,13 @@
import clsx from 'clsx';
import { useCallback, useEffect, useRef, useState, useTransition } from 'react';
import { useLocation, useNavigate, useResolvedPath } from 'react-router';
import { useLocation, useResolvedPath } from 'react-router';
import { useDebouncedCallback } from 'use-debounce';
import { Input, ModifierKeys, Shortcut } from '@sd/ui';
import { SearchParamsSchema } from '~/app/route-schemas';
import { useOperatingSystem, useZodSearchParams } from '~/hooks';
import { keybindForOs } from '~/util/keybinds';
import { getSearchStore, useSearchStore } from '../Explorer/View/SearchOptions/store';
import { getSearchStore, useSearchStore } from '../Explorer/Search/store';
export default () => {
const searchRef = useRef<HTMLInputElement>(null);

View file

@ -24,13 +24,13 @@ import { useKeyDeleteFile, useZodRouteParams } from '~/hooks';
import Explorer from '../Explorer';
import { ExplorerContextProvider } from '../Explorer/Context';
import { usePathsInfiniteQuery } from '../Explorer/queries';
import { SearchContextProvider } from '../Explorer/Search/Context';
import { useSearchFilters } from '../Explorer/Search/store';
import { createDefaultExplorerSettings, filePathOrderingKeysSchema } from '../Explorer/store';
import { DefaultTopBarOptions } from '../Explorer/TopBarOptions';
import { useExplorer, UseExplorerSettings, useExplorerSettings } from '../Explorer/useExplorer';
import { useExplorerSearchParams } from '../Explorer/util';
import { EmptyNotice } from '../Explorer/View';
import { FilterType } from '../Explorer/View/SearchOptions/Filters';
import { useSearchFilters } from '../Explorer/View/SearchOptions/store';
import { TopBarPortal } from '../TopBar/Portal';
import LocationOptions from './LocationOptions';
@ -43,9 +43,11 @@ export const Component = () => {
});
return (
<Suspense>
<LocationExplorer path={path} location={location.data!} />)
</Suspense>
<SearchContextProvider>
<Suspense>
<LocationExplorer path={path} location={location.data!} />)
</Suspense>
</SearchContextProvider>
);
};

View file

@ -1,27 +1,29 @@
import { getIcon, iconNames } from '@sd/assets/util';
import { useCallback, useMemo } from 'react';
import {
ObjectFilterArgs,
ObjectKindEnum,
ObjectOrder,
Tag,
useLibraryContext,
useLibraryQuery
} from '@sd/client';
import { ObjectKindEnum, ObjectOrder, Tag, useLibraryContext, useLibraryQuery } from '@sd/client';
import { LocationIdParamsSchema } from '~/app/route-schemas';
import { useZodRouteParams } from '~/hooks';
import Explorer from '../Explorer';
import { ExplorerContextProvider } from '../Explorer/Context';
import { useObjectsInfiniteQuery } from '../Explorer/queries';
import { SearchContextProvider } from '../Explorer/Search/Context';
import { useSearchFilters } from '../Explorer/Search/store';
import { createDefaultExplorerSettings, objectOrderingKeysSchema } from '../Explorer/store';
import { DefaultTopBarOptions } from '../Explorer/TopBarOptions';
import { useExplorer, UseExplorerSettings, useExplorerSettings } from '../Explorer/useExplorer';
import { EmptyNotice } from '../Explorer/View';
import { useSearchFilters } from '../Explorer/View/SearchOptions/store';
import { TopBarPortal } from '../TopBar/Portal';
export const Component = () => {
return (
<SearchContextProvider>
<Inner />
</SearchContextProvider>
);
};
function Inner() {
const { id: tagId } = useZodRouteParams(LocationIdParamsSchema);
const tag = useLibraryQuery(['tags.get', tagId], { suspense: true });
@ -78,7 +80,7 @@ export const Component = () => {
/>
</ExplorerContextProvider>
);
};
}
function useItems({ tag, settings }: { tag: Tag; settings: UseExplorerSettings<ObjectOrder> }) {
const { library } = useLibraryContext();

View file

@ -2,7 +2,7 @@ import { MouseEvent } from 'react';
import { useNavigate } from 'react-router';
import { useOperatingSystem } from '~/hooks';
import { useSearchStore } from '../app/$libraryId/Explorer/View/SearchOptions/store';
import { useSearchStore } from '../app/$libraryId/Explorer/Search/store';
export const useMouseNavigate = () => {
const idx = history.state.idx as number;