Move bulk action to react-query

This commit is contained in:
Jackson Harper
2024-07-30 21:00:18 +08:00
parent 582b811b8d
commit ab0e9b28be
10 changed files with 97 additions and 129 deletions

View File

@ -1,7 +1,7 @@
import type { LibraryItem } from '../../../lib/networking/library_items/useLibraryItems'
import { LinkedItemCardAction } from '../../patterns/LibraryCards/CardTypes'
import { MultiSelectMode } from './LibraryHeader'
import { BulkAction } from '../../../lib/networking/mutations/bulkActionMutation'
import { BulkAction } from '../../../lib/networking/library_items/useLibraryItems'
export type LayoutType = 'LIST_LAYOUT' | 'GRID_LAYOUT'
export type LibraryMode = 'reads' | 'highlights' | 'tldr'

View File

@ -10,7 +10,7 @@ import { LayoutType, LibraryMode } from './HomeFeedContainer'
import { OmnivoreSmallLogo } from '../../elements/images/OmnivoreNameLogo'
import { DEFAULT_HEADER_HEIGHT, HeaderSpacer } from './HeaderSpacer'
import { LIBRARY_LEFT_MENU_WIDTH } from '../navMenu/LibraryMenu'
import { BulkAction } from '../../../lib/networking/mutations/bulkActionMutation'
import { BulkAction } from '../../../lib/networking/library_items/useLibraryItems'
import { HeaderToggleGridIcon } from '../../elements/icons/HeaderToggleGridIcon'
import { HeaderToggleListIcon } from '../../elements/icons/HeaderToggleListIcon'
import { HeaderToggleTLDRIcon } from '../../elements/icons/HeaderToggleTLDRIcon'

View File

@ -2,7 +2,7 @@ import { useState } from 'react'
import { theme } from '../../tokens/stitches.config'
import { Box, HStack, SpanBox } from '../../elements/LayoutPrimitives'
import { Button } from '../../elements/Button'
import { BulkAction } from '../../../lib/networking/mutations/bulkActionMutation'
import { BulkAction } from '../../../lib/networking/library_items/useLibraryItems'
import { ArchiveIcon } from '../../elements/icons/ArchiveIcon'
import { LabelIcon } from '../../elements/icons/LabelIcon'
import { TrashIcon } from '../../elements/icons/TrashIcon'
@ -116,7 +116,7 @@ export const MultiSelectControls = (props: MultiSelectProps): JSX.Element => {
<SpanBox
css={{
display: 'none',
fontSize: '14px',
fontSize: '11px',
fontFamily: '$display',
marginRight: 'auto',
'@mdDown': {

View File

@ -19,6 +19,7 @@ import {
LibraryItems,
LibraryItemsQueryInput,
useArchiveItem,
useBulkActions,
useDeleteItem,
useGetLibraryItems,
useMoveItemToFolder,
@ -39,8 +40,7 @@ import { EditLibraryItemModal } from '../homeFeed/EditItemModals'
import { EmptyLibrary } from '../homeFeed/EmptyLibrary'
import { MultiSelectMode } from '../homeFeed/LibraryHeader'
import { UploadModal } from '../UploadModal'
import { BulkAction } from '../../../lib/networking/mutations/bulkActionMutation'
import { bulkActionMutation } from '../../../lib/networking/mutations/bulkActionMutation'
import { BulkAction } from '../../../lib/networking/library_items/useLibraryItems'
import {
showErrorToast,
showSuccessToast,
@ -117,6 +117,7 @@ export function LibraryContainer(props: LibraryContainerProps): JSX.Element {
const archiveItem = useArchiveItem()
const deleteItem = useDeleteItem()
const moveToFolder = useMoveItemToFolder()
const bulkAction = useBulkActions()
const updateItemReadStatus = useUpdateItemReadStatus()
const [queryInputs, setQueryInputs] =
@ -801,12 +802,12 @@ export function LibraryContainer(props: LibraryContainerProps): JSX.Element {
const expectedCount = checkedItems.length
try {
const res = await bulkActionMutation(
const res = await bulkAction.mutateAsync({
action,
query,
expectedCount,
labelIds
)
labelIds,
})
if (res) {
let successMessage: string | undefined = undefined
switch (action) {

View File

@ -9,7 +9,7 @@ import { FunnelSimple, X } from '@phosphor-icons/react'
import { LayoutType } from '../homeFeed/HomeFeedContainer'
import { OmnivoreSmallLogo } from '../../elements/images/OmnivoreNameLogo'
import { LIBRARY_LEFT_MENU_WIDTH } from '../navMenu/LibraryMenu'
import { BulkAction } from '../../../lib/networking/mutations/bulkActionMutation'
import { BulkAction } from '../../../lib/networking/library_items/useLibraryItems'
import { HeaderToggleGridIcon } from '../../elements/icons/HeaderToggleGridIcon'
import { HeaderToggleListIcon } from '../../elements/icons/HeaderToggleListIcon'
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'

View File

@ -1,56 +1,4 @@
import { Action, createAction, useKBar, useRegisterActions } from 'kbar'
import debounce from 'lodash/debounce'
import { useRouter } from 'next/router'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Toaster } from 'react-hot-toast'
import TopBarProgress from 'react-topbar-progress-indicator'
import { useFetchMore } from '../../../lib/hooks/useFetchMoreScroll'
import { usePersistedState } from '../../../lib/hooks/usePersistedState'
import { libraryListCommands } from '../../../lib/keyboardShortcuts/navigationShortcuts'
import { useKeyboardShortcuts } from '../../../lib/keyboardShortcuts/useKeyboardShortcuts'
import {
PageType,
State,
} from '../../../lib/networking/fragments/articleFragment'
import {
SearchItem,
TypeaheadSearchItemsData,
typeaheadSearchQuery,
} from '../../../lib/networking/queries/typeaheadSearch'
import type {
LibraryItem,
LibraryItemsQueryInput,
} from '../../../lib/networking/library_items/useLibraryItems'
import {
useGetViewerQuery,
UserBasicData,
} from '../../../lib/networking/queries/useGetViewerQuery'
import { Button } from '../../elements/Button'
import { StyledText } from '../../elements/StyledText'
import { ConfirmationModal } from '../../patterns/ConfirmationModal'
import { LinkedItemCardAction } from '../../patterns/LibraryCards/CardTypes'
import { LinkedItemCard } from '../../patterns/LibraryCards/LinkedItemCard'
import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives'
import { AddLinkModal } from '../AddLinkModal'
import { EditLibraryItemModal } from '../homeFeed/EditItemModals'
import { EmptyLibrary } from '../homeFeed/EmptyLibrary'
import { LegacyLibraryHeader, MultiSelectMode } from '../homeFeed/LibraryHeader'
import { UploadModal } from '../UploadModal'
import { BulkAction } from '../../../lib/networking/mutations/bulkActionMutation'
import { bulkActionMutation } from '../../../lib/networking/mutations/bulkActionMutation'
import {
showErrorToast,
showSuccessToast,
showSuccessToastWithAction,
} from '../../../lib/toastHelpers'
import { SetPageLabelsModalPresenter } from '../article/SetLabelsModalPresenter'
import { NotebookPresenter } from '../article/NotebookPresenter'
import { saveUrlMutation } from '../../../lib/networking/mutations/saveUrlMutation'
import { articleQuery } from '../../../lib/networking/queries/useGetArticleQuery'
import { PinnedButtons } from '../homeFeed/PinnedButtons'
import { PinnedSearch } from '../../../pages/settings/pinned-searches'
import { FetchItemsError } from '../homeFeed/FetchItemsError'
import { LibraryHeader } from './LibraryHeader'
import { VStack } from '../../elements/LayoutPrimitives'
type LibrarySideBarProps = {
text: string

View File

@ -235,3 +235,26 @@ export const GQL_GET_LIBRARY_ITEM_CONTENT = gql`
${labelFragment}
${recommendationFragment}
`
export const GQL_BULK_ACTION = gql`
mutation BulkAction(
$action: BulkActionType!
$query: String!
$expectedCount: Int
$labelIds: [ID!]
) {
bulkAction(
query: $query
action: $action
labelIds: $labelIds
expectedCount: $expectedCount
) {
... on BulkActionSuccess {
success
}
... on BulkActionError {
errorCodes
}
}
}
`

View File

@ -12,6 +12,7 @@ import { Highlight, highlightFragment } from '../fragments/highlightFragment'
import { makeGqlFetcher, requestHeaders } from '../networkHelpers'
import { Label } from '../fragments/labelFragment'
import {
GQL_BULK_ACTION,
GQL_DELETE_LIBRARY_ITEM,
GQL_GET_LIBRARY_ITEM_CONTENT,
GQL_MOVE_ITEM_TO_FOLDER,
@ -621,6 +622,56 @@ export const useSetItemLabels = () => {
})
}
export const useBulkActions = () => {
const queryClient = useQueryClient()
const bulkAction = async (variables: {
action: BulkAction
query: string
expectedCount: number
labelIds?: string[]
}) => {
const result = (await gqlFetcher(GQL_BULK_ACTION, {
...variables,
})) as BulkActionData
if (result.bulkAction?.errorCodes?.length) {
throw new Error(result.bulkAction.errorCodes[0])
}
return result.bulkAction.success
}
return useMutation({
mutationFn: bulkAction,
onMutate: async (variables: {
action: BulkAction
query: string
expectedCount: number
labelIds?: string[]
}) => {
await queryClient.cancelQueries({ queryKey: ['libraryItems'] })
},
onSettled: async (newLabels, variables) => {
await queryClient.invalidateQueries({
queryKey: ['libraryItems'],
})
},
})
}
export enum BulkAction {
ARCHIVE = 'ARCHIVE',
DELETE = 'DELETE',
ADD_LABELS = 'ADD_LABELS',
MARK_AS_READ = 'MARK_AS_READ',
}
type BulkActionResult = {
success?: boolean
errorCodes?: string[]
}
type BulkActionData = {
bulkAction: BulkActionResult
}
type UpdateLibraryItemInput = {
pageId: string
title?: string

View File

@ -1,62 +1,2 @@
import { gql } from 'graphql-request'
import { gqlFetcher } from '../networkHelpers'
export enum BulkAction {
ARCHIVE = 'ARCHIVE',
DELETE = 'DELETE',
ADD_LABELS = 'ADD_LABELS',
MARK_AS_READ = 'MARK_AS_READ',
}
type BulkActionResponseData = {
success: boolean
}
type BulkActionResponse = {
errorCodes?: string[]
bulkAction?: BulkActionResponseData
}
export async function bulkActionMutation(
action: BulkAction,
query: string,
expectedCount: number,
labelIds?: string[]
): Promise<boolean> {
const mutation = gql`
mutation BulkAction(
$action: BulkActionType!
$query: String!
$expectedCount: Int
$labelIds: [ID!]
) {
bulkAction(
query: $query
action: $action
labelIds: $labelIds
expectedCount: $expectedCount
) {
... on BulkActionSuccess {
success
}
... on BulkActionError {
errorCodes
}
}
}
`
try {
const response = await gqlFetcher(mutation, {
action,
query,
labelIds,
expectedCount,
})
const data = response as BulkActionResponse | undefined
return data?.bulkAction?.success ?? false
} catch (error) {
console.error(error)
return false
}
}

View File

@ -5,16 +5,16 @@ import { VStack } from '../../components/elements/LayoutPrimitives'
import { StyledText } from '../../components/elements/StyledText'
import { ProfileLayout } from '../../components/templates/ProfileLayout'
import {
BulkAction,
bulkActionMutation,
} from '../../lib/networking/mutations/bulkActionMutation'
import { BulkAction } from '../../lib/networking/library_items/useLibraryItems'
import { Button } from '../../components/elements/Button'
import { theme } from '../../components/tokens/stitches.config'
import { ConfirmationModal } from '../../components/patterns/ConfirmationModal'
import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers'
import { useRouter } from 'next/router'
import { useGetLibraryItems } from '../../lib/networking/library_items/useLibraryItems'
import {
useBulkActions,
useGetLibraryItems,
} from '../../lib/networking/library_items/useLibraryItems'
import {
BorderedFormInput,
FormLabel,
@ -33,6 +33,7 @@ export default function BulkPerformer(): JSX.Element {
const [expectedCount, setExpectedCount] = useState<number | undefined>()
const [errorMessage, setErrorMessage] = useState<string | undefined>()
const [runningState, setRunningState] = useState<RunningState>('none')
const bulkAction = useBulkActions()
const { data: itemsPages, isLoading } = useGetLibraryItems(undefined, {
searchQuery: query,
@ -64,7 +65,11 @@ export default function BulkPerformer(): JSX.Element {
return
}
try {
const success = await bulkActionMutation(action, query, expectedCount)
const success = await bulkAction.mutateAsync({
action,
query,
expectedCount,
})
if (!success) {
throw 'Success not returned'
}