refactor: rename category ui
Signed-off-by: Innei <i@innei.in>
This commit is contained in:
parent
918d85a591
commit
5f30bfa7c4
|
|
@ -3,7 +3,11 @@ import {
|
|||
getStableRouterNavigate,
|
||||
} from "@renderer/atoms/route"
|
||||
import { setSidebarActiveView } from "@renderer/atoms/sidebar"
|
||||
import { ROUTE_ENTRY_PENDING, ROUTE_FEED_PENDING } from "@renderer/constants"
|
||||
import {
|
||||
ROUTE_ENTRY_PENDING,
|
||||
ROUTE_FEED_IN_FOLDER,
|
||||
ROUTE_FEED_PENDING,
|
||||
} from "@renderer/constants"
|
||||
import { FeedViewType } from "@renderer/lib/enum"
|
||||
import { isUndefined } from "lodash-es"
|
||||
|
||||
|
|
@ -11,6 +15,7 @@ type NavigateEntryOptions = Partial<{
|
|||
feedId: string | null
|
||||
entryId: string | null
|
||||
view: FeedViewType
|
||||
folderName: string
|
||||
}>
|
||||
/**
|
||||
* @description a hook to navigate to `feedId`, `entryId`, add search for `view`, `level`
|
||||
|
|
@ -19,7 +24,7 @@ type NavigateEntryOptions = Partial<{
|
|||
export const useNavigateEntry = () => navigateEntry
|
||||
|
||||
export const navigateEntry = (options: NavigateEntryOptions) => {
|
||||
const { entryId, feedId, view } = options || {}
|
||||
const { entryId, feedId, view, folderName } = options || {}
|
||||
const { params, searchParams } = getReadonlyRoute()
|
||||
let finalFeedId = feedId || params.feedId || ROUTE_FEED_PENDING
|
||||
|
||||
|
|
@ -27,6 +32,10 @@ export const navigateEntry = (options: NavigateEntryOptions) => {
|
|||
finalFeedId = ROUTE_FEED_PENDING
|
||||
}
|
||||
|
||||
if (folderName) {
|
||||
finalFeedId = `${ROUTE_FEED_IN_FOLDER}${folderName}`
|
||||
}
|
||||
|
||||
finalFeedId = encodeURIComponent(finalFeedId)
|
||||
const nextSearchParams = new URLSearchParams(searchParams)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
import {
|
||||
FEED_COLLECTION_LIST,
|
||||
ROUTE_ENTRY_PENDING,
|
||||
ROUTE_FEED_IN_FOLDER,
|
||||
ROUTE_FEED_PENDING,
|
||||
} from "@renderer/constants"
|
||||
import { FeedViewType } from "@renderer/lib/enum"
|
||||
|
|
@ -45,9 +46,13 @@ export interface BizRouteParams {
|
|||
isAllFeeds: boolean
|
||||
isPendingEntry: boolean
|
||||
|
||||
folderName?: string
|
||||
}
|
||||
|
||||
const parseRouteParams = (params: Params<any>, search: URLSearchParams): BizRouteParams => {
|
||||
const parseRouteParams = (
|
||||
params: Params<any>,
|
||||
search: URLSearchParams,
|
||||
): BizRouteParams => {
|
||||
const _view = search.get("view")
|
||||
|
||||
const view =
|
||||
|
|
@ -63,6 +68,9 @@ const parseRouteParams = (params: Params<any>, search: URLSearchParams): BizRout
|
|||
isCollection: params.feedId === FEED_COLLECTION_LIST,
|
||||
isAllFeeds: params.feedId === ROUTE_FEED_PENDING,
|
||||
isPendingEntry: params.entryId === ROUTE_ENTRY_PENDING,
|
||||
folderName: params.feedId?.startsWith(ROUTE_FEED_IN_FOLDER) ?
|
||||
params.feedId.slice(ROUTE_FEED_IN_FOLDER.length) :
|
||||
undefined,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { MotionButtonBase } from "@renderer/components/ui/button"
|
||||
import { LoadingCircle } from "@renderer/components/ui/loading"
|
||||
import { ROUTE_FEED_IN_FOLDER, views } from "@renderer/constants"
|
||||
import { useNavigateEntry } from "@renderer/hooks/biz/useNavigateEntry"
|
||||
|
|
@ -16,11 +17,10 @@ import {
|
|||
import { useFeedUnreadStore } from "@renderer/store/unread"
|
||||
import { useMutation } from "@tanstack/react-query"
|
||||
import { AnimatePresence, m } from "framer-motion"
|
||||
import { memo, useEffect, useRef, useState } from "react"
|
||||
import { Fragment, memo, useEffect, useRef, useState } from "react"
|
||||
|
||||
import { useModalStack } from "../../components/ui/modal/stacked/hooks"
|
||||
import { CategoryRemoveDialogContent } from "./category-remove-dialog"
|
||||
import { CategoryRenameContent } from "./category-rename-dialog"
|
||||
import { FeedItem } from "./item"
|
||||
import { UnreadNumber } from "./unread-number"
|
||||
|
||||
|
|
@ -83,7 +83,7 @@ function FeedCategoryImpl({
|
|||
navigate({
|
||||
entryId: null,
|
||||
// TODO joint feedId is too long, need to be optimized
|
||||
feedId: `${ROUTE_FEED_IN_FOLDER}${folderName}`,
|
||||
folderName,
|
||||
view,
|
||||
})
|
||||
}
|
||||
|
|
@ -113,6 +113,31 @@ function FeedCategoryImpl({
|
|||
},
|
||||
})
|
||||
|
||||
const [isCategoryEditing, setIsCategoryEditing] = useState(false)
|
||||
|
||||
const renameMutation = useMutation({
|
||||
mutationFn: async ({
|
||||
lastCategory,
|
||||
newCategory,
|
||||
}: {
|
||||
lastCategory: string
|
||||
newCategory: string
|
||||
}) => subscriptionActions.renameCategory(lastCategory, newCategory),
|
||||
onMutate({ lastCategory, newCategory }) {
|
||||
const routeParams = getRouteParams()
|
||||
|
||||
if (routeParams.folderName === lastCategory) {
|
||||
navigate({
|
||||
folderName: newCategory,
|
||||
})
|
||||
}
|
||||
|
||||
setIsCategoryEditing(false)
|
||||
},
|
||||
})
|
||||
|
||||
const isCategoryIsWaiting = renameMutation.isPending || isChangePending
|
||||
|
||||
return (
|
||||
<div tabIndex={-1} onClick={stopPropagation}>
|
||||
{!!showCollapse && (
|
||||
|
|
@ -123,7 +148,9 @@ function FeedCategoryImpl({
|
|||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setCategoryActive()
|
||||
if (!isCategoryEditing) {
|
||||
setCategoryActive()
|
||||
}
|
||||
}}
|
||||
onContextMenu={(e) => {
|
||||
showNativeMenu(
|
||||
|
|
@ -150,18 +177,7 @@ function FeedCategoryImpl({
|
|||
type: "text",
|
||||
label: "Rename category",
|
||||
click: () => {
|
||||
present({
|
||||
title: "Rename Category",
|
||||
clickOutsideToDismiss: true,
|
||||
content: ({ dismiss }) => (
|
||||
<CategoryRenameContent
|
||||
feedIdList={ids}
|
||||
category={folderName || ""}
|
||||
view={view}
|
||||
onSuccess={dismiss}
|
||||
/>
|
||||
),
|
||||
})
|
||||
setIsCategoryEditing(true)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -186,6 +202,7 @@ function FeedCategoryImpl({
|
|||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
if (isCategoryEditing) return
|
||||
e.stopPropagation()
|
||||
setOpen(!open)
|
||||
}}
|
||||
|
|
@ -195,25 +212,66 @@ function FeedCategoryImpl({
|
|||
)}
|
||||
tabIndex={-1}
|
||||
>
|
||||
{isChangePending ? (
|
||||
{isCategoryIsWaiting ? (
|
||||
<LoadingCircle size="small" className="mr-2 size-[16px]" />
|
||||
) : (
|
||||
<div className="mr-2 size-[16px]">
|
||||
<i className="i-mgc-right-cute-fi transition-transform" />
|
||||
</div>
|
||||
)}
|
||||
) : isCategoryEditing ?
|
||||
(
|
||||
<MotionButtonBase
|
||||
onClick={() => {
|
||||
setIsCategoryEditing(false)
|
||||
}}
|
||||
className="center flex"
|
||||
>
|
||||
<i className="i-mgc-close-cute-re text-red-500" />
|
||||
</MotionButtonBase>
|
||||
) :
|
||||
(
|
||||
<div className="mr-2 size-[16px]">
|
||||
<i className="i-mgc-right-cute-fi transition-transform" />
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
<span
|
||||
className={cn(
|
||||
"truncate",
|
||||
!showUnreadCount &&
|
||||
(unread ? "font-bold" : "font-medium opacity-70"),
|
||||
)}
|
||||
>
|
||||
{folderName}
|
||||
</span>
|
||||
{isCategoryEditing ? (
|
||||
<form
|
||||
className="ml-3 flex w-full items-center"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault()
|
||||
|
||||
return renameMutation.mutateAsync({
|
||||
lastCategory: folderName!,
|
||||
newCategory: e.currentTarget.category.value,
|
||||
})
|
||||
}}
|
||||
>
|
||||
<input
|
||||
name="category"
|
||||
autoFocus
|
||||
defaultValue={folderName}
|
||||
className="w-full appearance-none bg-transparent caret-accent"
|
||||
/>
|
||||
<MotionButtonBase
|
||||
type="submit"
|
||||
className="center -mr-1 flex size-5 shrink-0 rounded-lg text-green-600 hover:bg-theme-button-hover dark:text-green-400"
|
||||
>
|
||||
<i className="i-mgc-check-filled size-3" />
|
||||
</MotionButtonBase>
|
||||
</form>
|
||||
) : (
|
||||
<Fragment>
|
||||
<span
|
||||
className={cn(
|
||||
"grow truncate",
|
||||
!showUnreadCount &&
|
||||
(unread ? "font-bold" : "font-medium opacity-70"),
|
||||
)}
|
||||
>
|
||||
{folderName}
|
||||
</span>
|
||||
|
||||
<UnreadNumber unread={unread} className="ml-2" />
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
<UnreadNumber unread={unread} className="ml-2" />
|
||||
</div>
|
||||
)}
|
||||
<AnimatePresence>
|
||||
|
|
|
|||
|
|
@ -61,6 +61,14 @@ class SubscriptionServiceStatic extends BaseService<SubscriptionModelWithId> {
|
|||
return this.table.where("userId").equals(userId).delete()
|
||||
}
|
||||
}
|
||||
|
||||
async renameCategory(userId: string, feedIdList: string[], category: string) {
|
||||
return this.table
|
||||
.where("userId")
|
||||
.equals(userId)
|
||||
.and((item) => feedIdList.includes(item.feedId))
|
||||
.modify({ category })
|
||||
}
|
||||
}
|
||||
|
||||
export const SubscriptionService = new SubscriptionServiceStatic()
|
||||
|
|
|
|||
|
|
@ -304,6 +304,50 @@ class SubscriptionActions {
|
|||
),
|
||||
)
|
||||
}
|
||||
|
||||
async renameCategory(lastCategory: string, newCategory: string) {
|
||||
const subscriptionIds = [] as string[]
|
||||
const state = get()
|
||||
for (const feedId in state.data) {
|
||||
const subscription = state.data[feedId]
|
||||
if (
|
||||
subscription.category === lastCategory ||
|
||||
subscription.defaultCategory === lastCategory
|
||||
) {
|
||||
subscriptionIds.push(feedId)
|
||||
}
|
||||
}
|
||||
|
||||
set((state) =>
|
||||
produce(state, (state) => {
|
||||
for (const feedId of subscriptionIds) {
|
||||
const subscription = state.data[feedId]
|
||||
if (subscription) {
|
||||
subscription.category = newCategory
|
||||
subscription.defaultCategory = undefined
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
return doMutationAndTransaction(
|
||||
() =>
|
||||
apiClient.categories.$patch({
|
||||
json: {
|
||||
feedIdList: subscriptionIds,
|
||||
category: newCategory,
|
||||
},
|
||||
}),
|
||||
async () =>
|
||||
// Db
|
||||
|
||||
SubscriptionService.renameCategory(
|
||||
whoami()!.id,
|
||||
subscriptionIds,
|
||||
newCategory,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const subscriptionActions = new SubscriptionActions()
|
||||
|
|
|
|||
Loading…
Reference in New Issue