Tag preferences (#2313)

* tag preferences

* types
This commit is contained in:
Brendan Allan 2024-04-12 18:17:46 +08:00 committed by GitHub
parent b485bcbcb0
commit effee2140d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 112 additions and 37 deletions

View file

@ -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(),
}
}
}

View file

@ -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) {

View file

@ -272,7 +272,7 @@ function useLocationExplorerSettings(location: Location) {
settings,
onSettingsChanged,
orderingKeys: filePathOrderingKeysSchema,
location
data: location
}),
preferences
};

View file

@ -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 };

View file

@ -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
};
}

View file

@ -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 }