Skip to content

Commit

Permalink
feat(ipc): add profile hooks via tanstack query
Browse files Browse the repository at this point in the history
  • Loading branch information
keiko233 committed Feb 1, 2025
1 parent f7e06f8 commit 068c80d
Show file tree
Hide file tree
Showing 6 changed files with 329 additions and 1 deletion.
1 change: 1 addition & 0 deletions frontend/interface/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"build": "tsc"
},
"dependencies": {
"@tanstack/react-query": "5.64.2",
"@tauri-apps/api": "2.2.0",
"ahooks": "3.8.4",
"ofetch": "1.4.1",
Expand Down
4 changes: 3 additions & 1 deletion frontend/interface/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from './ipc'
export * from './service'
export * from './openapi'
export * from './provider'
export * from './service'
export * from './utils'
80 changes: 80 additions & 0 deletions frontend/interface/src/ipc/use-profile-content.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { unwrapResult } from '../utils'
import { commands } from './bindings'

/**
* A custom hook that manages profile content data fetching and updating.
*
* @remarks
* This hook provides functionality to read and write profile content using React Query.
* It includes both query and mutation capabilities for profile data management.
*
* @param uid - The unique identifier for the profile
*
* @returns An object containing:
* - `query` - The React Query result object for fetching profile content
* - `upsert` - Mutation object for saving/updating profile content
*
* @example
* ```tsx
* const { query, upsert } = useProfileContent("user123");
* const { data, isLoading } = query;
*
* // To update profile content
* upsert.mutate("new profile content");
* ```
*/
export const useProfileContent = (uid: string) => {
const queryClient = useQueryClient()

/**
* A React Query hook that fetches profile content based on a user ID.
*
* @remarks
* This query uses the `readProfileFile` command to retrieve profile data
* and unwraps the result.
*
* @param uid - The user ID used to fetch the profile content
* @returns A React Query result object containing the profile content data,
* loading state, and error state
*
* @example
* ```tsx
* const { data, isLoading } = useQuery(['profileContent', userId]);
* ```
*/
const query = useQuery({
queryKey: ['profileContent', uid],
queryFn: async () => {
return unwrapResult(await commands.readProfileFile(uid))
},
})

/**
* Mutation hook for saving and updating profile file data
*
* @remarks
* This mutation will invalidate the profile content query cache on success
*
* @example
* ```ts
* const { mutate } = upsert;
* mutate("profile content");
* ```
*
* @returns A mutation object that handles saving profile file data
*/
const upsert = useMutation({
mutationFn: async (fileData: string) => {
return unwrapResult(await commands.saveProfileFile(uid, fileData))
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['profileContent', uid] })
},
})

return {
query,
upsert,
}
}
227 changes: 227 additions & 0 deletions frontend/interface/src/ipc/use-profile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { unwrapResult } from '../utils'
import { commands, ProfileKind, ProfilesBuilder } from './bindings'

type URLImportParams = Parameters<typeof commands.importProfile>

type ManualImportParams = Parameters<typeof commands.createProfile>

type CreateParams =
| {
type: 'url'
data: {
url: URLImportParams[0]
option: URLImportParams[1]
}
}
| {
type: 'manual'
data: {
item: ManualImportParams[0]
fileData: ManualImportParams[1]
}
}

/**
* A custom hook for managing profile operations using React Query.
* Provides functionality for CRUD operations on profiles including creation,
* updating, reordering, and deletion.
*
* @returns An object containing:
* - query: {@link UseQueryResult} Hook result for fetching profiles data
* - create: {@link UseMutationResult} Mutation for creating/importing profiles
* - update: {@link UseMutationResult} Mutation for updating existing profiles
* - sort: {@link UseMutationResult} Mutation for reordering profiles
* - upsert: {@link UseMutationResult} Mutation for upserting profile configurations
* - drop: {@link UseMutationResult} Mutation for deleting profiles
*
* @example
* ```typescript
* const { query, create, update, sort, upsert, drop } = useProfile();
*
* // Fetch profiles
* const { data, isLoading } = query;
*
* // Create a new profile
* create.mutate({
* type: 'file',
* data: { item: profileData, fileData: 'config' }
* });
*
* // Update a profile
* update.mutate({ uid: 'profile-id', profile: updatedProfile });
*
* // Reorder profiles
* sort.mutate(['uid1', 'uid2', 'uid3']);
*
* // Upsert profile config
* upsert.mutate(profilesConfig);
*
* // Delete a profile
* drop.mutate('profile-id');
* ```
*/
export const useProfile = () => {
const queryClient = useQueryClient()

/**
* A React Query hook that fetches profiles data.
* data is the full Profile configuration, including current, chain, valid, and items fields
* Uses the `getProfiles` command to retrieve profile information.
*
* @returns {UseQueryResult} A query result object containing:
* - data: {
* current: string | null - Currently selected profile UID
* chain: string[] - Global chain of profile UIDs
* valid: boolean - Whether the profile configuration is valid
* items: Profile[] - Array of profile configurations
* }
* - `isLoading`: Boolean indicating if the query is in loading state
* - `error`: Error object if the query failed
* - Other standard React Query result properties
*/
const query = useQuery({
queryKey: ['profiles'],
queryFn: async () => {
return unwrapResult(await commands.getProfiles())
},
})

/**
* Mutation hook for creating or importing profiles
*
* @remarks
* This mutation handles two types of profile creation:
* 1. URL-based import using `importProfile` command
* 2. Direct creation using `createProfile` command
*
* @returns A mutation object that accepts CreateParams and handles profile creation
*
* @throws Will throw an error if the profile creation/import fails
*
* @example
* ```ts
* const { mutate } = create();
* // Import from URL
* mutate({ type: 'url', data: { url: 'https://example.com/config.yaml', option: {...} }});
* // Create directly
* mutate({ type: 'file', data: { item: {...}, fileData: '...' }});
* ```
*/
const create = useMutation({
mutationFn: async ({ type, data }: CreateParams) => {
if (type === 'url') {
const { url, option } = data
return unwrapResult(await commands.importProfile(url, option))
} else {
const { item, fileData } = data
return unwrapResult(await commands.createProfile(item, fileData))
}
},
onSuccess: () => {
// Invalidate and refetch
queryClient.invalidateQueries({ queryKey: ['profiles'] })
},
})

/**
* Mutation hook for updating a profile.
* Uses React Query's useMutation to handle profile updates.
*
* @remarks
* This mutation will automatically invalidate and refetch the 'profiles' query on success
*
* @param uid - The unique identifier of the profile to update
* @param profile - The profile data of type ProfileKind to update with
*
* @returns A mutation object containing mutate function and mutation state
*
* @throws Will throw an error if the profile update fails
*/
const update = useMutation({
mutationFn: async ({
uid,
profile,
}: {
uid: string
profile: ProfileKind
}) => {
return unwrapResult(await commands.patchProfile(uid, profile))
},
onSuccess: () => {
// Invalidate and refetch
queryClient.invalidateQueries({ queryKey: ['profiles'] })
},
})

/**
* Mutation hook for reordering profiles.
* Uses the React Query's useMutation hook to handle profile reordering operations.
*
* @remarks
* This mutation takes an array of profile UIDs and reorders them according to the new sequence.
* On successful reordering, it invalidates the 'profiles' query cache to trigger a refresh.
*
* @example
* ```typescript
* const { mutate } = sort;
* mutate(['uid1', 'uid2', 'uid3']);
* ```
*/
const sort = useMutation({
mutationFn: async (uids: string[]) => {
return unwrapResult(await commands.reorderProfilesByList(uids))
},
onSuccess: () => {
// Invalidate and refetch
queryClient.invalidateQueries({ queryKey: ['profiles'] })
},
})

/**
* Mutation hook for upserting profile configurations.
*
* @remarks
* This mutation handles the update/insert of profile configurations and invalidates
* the profiles query cache on success.
*
* @returns A mutation object that:
* - Accepts a ProfilesBuilder parameter for the mutation
* - Returns the unwrapped result from patchProfilesConfig command
* - Automatically invalidates the 'profiles' query cache on successful mutation
*/
const upsert = useMutation({
mutationFn: async (options: ProfilesBuilder) => {
return unwrapResult(await commands.patchProfilesConfig(options))
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['profiles'] })
},
})

/**
* A mutation hook for deleting a profile.
*
* @returns {UseMutationResult} A mutation object that:
* - Accepts a profile UID as parameter
* - Deletes the profile via commands.deleteProfile
* - Automatically invalidates 'profiles' queries on success
*/
const drop = useMutation({
mutationFn: async (uid: string) => {
return unwrapResult(await commands.deleteProfile(uid))
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['profiles'] })
},
})

return {
query,
create,
update,
sort,
upsert,
drop,
}
}
10 changes: 10 additions & 0 deletions frontend/interface/src/provider/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { PropsWithChildren } from 'react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'

const queryClient = new QueryClient()

export const NyanpasuProvider = ({ children }: PropsWithChildren) => {
return (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
)
}
8 changes: 8 additions & 0 deletions frontend/interface/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { Result } from '../ipc/bindings'

export function unwrapResult<T, E>(res: Result<T, E>) {
if (res.status === 'error') {
throw res.error
}
return res.status === 'ok' ? res.data : undefined
}

0 comments on commit 068c80d

Please sign in to comment.