mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-02 11:13:29 +00:00
parent
b485bcbcb0
commit
effee2140d
|
@ -16,6 +16,9 @@ pub struct LibraryPreferences {
|
|||
#[serde(default)]
|
||||
#[specta(optional)]
|
||||
location: HashMap<Uuid, Settings<LocationSettings>>,
|
||||
#[serde(default)]
|
||||
#[specta(optional)]
|
||||
tag: HashMap<Uuid, Settings<TagSettings>>,
|
||||
}
|
||||
|
||||
impl LibraryPreferences {
|
||||
|
@ -56,6 +59,12 @@ pub struct LocationSettings {
|
|||
explorer: ExplorerSettings<search::file_path::FilePathOrder>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Type, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TagSettings {
|
||||
explorer: ExplorerSettings<search::object::ObjectOrder>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Type, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ExplorerSettings<TOrder> {
|
||||
|
@ -95,9 +104,14 @@ pub enum DoubleClickAction {
|
|||
|
||||
impl Preferences for LibraryPreferences {
|
||||
fn to_kvs(self) -> PreferenceKVs {
|
||||
let Self { location } = self;
|
||||
let Self { location, tag } = self;
|
||||
|
||||
location.to_kvs().with_prefix("location")
|
||||
let mut ret = vec![];
|
||||
|
||||
ret.extend(location.to_kvs().with_prefix("location"));
|
||||
ret.extend(tag.to_kvs().with_prefix("tag"));
|
||||
|
||||
PreferenceKVs::new(ret)
|
||||
}
|
||||
|
||||
fn from_entries(mut entries: Entries) -> Self {
|
||||
|
@ -106,6 +120,10 @@ impl Preferences for LibraryPreferences {
|
|||
.remove("location")
|
||||
.map(|value| HashMap::from_entries(value.expect_nested()))
|
||||
.unwrap_or_default(),
|
||||
tag: entries
|
||||
.remove("tag")
|
||||
.map(|value| HashMap::from_entries(value.expect_nested()))
|
||||
.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ export interface UseExplorerProps<TOrder extends Ordering> {
|
|||
* @defaultValue `true`
|
||||
*/
|
||||
selectable?: boolean;
|
||||
settings: ReturnType<typeof useExplorerSettings<TOrder>>;
|
||||
settings: ReturnType<typeof useExplorerSettings<TOrder, any>>;
|
||||
/**
|
||||
* @defaultValue `true`
|
||||
*/
|
||||
|
@ -89,29 +89,26 @@ export function useExplorer<TOrder extends Ordering>({
|
|||
|
||||
export type UseExplorer<TOrder extends Ordering> = ReturnType<typeof useExplorer<TOrder>>;
|
||||
|
||||
export function useExplorerSettings<TOrder extends Ordering>({
|
||||
export function useExplorerSettings<TOrder extends Ordering, T>({
|
||||
settings,
|
||||
onSettingsChanged,
|
||||
orderingKeys,
|
||||
location
|
||||
data
|
||||
}: {
|
||||
settings: ReturnType<typeof createDefaultExplorerSettings<TOrder>>;
|
||||
onSettingsChanged?: (settings: ExplorerSettings<TOrder>, location: Location) => void;
|
||||
onSettingsChanged?: (settings: ExplorerSettings<TOrder>, data: T) => void;
|
||||
orderingKeys?: z.ZodUnion<
|
||||
[z.ZodLiteral<OrderingKeys<TOrder>>, ...z.ZodLiteral<OrderingKeys<TOrder>>[]]
|
||||
>;
|
||||
location?: Location | null;
|
||||
data?: T | null;
|
||||
}) {
|
||||
const [store] = useState(() => proxy(settings));
|
||||
|
||||
const updateSettings = useDebouncedCallback(
|
||||
(settings: ExplorerSettings<TOrder>, location: Location) => {
|
||||
onSettingsChanged?.(settings, location);
|
||||
},
|
||||
500
|
||||
);
|
||||
const updateSettings = useDebouncedCallback((settings: ExplorerSettings<TOrder>, data: T) => {
|
||||
onSettingsChanged?.(settings, data);
|
||||
}, 500);
|
||||
|
||||
useEffect(() => updateSettings.flush(), [location, updateSettings]);
|
||||
useEffect(() => updateSettings.flush(), [data, updateSettings]);
|
||||
|
||||
useEffect(() => {
|
||||
if (updateSettings.isPending()) return;
|
||||
|
@ -119,12 +116,12 @@ export function useExplorerSettings<TOrder extends Ordering>({
|
|||
}, [settings, store, updateSettings]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!onSettingsChanged || !location) return;
|
||||
if (!onSettingsChanged || !data) return;
|
||||
const unsubscribe = subscribe(store, () => {
|
||||
updateSettings(snapshot(store) as ExplorerSettings<TOrder>, location);
|
||||
updateSettings(snapshot(store) as ExplorerSettings<TOrder>, data);
|
||||
});
|
||||
return () => unsubscribe();
|
||||
}, [store, updateSettings, location, onSettingsChanged]);
|
||||
}, [store, updateSettings, data, onSettingsChanged]);
|
||||
|
||||
return {
|
||||
useSettingsSnapshot: () => useSnapshot(store),
|
||||
|
@ -139,8 +136,8 @@ export function useExplorerSettings<TOrder extends Ordering>({
|
|||
};
|
||||
}
|
||||
|
||||
export type UseExplorerSettings<TOrder extends Ordering> = ReturnType<
|
||||
typeof useExplorerSettings<TOrder>
|
||||
export type UseExplorerSettings<TOrder extends Ordering, T> = ReturnType<
|
||||
typeof useExplorerSettings<TOrder, T>
|
||||
>;
|
||||
|
||||
function useSelectedItems(items: ExplorerItem[] | null) {
|
||||
|
|
|
@ -272,7 +272,7 @@ function useLocationExplorerSettings(location: Location) {
|
|||
settings,
|
||||
onSettingsChanged,
|
||||
orderingKeys: filePathOrderingKeysSchema,
|
||||
location
|
||||
data: location
|
||||
}),
|
||||
preferences
|
||||
};
|
||||
|
|
|
@ -13,7 +13,7 @@ import { UseSearch, UseSearchSource } from './useSearch';
|
|||
|
||||
export function useSearchExplorerQuery<TSource extends UseSearchSource>(props: {
|
||||
search: UseSearch<TSource>;
|
||||
explorerSettings: UseExplorerSettings<any>;
|
||||
explorerSettings: UseExplorerSettings<any, any>;
|
||||
filters: SearchFilterArgs[];
|
||||
take: number;
|
||||
paths?: { arg?: Omit<FilePathSearchArgs, 'filters' | 'take'>; order?: FilePathOrder | null };
|
||||
|
|
|
@ -1,8 +1,18 @@
|
|||
import { useMemo } from 'react';
|
||||
import { ObjectOrder, useCache, useLibraryQuery, useNodes } from '@sd/client';
|
||||
import {
|
||||
ExplorerSettings,
|
||||
ObjectOrder,
|
||||
Tag,
|
||||
useCache,
|
||||
useLibraryMutation,
|
||||
useLibraryQuery,
|
||||
useNodes,
|
||||
useRspcLibraryContext
|
||||
} from '@sd/client';
|
||||
import { LocationIdParamsSchema } from '~/app/route-schemas';
|
||||
import { Icon } from '~/components';
|
||||
import { useLocale, useRouteTitle, useZodRouteParams } from '~/hooks';
|
||||
import { stringify } from '~/util/uuid';
|
||||
|
||||
import Explorer from '../Explorer';
|
||||
import { ExplorerContextProvider } from '../Explorer/Context';
|
||||
|
@ -25,12 +35,7 @@ export function Component() {
|
|||
|
||||
useRouteTitle(tag!.name ?? 'Tag');
|
||||
|
||||
const explorerSettings = useExplorerSettings({
|
||||
settings: useMemo(() => {
|
||||
return createDefaultExplorerSettings<ObjectOrder>({ order: null });
|
||||
}, []),
|
||||
orderingKeys: objectOrderingKeysSchema
|
||||
});
|
||||
const { explorerSettings, preferences } = useTagExplorerSettings(tag);
|
||||
|
||||
const search = useSearchFromSearchParams();
|
||||
|
||||
|
@ -47,6 +52,7 @@ export function Component() {
|
|||
const explorer = useExplorer({
|
||||
...items,
|
||||
isFetchingNextPage: items.query.isFetchingNextPage,
|
||||
isLoadingPreferences: preferences.isLoading,
|
||||
settings: explorerSettings,
|
||||
parent: { type: 'Tag', tag: tag! }
|
||||
});
|
||||
|
@ -76,14 +82,66 @@ export function Component() {
|
|||
</TopBarPortal>
|
||||
</SearchContextProvider>
|
||||
|
||||
<Explorer
|
||||
emptyNotice={
|
||||
<EmptyNotice
|
||||
icon={<Icon name="Tags" size={128} />}
|
||||
message={t('tags_notice_message')}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{!preferences.isLoading && (
|
||||
<Explorer
|
||||
emptyNotice={
|
||||
<EmptyNotice
|
||||
icon={<Icon name="Tags" size={128} />}
|
||||
message={t('tags_notice_message')}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</ExplorerContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
function useTagExplorerSettings(tag: Tag) {
|
||||
const rspc = useRspcLibraryContext();
|
||||
|
||||
const preferences = useLibraryQuery(['preferences.get']);
|
||||
const updatePreferences = useLibraryMutation('preferences.update');
|
||||
|
||||
const settings = useMemo(() => {
|
||||
const defaults = createDefaultExplorerSettings<ObjectOrder>({ order: null });
|
||||
|
||||
if (!location) return defaults;
|
||||
|
||||
const pubId = stringify(tag.pub_id);
|
||||
|
||||
const settings = preferences.data?.location?.[pubId]?.explorer;
|
||||
|
||||
if (!settings) return defaults;
|
||||
|
||||
for (const [key, value] of Object.entries(settings)) {
|
||||
if (value !== null) Object.assign(defaults, { [key]: value });
|
||||
}
|
||||
|
||||
return defaults;
|
||||
}, [tag, preferences.data?.location]);
|
||||
|
||||
const onSettingsChanged = async (settings: ExplorerSettings<ObjectOrder>, changedTag: Tag) => {
|
||||
if (changedTag.id === tag.id && preferences.isLoading) return;
|
||||
|
||||
const pubId = stringify(changedTag.pub_id);
|
||||
|
||||
try {
|
||||
await updatePreferences.mutateAsync({
|
||||
tag: { [pubId]: { explorer: settings } }
|
||||
});
|
||||
rspc.queryClient.invalidateQueries(['preferences.get']);
|
||||
} catch (e) {
|
||||
alert('An error has occurred while updating your preferences.');
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
explorerSettings: useExplorerSettings({
|
||||
settings,
|
||||
onSettingsChanged,
|
||||
orderingKeys: objectOrderingKeysSchema,
|
||||
data: tag
|
||||
}),
|
||||
preferences
|
||||
};
|
||||
}
|
||||
|
|
|
@ -403,7 +403,7 @@ export type LibraryConfigWrapped = { uuid: string; instance_id: string; instance
|
|||
|
||||
export type LibraryName = string
|
||||
|
||||
export type LibraryPreferences = { location?: { [key in string]: LocationSettings } }
|
||||
export type LibraryPreferences = { location?: { [key in string]: LocationSettings }; tag?: { [key in string]: TagSettings } }
|
||||
|
||||
export type LightScanArgs = { location_id: number; sub_path: string }
|
||||
|
||||
|
@ -601,6 +601,8 @@ export type Tag = { id: number; pub_id: number[]; name: string | null; color: st
|
|||
|
||||
export type TagCreateArgs = { name: string; color: string }
|
||||
|
||||
export type TagSettings = { explorer: ExplorerSettings<ObjectOrder> }
|
||||
|
||||
export type TagUpdateArgs = { id: number; name: string | null; color: string | null }
|
||||
|
||||
export type Target = { Object: number } | { FilePath: number }
|
||||
|
|
Loading…
Reference in a new issue