Use react-query for shortcuts
This commit is contained in:
@ -6,15 +6,12 @@ import { Circle, DotsThree, MagnifyingGlass, X } from '@phosphor-icons/react'
|
||||
import {
|
||||
Subscription,
|
||||
SubscriptionType,
|
||||
useGetSubscriptionsQuery,
|
||||
} from '../../../lib/networking/queries/useGetSubscriptionsQuery'
|
||||
import { useGetLabelsQuery } from '../../../lib/networking/queries/useGetLabelsQuery'
|
||||
import { Label } from '../../../lib/networking/fragments/labelFragment'
|
||||
import { theme } from '../../tokens/stitches.config'
|
||||
import { useRegisterActions } from 'kbar'
|
||||
import { LogoBox } from '../../elements/LogoBox'
|
||||
import { usePersistedState } from '../../../lib/hooks/usePersistedState'
|
||||
import { useGetSavedSearchQuery } from '../../../lib/networking/queries/useGetSavedSearchQuery'
|
||||
import { SavedSearch } from '../../../lib/networking/fragments/savedSearchFragment'
|
||||
import { ToggleCaretDownIcon } from '../../elements/icons/ToggleCaretDownIcon'
|
||||
import Link from 'next/link'
|
||||
@ -185,57 +182,6 @@ const Shortcuts = (props: LibraryFilterMenuProps): JSX.Element => {
|
||||
initialValue: [],
|
||||
})
|
||||
|
||||
// const shortcuts: Shortcut[] = [
|
||||
// {
|
||||
// id: '12asdfasdf',
|
||||
// name: 'Omnivore Blog',
|
||||
// icon: 'https://substackcdn.com/image/fetch/w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F052c15c4-ecfd-4d32-87db-13bcac9afad5_512x512.png',
|
||||
// filter: 'subscription:"Money Talk"',
|
||||
// type: 'feed',
|
||||
// },
|
||||
// {
|
||||
// id: 'sdfsdfgdsfg',
|
||||
// name: 'Follow the Money | Arne & Harr',
|
||||
// filter: 'subscription:"Money Talk"',
|
||||
// type: 'feed',
|
||||
// },
|
||||
// {
|
||||
// id: 'sdfasdfasdfsdfsdfsgasdfg',
|
||||
// name: 'Andrew Kenneson from Center for the Study of Partisanship and Ideology',
|
||||
// // icon: 'https://substackcdn.com/image/fetch/w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F052c15c4-ecfd-4d32-87db-13bcac9afad5_512x512.png',
|
||||
// filter: 'in:all label:"Hockey"',
|
||||
// type: 'newsletter',
|
||||
// },
|
||||
// {
|
||||
// id: 'sdfasdfasdfsdfsdfsgasdfg',
|
||||
// name: 'Robert的博客',
|
||||
// // icon: 'https://substackcdn.com/image/fetch/w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F052c15c4-ecfd-4d32-87db-13bcac9afad5_512x512.png',
|
||||
// filter: 'in:all label:"Hockey"',
|
||||
// type: 'feed',
|
||||
// },
|
||||
// {
|
||||
// id: 'sdfasdfasdfasdfasf',
|
||||
// name: 'Oldest First',
|
||||
// // icon: 'https://substackcdn.com/image/fetch/w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F052c15c4-ecfd-4d32-87db-13bcac9afad5_512x512.png',
|
||||
// filter: 'in:all label:"Hockey"',
|
||||
// type: 'search',
|
||||
// },
|
||||
// {
|
||||
// id: 'sdfasdfasdfgasdfg',
|
||||
// name: 'Hockey',
|
||||
// // icon: 'https://substackcdn.com/image/fetch/w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F052c15c4-ecfd-4d32-87db-13bcac9afad5_512x512.png',
|
||||
// filter: 'in:all label:"Hockey"',
|
||||
// type: 'label',
|
||||
// label: {
|
||||
// id: 'sdfsdfsdf',
|
||||
// name: 'Hockey',
|
||||
// color: '#E98B8B',
|
||||
// createdAt: new Date(),
|
||||
// },
|
||||
// },
|
||||
// ]
|
||||
//
|
||||
|
||||
return (
|
||||
<VStack
|
||||
css={{
|
||||
|
||||
@ -26,8 +26,6 @@ import { NavigationSection } from '../NavigationLayout'
|
||||
import { NodeApi, SimpleTree, Tree, TreeApi } from 'react-arborist'
|
||||
import { ListMagnifyingGlass } from '@phosphor-icons/react'
|
||||
import React from 'react'
|
||||
import useSWR from 'swr'
|
||||
import useSWRMutation from 'swr/mutation'
|
||||
import { fetchEndpoint } from '../../../lib/appConfig'
|
||||
import { requestHeaders } from '../../../lib/networking/networkHelpers'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
@ -40,6 +38,11 @@ import { ShortcutFolderClosed } from '../../elements/icons/ShortcutFolderClosed'
|
||||
import { TrashSectionIcon } from '../../elements/icons/TrashSectionIcon'
|
||||
import { ShortcutFolderOpen } from '../../elements/icons/ShortcutFolderOpen'
|
||||
import useResizeObserver from 'use-resize-observer'
|
||||
import {
|
||||
useGetShortcuts,
|
||||
useResetShortcuts,
|
||||
useSetShortcuts,
|
||||
} from '../../../lib/networking/shortcuts/useShortcuts'
|
||||
|
||||
export const LIBRARY_LEFT_MENU_WIDTH = '275px'
|
||||
|
||||
@ -268,10 +271,7 @@ const LibraryNav = (props: NavigationMenuProps): JSX.Element => {
|
||||
|
||||
const Shortcuts = (props: NavigationMenuProps): JSX.Element => {
|
||||
const treeRef = useRef<TreeApi<Shortcut> | undefined>(undefined)
|
||||
const { trigger: resetShortcutsTrigger } = useSWRMutation(
|
||||
'/api/shortcuts',
|
||||
resetShortcuts
|
||||
)
|
||||
const resetShortcuts = useResetShortcuts()
|
||||
|
||||
const createNewFolder = useCallback(async () => {
|
||||
if (treeRef.current) {
|
||||
@ -283,9 +283,7 @@ const Shortcuts = (props: NavigationMenuProps): JSX.Element => {
|
||||
}, [treeRef])
|
||||
|
||||
const resetShortcutsToDefault = useCallback(async () => {
|
||||
resetShortcutsTrigger(null, {
|
||||
revalidate: true,
|
||||
})
|
||||
await resetShortcuts.mutateAsync()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
@ -439,15 +437,10 @@ const cachedShortcutsData = (): Shortcut[] | undefined => {
|
||||
const ShortcutsTree = (props: ShortcutsTreeProps): JSX.Element => {
|
||||
const router = useRouter()
|
||||
const { ref, width, height } = useResizeObserver()
|
||||
const { data, isLoading } = useGetShortcuts()
|
||||
const setShorcuts = useSetShortcuts()
|
||||
const resetShortcuts = useResetShortcuts()
|
||||
|
||||
const { isValidating, data } = useSWR('/api/shortcuts', getShortcuts, {
|
||||
revalidateOnFocus: false,
|
||||
fallbackData: cachedShortcutsData(),
|
||||
onSuccess(data) {
|
||||
localStorage.setItem('/api/shortcuts', JSON.stringify(data))
|
||||
},
|
||||
})
|
||||
const { trigger, isMutating } = useSWRMutation('/api/shortcuts', setShortcuts)
|
||||
const [folderOpenState, setFolderOpenState] = usePersistedState<
|
||||
Record<string, boolean>
|
||||
>({
|
||||
@ -460,55 +453,49 @@ const ShortcutsTree = (props: ShortcutsTreeProps): JSX.Element => {
|
||||
return result
|
||||
}, [data])
|
||||
|
||||
const syncTreeData = (data: Shortcut[]) => {
|
||||
trigger(
|
||||
{ shortcuts: data },
|
||||
{
|
||||
optimisticData: data,
|
||||
rollbackOnError: true,
|
||||
populateCache: (updatedShortcuts) => {
|
||||
return updatedShortcuts
|
||||
},
|
||||
revalidate: false,
|
||||
}
|
||||
)
|
||||
const syncTreeData = async (data: Shortcut[]) => {
|
||||
await setShorcuts.mutateAsync({ shortcuts: data })
|
||||
}
|
||||
|
||||
const onMove = useCallback(
|
||||
(args: { dragIds: string[]; parentId: null | string; index: number }) => {
|
||||
async (args: {
|
||||
dragIds: string[]
|
||||
parentId: null | string
|
||||
index: number
|
||||
}) => {
|
||||
for (const id of args.dragIds) {
|
||||
tree?.move({ id, parentId: args.parentId, index: args.index })
|
||||
}
|
||||
syncTreeData(tree.data)
|
||||
await syncTreeData(tree.data)
|
||||
},
|
||||
[tree, data]
|
||||
)
|
||||
|
||||
const onCreate = useCallback(
|
||||
(args: { parentId: string | null; index: number; type: string }) => {
|
||||
async (args: { parentId: string | null; index: number; type: string }) => {
|
||||
const data = { id: uuidv4(), name: '', type: 'folder' } as any
|
||||
if (args.type === 'internal') {
|
||||
data.children = []
|
||||
}
|
||||
tree.create({ parentId: args.parentId, index: args.index, data })
|
||||
syncTreeData(tree.data)
|
||||
await syncTreeData(tree.data)
|
||||
return data
|
||||
},
|
||||
[tree, data]
|
||||
)
|
||||
|
||||
const onDelete = useCallback(
|
||||
(args: { ids: string[] }) => {
|
||||
async (args: { ids: string[] }) => {
|
||||
args.ids.forEach((id) => tree.drop({ id }))
|
||||
syncTreeData(tree.data)
|
||||
await syncTreeData(tree.data)
|
||||
},
|
||||
[tree, data]
|
||||
)
|
||||
|
||||
const onRename = useCallback(
|
||||
(args: { name: string; id: string }) => {
|
||||
async (args: { name: string; id: string }) => {
|
||||
tree.update({ id: args.id, changes: { name: args.name } as any })
|
||||
syncTreeData(tree.data)
|
||||
await syncTreeData(tree.data)
|
||||
},
|
||||
[tree, data]
|
||||
)
|
||||
@ -575,7 +562,7 @@ const ShortcutsTree = (props: ShortcutsTreeProps): JSX.Element => {
|
||||
minBlockSize: 0,
|
||||
}}
|
||||
>
|
||||
{!isValidating && (
|
||||
{!isLoading && (
|
||||
<Tree
|
||||
ref={props.treeRef}
|
||||
data={data as Shortcut[]}
|
||||
|
||||
117
packages/web/lib/networking/shortcuts/useShortcuts.tsx
Normal file
117
packages/web/lib/networking/shortcuts/useShortcuts.tsx
Normal file
@ -0,0 +1,117 @@
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { requestHeaders } from '../networkHelpers'
|
||||
import { fetchEndpoint } from '../../appConfig'
|
||||
import { Shortcut } from '../../../components/templates/navMenu/NavigationMenu'
|
||||
|
||||
export function useGetShortcuts() {
|
||||
return useQuery({
|
||||
queryKey: ['shortcuts'],
|
||||
queryFn: async () => {
|
||||
return await getShortcuts()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useSetShortcuts = () => {
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: async (variables: { shortcuts: Shortcut[] }) => {
|
||||
return await setShortcuts(variables)
|
||||
},
|
||||
onMutate: async (variables: { shortcuts: Shortcut[] }) => {
|
||||
await queryClient.cancelQueries({ queryKey: ['shortcuts'] })
|
||||
queryClient.setQueryData(['shortcuts'], variables.shortcuts)
|
||||
const previousState = {
|
||||
previousItems: queryClient.getQueryData(['shortcuts']),
|
||||
}
|
||||
return previousState
|
||||
},
|
||||
onError: (error, variables, context) => {
|
||||
if (context?.previousItems) {
|
||||
queryClient.setQueryData(['shortcuts'], context.previousItems)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useResetShortcuts = () => {
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
return await resetShortcuts()
|
||||
},
|
||||
onMutate: async () => {
|
||||
const previousState = {
|
||||
previousItems: queryClient.getQueryData(['shortcuts']),
|
||||
}
|
||||
return previousState
|
||||
},
|
||||
onError: (error, variables, context) => {
|
||||
if (context?.previousItems) {
|
||||
queryClient.setQueryData(['shortcuts'], context.previousItems)
|
||||
}
|
||||
},
|
||||
onSuccess: (data, variables, context) => {
|
||||
queryClient.setQueryData(['shortcuts'], data)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async function getShortcuts(): Promise<Shortcut[]> {
|
||||
const url = new URL(`/api/shortcuts`, fetchEndpoint)
|
||||
try {
|
||||
const response = await fetch(url.toString(), {
|
||||
method: 'GET',
|
||||
headers: requestHeaders(),
|
||||
credentials: 'include',
|
||||
mode: 'cors',
|
||||
})
|
||||
const payload = await response.json()
|
||||
if ('shortcuts' in payload) {
|
||||
return payload['shortcuts'] as Shortcut[]
|
||||
}
|
||||
return []
|
||||
} catch (err) {
|
||||
console.log('error getting shortcuts: ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function setShortcuts(variables: {
|
||||
shortcuts: Shortcut[]
|
||||
}): Promise<Shortcut[]> {
|
||||
const url = new URL(`/api/shortcuts`, fetchEndpoint)
|
||||
const response = await fetch(url.toString(), {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...requestHeaders(),
|
||||
},
|
||||
credentials: 'include',
|
||||
mode: 'cors',
|
||||
body: JSON.stringify({ shortcuts: variables.shortcuts }),
|
||||
})
|
||||
const payload = await response.json()
|
||||
if (!('shortcuts' in payload)) {
|
||||
throw new Error('Error syncing shortcuts')
|
||||
}
|
||||
return payload['shortcuts'] as Shortcut[]
|
||||
}
|
||||
|
||||
async function resetShortcuts(): Promise<Shortcut[]> {
|
||||
const url = new URL(`/api/shortcuts`, fetchEndpoint)
|
||||
const response = await fetch(url.toString(), {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...requestHeaders(),
|
||||
},
|
||||
credentials: 'include',
|
||||
mode: 'cors',
|
||||
})
|
||||
const payload = await response.json()
|
||||
if (!('shortcuts' in payload)) {
|
||||
throw new Error('Error syncing shortcuts')
|
||||
}
|
||||
return payload['shortcuts'] as Shortcut[]
|
||||
}
|
||||
Reference in New Issue
Block a user