mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-07 04:23:29 +00:00
broken fixed filters but working inNotIn filters
This commit is contained in:
parent
f3d2a4feb6
commit
ffe3b2e9df
|
@ -146,7 +146,7 @@ pub fn mount() -> AlphaRouter<Ctx> {
|
|||
#[specta(optional)]
|
||||
order_and_pagination: Option<file_path::OrderAndPagination>,
|
||||
#[serde(default)]
|
||||
filter: Vec<SearchFilterArgs>,
|
||||
filters: Vec<SearchFilterArgs>,
|
||||
#[serde(default = "default_group_directories")]
|
||||
group_directories: bool,
|
||||
}
|
||||
|
@ -160,7 +160,7 @@ pub fn mount() -> AlphaRouter<Ctx> {
|
|||
FilePathSearchArgs {
|
||||
take,
|
||||
order_and_pagination,
|
||||
filter,
|
||||
filters,
|
||||
group_directories,
|
||||
}| async move {
|
||||
let Library { db, .. } = library.as_ref();
|
||||
|
@ -168,7 +168,7 @@ pub fn mount() -> AlphaRouter<Ctx> {
|
|||
let mut query = db.file_path().find_many({
|
||||
let mut params = Vec::new();
|
||||
|
||||
for filter in filter {
|
||||
for filter in filters {
|
||||
params.extend(filter.into_file_path_params(db).await?);
|
||||
}
|
||||
|
||||
|
@ -230,16 +230,24 @@ pub fn mount() -> AlphaRouter<Ctx> {
|
|||
#[specta(inline)]
|
||||
struct Args {
|
||||
#[specta(default)]
|
||||
filter: SearchFilterArgs,
|
||||
filters: Vec<SearchFilterArgs>,
|
||||
}
|
||||
|
||||
R.with2(library())
|
||||
.query(|(_, library), Args { filter }| async move {
|
||||
.query(|(_, library), Args { filters }| async move {
|
||||
let Library { db, .. } = library.as_ref();
|
||||
|
||||
Ok(db
|
||||
.file_path()
|
||||
.count(filter.into_file_path_params(db).await?)
|
||||
.count({
|
||||
let mut params = Vec::new();
|
||||
|
||||
for filter in filters {
|
||||
params.extend(filter.into_file_path_params(db).await?);
|
||||
}
|
||||
|
||||
params
|
||||
})
|
||||
.exec()
|
||||
.await? as u32)
|
||||
})
|
||||
|
@ -252,7 +260,7 @@ pub fn mount() -> AlphaRouter<Ctx> {
|
|||
#[specta(optional)]
|
||||
order_and_pagination: Option<object::OrderAndPagination>,
|
||||
#[serde(default)]
|
||||
filter: Vec<SearchFilterArgs>,
|
||||
filters: Vec<SearchFilterArgs>,
|
||||
}
|
||||
|
||||
R.with2(library()).query(
|
||||
|
@ -260,7 +268,7 @@ pub fn mount() -> AlphaRouter<Ctx> {
|
|||
ObjectSearchArgs {
|
||||
take,
|
||||
order_and_pagination,
|
||||
filter,
|
||||
filters,
|
||||
}| async move {
|
||||
let Library { db, .. } = library.as_ref();
|
||||
|
||||
|
@ -271,7 +279,7 @@ pub fn mount() -> AlphaRouter<Ctx> {
|
|||
.find_many({
|
||||
let mut params = Vec::new();
|
||||
|
||||
for filter in filter {
|
||||
for filter in filters {
|
||||
params.extend(filter.into_object_params(db).await?);
|
||||
}
|
||||
|
||||
|
@ -335,11 +343,11 @@ pub fn mount() -> AlphaRouter<Ctx> {
|
|||
#[specta(inline)]
|
||||
struct Args {
|
||||
#[serde(default)]
|
||||
filter: Vec<SearchFilterArgs>,
|
||||
filters: Vec<SearchFilterArgs>,
|
||||
}
|
||||
|
||||
R.with2(library())
|
||||
.query(|(_, library), Args { filter }| async move {
|
||||
.query(|(_, library), Args { filters }| async move {
|
||||
let Library { db, .. } = library.as_ref();
|
||||
|
||||
Ok(db
|
||||
|
@ -347,7 +355,7 @@ pub fn mount() -> AlphaRouter<Ctx> {
|
|||
.count({
|
||||
let mut params = Vec::new();
|
||||
|
||||
for filter in filter {
|
||||
for filter in filters {
|
||||
params.extend(filter.into_object_params(db).await?);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import { MagnifyingGlass, X } from '@phosphor-icons/react';
|
||||
import { produce } from 'immer';
|
||||
import { forwardRef, useMemo } from 'react';
|
||||
import { ref, snapshot } from 'valtio';
|
||||
import { tw } from '@sd/ui';
|
||||
|
||||
import { filterRegistry } from './Filters';
|
||||
import {
|
||||
deselectFilterOption,
|
||||
getKey,
|
||||
getSearchStore,
|
||||
getSelectedFiltersGrouped,
|
||||
useSearchStore
|
||||
|
@ -23,7 +26,7 @@ const CloseTab = forwardRef<HTMLDivElement, { onClick: () => void }>(({ onClick
|
|||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className="flex h-full items-center rounded-r border-l border-app-darkerBox/70 px-1.5 py-0.5 text-sm hover:bg-app-lightBox/30"
|
||||
className="border-app-darkerBox/70 flex h-full items-center rounded-r border-l px-1.5 py-0.5 text-sm hover:bg-app-lightBox/30"
|
||||
onClick={onClick}
|
||||
>
|
||||
<RenderIcon className="h-3 w-3" icon={X} />
|
||||
|
@ -32,83 +35,103 @@ const CloseTab = forwardRef<HTMLDivElement, { onClick: () => void }>(({ onClick
|
|||
});
|
||||
|
||||
export const AppliedOptions = () => {
|
||||
const searchStore = useSearchStore();
|
||||
const searchState = useSearchStore();
|
||||
|
||||
// turn the above into use memo
|
||||
const groupedFilters = useMemo(
|
||||
() => getSelectedFiltersGrouped(),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[searchStore.selectedFilters.size]
|
||||
[searchState.selectedFilters.size]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-row gap-2">
|
||||
{searchStore.searchQuery && (
|
||||
{searchState.searchQuery && (
|
||||
<FilterContainer>
|
||||
<StaticSection>
|
||||
<RenderIcon className="h-4 w-4" icon={MagnifyingGlass} />
|
||||
<FilterText>{searchStore.searchQuery}</FilterText>
|
||||
<FilterText>{searchState.searchQuery}</FilterText>
|
||||
</StaticSection>
|
||||
<CloseTab onClick={() => (getSearchStore().searchQuery = null)} />
|
||||
</FilterContainer>
|
||||
)}
|
||||
{groupedFilters?.map((group) => {
|
||||
const showRemoveButton = group.filters.some((filter) => filter.canBeRemoved);
|
||||
const meta = filterRegistry.find((f) => f.name === group.type);
|
||||
{searchState.filterArgs.map((arg, index) => {
|
||||
const filter = filterRegistry.find((f) => f.find(arg));
|
||||
if (!filter) return;
|
||||
|
||||
const options = searchState.filterOptions.get(filter);
|
||||
if (!options) return;
|
||||
|
||||
const isFixed = searchState.fixedFilters.at(index) !== undefined;
|
||||
|
||||
const activeOptions =
|
||||
filter.getActiveOptions &&
|
||||
filter.getActiveOptions(filter.find(arg)! as any, options);
|
||||
|
||||
return (
|
||||
<FilterContainer key={group.type}>
|
||||
<FilterContainer key={`${filter.name}-${index}`}>
|
||||
<StaticSection>
|
||||
<RenderIcon className="h-4 w-4" icon={meta?.icon} />
|
||||
<FilterText>{meta?.name}</FilterText>
|
||||
<RenderIcon className="h-4 w-4" icon={filter.icon} />
|
||||
<FilterText>{filter.name}</FilterText>
|
||||
</StaticSection>
|
||||
{meta?.conditions && (
|
||||
<InteractiveSection className="border-l">
|
||||
{/* {Object.values(meta.conditions).map((condition) => (
|
||||
<div key={condition}>{condition}</div>
|
||||
))} */}
|
||||
|
||||
is
|
||||
</InteractiveSection>
|
||||
)}
|
||||
|
||||
<InteractiveSection className="gap-1 border-l border-app-darkerBox/70 py-0.5 pl-1.5 pr-2 text-sm">
|
||||
{group.filters.length > 1 && (
|
||||
<div
|
||||
className="relative"
|
||||
style={{ width: `${group.filters.length * 12}px` }}
|
||||
>
|
||||
{group.filters.map((filter, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="absolute -top-2 left-0"
|
||||
style={{
|
||||
zIndex: group.filters.length - index,
|
||||
left: `${index * 10}px`
|
||||
}}
|
||||
>
|
||||
<RenderIcon className="h-4 w-4" icon={filter.icon} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{group.filters.length === 1 && (
|
||||
<RenderIcon className="h-4 w-4" icon={group.filters[0]?.icon} />
|
||||
)}
|
||||
{group.filters.length > 1
|
||||
? `${group.filters.length} ${pluralize(meta?.name)}`
|
||||
: group.filters[0]?.name}
|
||||
<InteractiveSection className="border-l">
|
||||
{/* {Object.entries(filter.conditions).map(([value, displayName]) => (
|
||||
<div key={value}>{displayName}</div>
|
||||
))} */}
|
||||
in
|
||||
</InteractiveSection>
|
||||
|
||||
{showRemoveButton && (
|
||||
<InteractiveSection className="border-app-darkerBox/70 gap-1 border-l py-0.5 pl-1.5 pr-2 text-sm">
|
||||
{activeOptions && (
|
||||
<>
|
||||
{activeOptions.length === 1 ? (
|
||||
<RenderIcon
|
||||
className="h-4 w-4"
|
||||
icon={activeOptions[0]!.icon}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className="relative"
|
||||
style={{ width: `${activeOptions.length * 12}px` }}
|
||||
>
|
||||
{activeOptions.map((option, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="absolute -top-2 left-0"
|
||||
style={{
|
||||
zIndex: activeOptions.length - index,
|
||||
left: `${index * 10}px`
|
||||
}}
|
||||
>
|
||||
<RenderIcon
|
||||
className="h-4 w-4"
|
||||
icon={option.icon}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{activeOptions.length > 1
|
||||
? `${activeOptions.length} ${pluralize(filter.name)}`
|
||||
: activeOptions[0]?.name}
|
||||
</>
|
||||
)}
|
||||
{/* {group.filters.length > 1
|
||||
? `${group.filters.length} ${pluralize(meta?.name)}`
|
||||
: group.filters[0]?.name} */}
|
||||
</InteractiveSection>
|
||||
|
||||
{!isFixed && (
|
||||
<CloseTab
|
||||
onClick={() =>
|
||||
group.filters.forEach((filter) => {
|
||||
if (filter.canBeRemoved) {
|
||||
deselectFilterOption(filter);
|
||||
}
|
||||
})
|
||||
}
|
||||
onClick={() => {
|
||||
getSearchStore().filterArgs = ref(
|
||||
produce(getSearchStore().filterArgs, (args) => {
|
||||
args.splice(index);
|
||||
|
||||
return args;
|
||||
})
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</FilterContainer>
|
||||
|
@ -122,3 +145,67 @@ function pluralize(word?: string) {
|
|||
if (word?.endsWith('s')) return word;
|
||||
return `${word}s`;
|
||||
}
|
||||
|
||||
// {
|
||||
// groupedFilters?.map((group) => {
|
||||
// const showRemoveButton = group.filters.some((filter) => filter.canBeRemoved);
|
||||
// const meta = filterRegistry.find((f) => f.name === group.type);
|
||||
|
||||
// return (
|
||||
// <FilterContainer key={group.type}>
|
||||
// <StaticSection>
|
||||
// <RenderIcon className="h-4 w-4" icon={meta?.icon} />
|
||||
// <FilterText>{meta?.name}</FilterText>
|
||||
// </StaticSection>
|
||||
// {meta?.conditions && (
|
||||
// <InteractiveSection className="border-l">
|
||||
// {/* {Object.values(meta.conditions).map((condition) => (
|
||||
// <div key={condition}>{condition}</div>
|
||||
// ))} */}
|
||||
// is
|
||||
// </InteractiveSection>
|
||||
// )}
|
||||
|
||||
// <InteractiveSection className="border-app-darkerBox/70 gap-1 border-l py-0.5 pl-1.5 pr-2 text-sm">
|
||||
// {group.filters.length > 1 && (
|
||||
// <div
|
||||
// className="relative"
|
||||
// style={{ width: `${group.filters.length * 12}px` }}
|
||||
// >
|
||||
// {group.filters.map((filter, index) => (
|
||||
// <div
|
||||
// key={index}
|
||||
// className="absolute -top-2 left-0"
|
||||
// style={{
|
||||
// zIndex: group.filters.length - index,
|
||||
// left: `${index * 10}px`
|
||||
// }}
|
||||
// >
|
||||
// <RenderIcon className="h-4 w-4" icon={filter.icon} />
|
||||
// </div>
|
||||
// ))}
|
||||
// </div>
|
||||
// )}
|
||||
// {group.filters.length === 1 && (
|
||||
// <RenderIcon className="h-4 w-4" icon={group.filters[0]?.icon} />
|
||||
// )}
|
||||
// {group.filters.length > 1
|
||||
// ? `${group.filters.length} ${pluralize(meta?.name)}`
|
||||
// : group.filters[0]?.name}
|
||||
// </InteractiveSection>
|
||||
|
||||
// {showRemoveButton && (
|
||||
// <CloseTab
|
||||
// onClick={() =>
|
||||
// group.filters.forEach((filter) => {
|
||||
// if (filter.canBeRemoved) {
|
||||
// deselectFilterOption(filter);
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// />
|
||||
// )}
|
||||
// </FilterContainer>
|
||||
// );
|
||||
// });
|
||||
// }
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { CircleDashed, Cube, Folder, Icon, SelectionSlash, Textbox } from '@phosphor-icons/react';
|
||||
import { produce } from 'immer';
|
||||
import { useState } from 'react';
|
||||
import { ref, snapshot } from 'valtio';
|
||||
import { InOrNotIn, ObjectKind, SearchFilterArgs, TextMatch, useLibraryQuery } from '@sd/client';
|
||||
import { Button, Input } from '@sd/ui';
|
||||
|
||||
|
@ -8,7 +9,7 @@ import { SearchOptionItem, SearchOptionSubMenu } from '.';
|
|||
import {
|
||||
AllKeys,
|
||||
deselectFilterOption,
|
||||
FilterArgs,
|
||||
FilterOption,
|
||||
getSearchStore,
|
||||
selectFilterOption,
|
||||
SetFilter,
|
||||
|
@ -30,9 +31,10 @@ interface SearchFilterCRUD<
|
|||
> extends SearchFilter<TConditions> {
|
||||
getCondition: (args: T) => keyof TConditions | undefined;
|
||||
setCondition: (args: T, condition: keyof TConditions) => void;
|
||||
getOptionActive: (args: T, option: FilterArgs) => boolean;
|
||||
applyAdd: (args: T, option: FilterArgs) => void;
|
||||
applyRemove: (args: T, option: FilterArgs) => T | undefined;
|
||||
getOptionActive: (args: T, option: FilterOption) => boolean;
|
||||
getActiveOptions?: (args: T, allOptions: FilterOption[]) => FilterOption[];
|
||||
applyAdd: (args: T, option: FilterOption) => void;
|
||||
applyRemove: (args: T, option: FilterOption) => T | undefined;
|
||||
find: (arg: SearchFilterArgs) => T | undefined;
|
||||
create: () => SearchFilterArgs;
|
||||
}
|
||||
|
@ -44,10 +46,10 @@ export interface RenderSearchFilter<
|
|||
// Render is responsible for fetching the filter options and rendering them
|
||||
Render: (props: {
|
||||
filter: SearchFilterCRUD<TConditions>;
|
||||
options: (FilterArgs & { type: string })[];
|
||||
options: (FilterOption & { type: string })[];
|
||||
}) => JSX.Element;
|
||||
// Apply is responsible for applying the filter to the search args
|
||||
useOptions: (props: { search: string }) => FilterArgs[];
|
||||
useOptions: (props: { search: string }) => FilterOption[];
|
||||
}
|
||||
|
||||
const FilterOptionList = ({
|
||||
|
@ -55,26 +57,47 @@ const FilterOptionList = ({
|
|||
options
|
||||
}: {
|
||||
filter: SearchFilterCRUD;
|
||||
options: FilterArgs[];
|
||||
options: FilterOption[];
|
||||
}) => {
|
||||
const store = useSearchStore();
|
||||
|
||||
const arg = store.filterArgs.find(filter.find);
|
||||
const specificArg = arg ? filter.find(arg) : undefined;
|
||||
|
||||
return (
|
||||
<SearchOptionSubMenu name={filter.name} icon={filter.icon}>
|
||||
{options?.map((option) => (
|
||||
<SearchOptionItem
|
||||
selected={filter.getOptionActive?.(store.filterArgs, option) ?? false}
|
||||
selected={
|
||||
(specificArg && filter.getOptionActive?.(specificArg, option)) ?? false
|
||||
}
|
||||
setSelected={(value) => {
|
||||
getSearchStore().filterArgs = produce(store.filterArgs, (args) => {
|
||||
if (!filter.getCondition?.(args))
|
||||
filter.setCondition(args, Object.keys(filter.conditions)[0]!);
|
||||
getSearchStore().filterArgs = ref(
|
||||
produce(store.filterArgs, (args) => {
|
||||
let rawArg = args.find((arg) => filter.find(arg));
|
||||
|
||||
if (value) filter.applyAdd(args, option);
|
||||
else filter.applyRemove(args, option);
|
||||
});
|
||||
if (!rawArg) {
|
||||
rawArg = filter.create();
|
||||
args.push(rawArg);
|
||||
}
|
||||
|
||||
if (value) selectFilterOption({ ...option, type: filter.name });
|
||||
else deselectFilterOption({ ...option, type: filter.name });
|
||||
const rawArgIndex = args.findIndex((arg) => filter.find(arg))!;
|
||||
|
||||
const arg = filter.find(rawArg)!;
|
||||
|
||||
if (!filter.getCondition?.(arg))
|
||||
filter.setCondition(arg, Object.keys(filter.conditions)[0]!);
|
||||
|
||||
if (value) filter.applyAdd(arg, option);
|
||||
else filter.applyRemove(arg, option);
|
||||
|
||||
if (!filter.getActiveOptions?.(arg, options).length) {
|
||||
args.splice(rawArgIndex);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
console.log(snapshot(getSearchStore()).filterArgs);
|
||||
}}
|
||||
key={option.value}
|
||||
icon={option.icon}
|
||||
|
@ -110,7 +133,7 @@ function createFilter<TConditions extends FilterTypeCondition[keyof FilterTypeCo
|
|||
return filter;
|
||||
}
|
||||
|
||||
function createInOrNotInFilter<T>(
|
||||
function createInOrNotInFilter<T extends string | number>(
|
||||
filter: Omit<
|
||||
ReturnType<typeof createFilter<any, InOrNotIn<T>>>,
|
||||
| 'conditions'
|
||||
|
@ -143,6 +166,14 @@ function createInOrNotInFilter<T>(
|
|||
if ('in' in data) return data.in.includes(option.value);
|
||||
else return data.notIn.includes(option.value);
|
||||
},
|
||||
getActiveOptions: (data, options) => {
|
||||
let value: T[];
|
||||
|
||||
if ('in' in data) value = data.in;
|
||||
else value = data.notIn;
|
||||
|
||||
return value.map((v) => options.find((o) => o.value === v)!).filter(Boolean);
|
||||
},
|
||||
applyAdd: (data, option) => {
|
||||
if ('in' in data) data.in.push(option.value);
|
||||
else data.notIn.push(option.value);
|
||||
|
|
|
@ -95,7 +95,7 @@ const SearchOptions = () => {
|
|||
.map((o) => ({ ...o, type: filter.name }));
|
||||
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
useRegisterSearchFilterOptions(options);
|
||||
useRegisterSearchFilterOptions(filter, options);
|
||||
|
||||
return [filter, options] as const;
|
||||
});
|
||||
|
@ -108,7 +108,7 @@ const SearchOptions = () => {
|
|||
onMouseLeave={() => {
|
||||
getSearchStore().interactingWithSearchOptions = false;
|
||||
}}
|
||||
className="flex h-[45px] w-full flex-row items-center gap-4 border-b border-app-line/50 bg-app-darkerBox/90 px-4 backdrop-blur"
|
||||
className="bg-app-darkerBox/90 flex h-[45px] w-full flex-row items-center gap-4 border-b border-app-line/50 px-4 backdrop-blur"
|
||||
>
|
||||
{/* <OptionContainer className="flex flex-row items-center">
|
||||
<FilterContainer>
|
||||
|
@ -168,7 +168,7 @@ const SearchOptions = () => {
|
|||
: filtersWithOptions.map(([filter, options]) => (
|
||||
<filter.Render
|
||||
key={filter.name}
|
||||
filter={filter}
|
||||
filter={filter as any}
|
||||
options={options}
|
||||
/>
|
||||
))}
|
||||
|
|
|
@ -4,20 +4,20 @@ import { proxy, ref, useSnapshot } from 'valtio';
|
|||
import { proxyMap } from 'valtio/utils';
|
||||
import { SearchFilterArgs } from '@sd/client';
|
||||
|
||||
import { filterRegistry, FilterType } from './Filters';
|
||||
import { FilterType, RenderSearchFilter } from './Filters';
|
||||
import { FilterTypeCondition } from './util';
|
||||
|
||||
export type SearchType = 'paths' | 'objects';
|
||||
|
||||
export type SearchScope = 'directory' | 'location' | 'device' | 'library';
|
||||
|
||||
export interface FilterArgs {
|
||||
export interface FilterOption {
|
||||
value: string | any;
|
||||
name: string;
|
||||
icon?: string; // "Folder" or "#efefef"
|
||||
}
|
||||
|
||||
export interface Filter extends FilterArgs {
|
||||
export interface Filter extends FilterOption {
|
||||
type: FilterType;
|
||||
}
|
||||
|
||||
|
@ -39,13 +39,15 @@ const searchStore = proxy({
|
|||
searchType: 'paths' as SearchType,
|
||||
searchQuery: null as string | null,
|
||||
filterArgs: ref([] as SearchFilterArgs[]),
|
||||
fixedFilters: ref([] as SearchFilterArgs[]),
|
||||
filterOptions: ref(new Map<RenderSearchFilter, FilterOption[]>()),
|
||||
// we register filters so we can search them
|
||||
registeredFilters: proxyMap() as Map<string, Filter>,
|
||||
// selected filters are applied to the search args
|
||||
selectedFilters: proxyMap() as Map<string, SetFilter>
|
||||
});
|
||||
|
||||
export const useSearchFilters = <T extends SearchType>(
|
||||
export const useSearchFiltersOld = <T extends SearchType>(
|
||||
searchType: T,
|
||||
fixedFilters?: Filter[]
|
||||
): SearchFilterArgs => {
|
||||
|
@ -72,15 +74,45 @@ export const useSearchFilters = <T extends SearchType>(
|
|||
return filters;
|
||||
};
|
||||
|
||||
export function useSearchFilters<T extends SearchType>(
|
||||
searchType: T,
|
||||
fixedFilters: SearchFilterArgs[]
|
||||
) {
|
||||
const state = useSearchStore();
|
||||
|
||||
useEffect(() => {
|
||||
resetSearchStore();
|
||||
searchStore.fixedFilters = ref(fixedFilters);
|
||||
searchStore.filterArgs = ref(fixedFilters);
|
||||
}, [fixedFilters]);
|
||||
|
||||
return [...state.filterArgs];
|
||||
}
|
||||
|
||||
// this makes the filter unique and easily searchable using .includes
|
||||
export const getKey = (filter: Filter) => `${filter.type}-${filter.name}-${filter.value}`;
|
||||
|
||||
// this hook allows us to register filters to the search store
|
||||
// and returns the filters with the correct type
|
||||
export const useRegisterSearchFilterOptions = (filters?: (FilterArgs & { type: FilterType })[]) => {
|
||||
export const useRegisterSearchFilterOptions = (
|
||||
filter: RenderSearchFilter,
|
||||
options: (FilterOption & { type: FilterType })[]
|
||||
) => {
|
||||
useEffect(
|
||||
() => {
|
||||
const keys = filters?.map((filter) => {
|
||||
if (options) {
|
||||
searchStore.filterOptions.set(filter, options);
|
||||
return () => {
|
||||
searchStore.filterOptions.delete(filter);
|
||||
};
|
||||
}
|
||||
},
|
||||
options?.map(getKey) ?? []
|
||||
);
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
const keys = options?.map((filter) => {
|
||||
const key = getKey(filter);
|
||||
|
||||
if (!searchStore.registeredFilters.has(key)) {
|
||||
|
@ -95,7 +127,7 @@ export const useRegisterSearchFilterOptions = (filters?: (FilterArgs & { type: F
|
|||
if (key) searchStore.registeredFilters.delete(key);
|
||||
});
|
||||
},
|
||||
filters?.map(getKey) ?? []
|
||||
options?.map(getKey) ?? []
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -192,33 +192,40 @@ const useItems = ({
|
|||
|
||||
const explorerSettings = settings.useSettingsSnapshot();
|
||||
|
||||
const filter = useSearchFilters(
|
||||
'paths',
|
||||
useMemo(
|
||||
() => [
|
||||
{ filePath: { locations: { in: [location.id] } } },
|
||||
...(explorerSettings.layoutMode === 'media'
|
||||
? [{ object: { kind: { in: [ObjectKindEnum.Image, ObjectKindEnum.Video] } } }]
|
||||
: [])
|
||||
],
|
||||
[location.id, explorerSettings.layoutMode]
|
||||
)
|
||||
// useMemo lets us embrace immutability and use fixedFilters in useEffects!
|
||||
const fixedFilters = useMemo(
|
||||
() => [
|
||||
{ filePath: { locations: { in: [location.id] } } },
|
||||
...(explorerSettings.layoutMode === 'media'
|
||||
? [{ object: { kind: { in: [ObjectKindEnum.Image, ObjectKindEnum.Video] } } }]
|
||||
: [])
|
||||
],
|
||||
[location.id, explorerSettings.layoutMode]
|
||||
);
|
||||
|
||||
(filter.filePath ??= {}).path = [location.id, path ?? ''];
|
||||
const filters = useSearchFilters('paths', fixedFilters);
|
||||
|
||||
if (explorerSettings.layoutMode === 'media' && explorerSettings.mediaViewWithDescendants)
|
||||
(filter.filePath ??= {}).withDescendants = true;
|
||||
filters.push({
|
||||
filePath: {
|
||||
path: {
|
||||
location_id: location.id,
|
||||
path: path ?? '',
|
||||
include_descendants:
|
||||
explorerSettings.layoutMode === 'media' &&
|
||||
explorerSettings.mediaViewWithDescendants
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!explorerSettings.showHiddenFiles) (filter.filePath ??= {}).hidden = false;
|
||||
if (!explorerSettings.showHiddenFiles) filters.push({ filePath: { hidden: false } });
|
||||
|
||||
const query = usePathsInfiniteQuery({
|
||||
arg: { filter, take },
|
||||
arg: { filters, take },
|
||||
library,
|
||||
settings
|
||||
});
|
||||
|
||||
const count = useLibraryQuery(['search.pathsCount', { filter }], { enabled: query.isSuccess });
|
||||
const count = useLibraryQuery(['search.pathsCount', { filters }], { enabled: query.isSuccess });
|
||||
|
||||
const items = useMemo(() => query.data?.pages.flatMap((d) => d.items) ?? null, [query.data]);
|
||||
|
||||
|
|
|
@ -32,9 +32,9 @@ export type Procedures = {
|
|||
{ key: "preferences.get", input: LibraryArgs<null>, result: LibraryPreferences } |
|
||||
{ key: "search.ephemeralPaths", input: LibraryArgs<EphemeralPathSearchArgs>, result: NonIndexedFileSystemEntries } |
|
||||
{ key: "search.objects", input: LibraryArgs<ObjectSearchArgs>, result: SearchData<ExplorerItem> } |
|
||||
{ key: "search.objectsCount", input: LibraryArgs<{ filter?: SearchFilterArgs[] }>, result: number } |
|
||||
{ key: "search.objectsCount", input: LibraryArgs<{ filters?: SearchFilterArgs[] }>, result: number } |
|
||||
{ key: "search.paths", input: LibraryArgs<FilePathSearchArgs>, result: SearchData<ExplorerItem> } |
|
||||
{ key: "search.pathsCount", input: LibraryArgs<{ filter?: SearchFilterArgs }>, result: number } |
|
||||
{ key: "search.pathsCount", input: LibraryArgs<{ filters?: SearchFilterArgs[] }>, result: number } |
|
||||
{ key: "search.saved.get", input: LibraryArgs<number>, result: SavedSearch | null } |
|
||||
{ key: "search.saved.list", input: LibraryArgs<null>, result: SavedSearchResponse[] } |
|
||||
{ key: "sync.messages", input: LibraryArgs<null>, result: CRDTOperation[] } |
|
||||
|
@ -196,7 +196,7 @@ export type FilePathObjectCursor = { dateAccessed: CursorOrderItem<string> } | {
|
|||
|
||||
export type FilePathOrder = { field: "name"; value: SortOrder } | { field: "sizeInBytes"; value: SortOrder } | { field: "dateCreated"; value: SortOrder } | { field: "dateModified"; value: SortOrder } | { field: "dateIndexed"; value: SortOrder } | { field: "object"; value: ObjectOrder }
|
||||
|
||||
export type FilePathSearchArgs = { take?: number | null; orderAndPagination?: OrderAndPagination<number, FilePathOrder, FilePathCursor> | null; filter?: SearchFilterArgs[]; groupDirectories?: boolean }
|
||||
export type FilePathSearchArgs = { take?: number | null; orderAndPagination?: OrderAndPagination<number, FilePathOrder, FilePathCursor> | null; filters?: SearchFilterArgs[]; groupDirectories?: boolean }
|
||||
|
||||
export type FilePathWithObject = { id: number; pub_id: number[]; is_dir: boolean | null; cas_id: string | null; integrity_checksum: string | null; location_id: number | null; materialized_path: string | null; name: string | null; extension: string | null; hidden: boolean | null; size_in_bytes: string | null; size_in_bytes_bytes: number[] | null; inode: number[] | null; object_id: number | null; key_id: number | null; date_created: string | null; date_modified: string | null; date_indexed: string | null; object: Object | null }
|
||||
|
||||
|
@ -336,7 +336,7 @@ export type ObjectHiddenFilter = "exclude" | "include"
|
|||
|
||||
export type ObjectOrder = { field: "dateAccessed"; value: SortOrder } | { field: "kind"; value: SortOrder } | { field: "mediaData"; value: MediaDataOrder }
|
||||
|
||||
export type ObjectSearchArgs = { take: number; orderAndPagination?: OrderAndPagination<number, ObjectOrder, ObjectCursor> | null; filter?: SearchFilterArgs[] }
|
||||
export type ObjectSearchArgs = { take: number; orderAndPagination?: OrderAndPagination<number, ObjectOrder, ObjectCursor> | null; filters?: SearchFilterArgs[] }
|
||||
|
||||
export type ObjectValidatorArgs = { id: number; path: string }
|
||||
|
||||
|
|
Loading…
Reference in a new issue