fix(desktop): can update list after create list
- Replaced grouped data hooks with direct ID retrieval for lists and inboxes to simplify data management. - Updated related components to utilize the new hooks for improved performance and clarity. - Enhanced list creation and update logic to utilize centralized actions for better maintainability. Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
parent
e03d1ec872
commit
d39f95c5b3
|
|
@ -35,16 +35,14 @@ import type { Suggestion } from "~/components/ui/auto-completion"
|
|||
import { Autocomplete } from "~/components/ui/auto-completion"
|
||||
import { useCurrentModal } from "~/components/ui/modal/stacked/hooks"
|
||||
import { useAddFeedToFeedList, useRemoveFeedFromFeedList } from "~/hooks/biz/useFeedActions"
|
||||
import { apiClient } from "~/lib/api-fetch"
|
||||
import { createErrorToaster } from "~/lib/error-parser"
|
||||
import { UrlBuilder } from "~/lib/url-builder"
|
||||
import { FeedCertification } from "~/modules/feed/feed-certification"
|
||||
import { FeedIcon } from "~/modules/feed/feed-icon"
|
||||
import { ViewSelectorRadioGroup } from "~/modules/shared/ViewSelectorRadioGroup"
|
||||
import { Queries } from "~/queries"
|
||||
import { useFeedById } from "~/store/feed"
|
||||
import { useListById } from "~/store/list"
|
||||
import { subscriptionActions, useAllFeeds } from "~/store/subscription"
|
||||
import { listActions, useListById } from "~/store/list"
|
||||
import { useAllFeeds } from "~/store/subscription"
|
||||
|
||||
const formSchema = z.object({
|
||||
view: z.string(),
|
||||
|
|
@ -74,30 +72,23 @@ export const ListCreationModalContent = ({ id }: { id?: string }) => {
|
|||
const createMutation = useMutation({
|
||||
mutationFn: async (values: z.infer<typeof formSchema>) => {
|
||||
if (id) {
|
||||
await apiClient.lists.$patch({
|
||||
json: {
|
||||
listId: id,
|
||||
...values,
|
||||
view: Number.parseInt(values.view),
|
||||
},
|
||||
listActions.updateList({
|
||||
listId: id,
|
||||
...values,
|
||||
view: Number.parseInt(values.view),
|
||||
})
|
||||
} else {
|
||||
await apiClient.lists.$post({
|
||||
json: {
|
||||
...values,
|
||||
view: Number.parseInt(values.view),
|
||||
},
|
||||
listActions.createList({
|
||||
...values,
|
||||
view: Number.parseInt(values.view),
|
||||
})
|
||||
}
|
||||
},
|
||||
onSuccess: (_, values) => {
|
||||
toast.success(t(id ? "lists.edit.success" : "lists.created.success"))
|
||||
Queries.lists.list().invalidate()
|
||||
dismiss()
|
||||
onSuccess: (_) => {
|
||||
const isCreate = !id
|
||||
toast.success(t(isCreate ? "lists.created.success" : "lists.edit.success"))
|
||||
|
||||
if (!list) return
|
||||
if (id)
|
||||
subscriptionActions.changeListView(id, views[list.view]!.view, views[values.view].view)
|
||||
dismiss()
|
||||
},
|
||||
onError: createErrorToaster(id ? t("lists.edit.error") : t("lists.created.error")),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ import { useList } from "~/queries/lists"
|
|||
import {
|
||||
useCategoryOpenStateByView,
|
||||
useFeedsGroupedData,
|
||||
useInboxesGroupedData,
|
||||
useListsGroupedData,
|
||||
useSubscriptionInboxIds,
|
||||
useSubscriptionListIds,
|
||||
} from "~/store/subscription"
|
||||
|
||||
import { COMMAND_ID } from "../command/commands/id"
|
||||
|
|
@ -45,23 +45,21 @@ import { EmptyFeedList, ListHeader, StarredItem } from "./SubscriptionList.share
|
|||
|
||||
const SubscriptionImpl = ({ ref, className, view }: SubscriptionProps) => {
|
||||
const feedsData = useFeedsGroupedData(view)
|
||||
const listsData = useListsGroupedData(view)
|
||||
const inboxesData = useInboxesGroupedData(view)
|
||||
const listSubIds = useSubscriptionListIds(view)
|
||||
const inboxSubIds = useSubscriptionInboxIds(view)
|
||||
|
||||
const categoryOpenStateData = useCategoryOpenStateByView(view)
|
||||
|
||||
const hasData =
|
||||
Object.keys(feedsData).length > 0 ||
|
||||
Object.keys(listsData).length > 0 ||
|
||||
Object.keys(inboxesData).length > 0
|
||||
Object.keys(feedsData).length > 0 || listSubIds.length > 0 || inboxSubIds.length > 0
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
// Data prefetch
|
||||
useAuthQuery(Queries.lists.list())
|
||||
|
||||
const hasListData = Object.keys(listsData).length > 0
|
||||
const hasInboxData = Object.keys(inboxesData).length > 0
|
||||
const hasListData = Object.keys(listSubIds).length > 0
|
||||
const hasInboxData = Object.keys(inboxSubIds).length > 0
|
||||
|
||||
const scrollerRef = useRef<HTMLDivElement | null>(null)
|
||||
const selectoRef = useRef<Selecto>(null)
|
||||
|
|
@ -254,7 +252,7 @@ const SubscriptionImpl = ({ ref, className, view }: SubscriptionProps) => {
|
|||
isPreview
|
||||
/>
|
||||
)}
|
||||
<SortByAlphabeticalList view={view} data={listsData} />
|
||||
<SortByAlphabeticalList view={view} data={listSubIds} />
|
||||
</>
|
||||
)}
|
||||
{hasInboxData && (
|
||||
|
|
@ -262,7 +260,7 @@ const SubscriptionImpl = ({ ref, className, view }: SubscriptionProps) => {
|
|||
<div className="text-text-secondary mt-1 flex h-6 w-full shrink-0 items-center rounded-md px-2.5 text-xs font-semibold transition-colors">
|
||||
{t("words.inbox")}
|
||||
</div>
|
||||
<SortByAlphabeticalInbox view={view} data={inboxesData} />
|
||||
<SortByAlphabeticalInbox view={view} data={inboxSubIds} />
|
||||
</>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ import { Queries } from "~/queries"
|
|||
import {
|
||||
useCategoryOpenStateByView,
|
||||
useFeedsGroupedData,
|
||||
useInboxesGroupedData,
|
||||
useListsGroupedData,
|
||||
useSubscriptionInboxIds,
|
||||
useSubscriptionListIds,
|
||||
} from "~/store/subscription"
|
||||
|
||||
import { SortableFeedList, SortByAlphabeticalInbox, SortByAlphabeticalList } from "./sort-by"
|
||||
|
|
@ -21,8 +21,8 @@ import { EmptyFeedList, ListHeader, StarredItem } from "./SubscriptionList.share
|
|||
|
||||
const FeedListImpl = ({ className, view }: SubscriptionProps) => {
|
||||
const feedsData = useFeedsGroupedData(view)
|
||||
const listsData = useListsGroupedData(view)
|
||||
const inboxesData = useInboxesGroupedData(view)
|
||||
const listsData = useSubscriptionListIds(view)
|
||||
const inboxesData = useSubscriptionInboxIds(view)
|
||||
const categoryOpenStateData = useCategoryOpenStateByView(view)
|
||||
|
||||
const hasData =
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ export const SortByAlphabeticalFeedList = ({
|
|||
export const SortByAlphabeticalListList = ({ view, data }: ListListProps) => {
|
||||
return (
|
||||
<div>
|
||||
{Object.keys(data).map((listId) => (
|
||||
{data.map((listId) => (
|
||||
<ListItemAutoHideUnread key={listId} listId={listId} view={view} />
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -106,8 +106,8 @@ export const SortByAlphabeticalListList = ({ view, data }: ListListProps) => {
|
|||
export const SortByAlphabeticalInboxList = ({ view, data }: ListListProps) => {
|
||||
return (
|
||||
<div>
|
||||
{Object.keys(data).map((feedId) => (
|
||||
<InboxItem key={feedId} inboxId={getInboxOrFeedIdFromFeedId(feedId)} view={view} />
|
||||
{data.map((inboxId) => (
|
||||
<InboxItem key={inboxId} inboxId={getInboxOrFeedIdFromFeedId(inboxId)} view={view} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,5 +9,5 @@ export type SortBy = "count" | "alphabetical"
|
|||
|
||||
export type ListListProps = {
|
||||
view: FeedViewType
|
||||
data: Record<string, string[]>
|
||||
data: string[]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,12 @@ class ServiceStatic extends BaseService<{ id: string }> implements Hydratable {
|
|||
return this.table.bulkDelete(ids)
|
||||
}
|
||||
|
||||
async findAndUpdate(id: string, data: Partial<ListModel>) {
|
||||
const list = await this.table.get(id)
|
||||
if (!list) return
|
||||
return this.table.put({ ...list, ...data })
|
||||
}
|
||||
|
||||
async hydrate() {
|
||||
const lists = await ListService.findAll()
|
||||
listActions.upsertMany(lists)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
import type { FeedModel, ListModel, ListModelPoplutedFeeds } from "@follow/models/types"
|
||||
import { views } from "@follow/constants"
|
||||
import type {
|
||||
ExtractHonoParams,
|
||||
FeedModel,
|
||||
ListModel,
|
||||
ListModelPoplutedFeeds,
|
||||
} from "@follow/models/types"
|
||||
import { sleep } from "@follow/utils/utils"
|
||||
|
||||
import { runTransactionInScope } from "~/database"
|
||||
|
|
@ -6,6 +12,7 @@ import { apiClient } from "~/lib/api-fetch"
|
|||
import { ListService } from "~/services/list"
|
||||
|
||||
import { feedActions } from "../feed"
|
||||
import { subscriptionActions } from "../subscription"
|
||||
import { createImmerSetter, createTransaction, createZustandStore } from "../utils/helper"
|
||||
import type { ListState } from "./types"
|
||||
|
||||
|
|
@ -15,34 +22,33 @@ export const useListStore = createZustandStore<ListState>("list")(() => ({
|
|||
|
||||
const immerSet = createImmerSetter(useListStore)
|
||||
const get = useListStore.getState
|
||||
const set = useListStore.setState
|
||||
|
||||
class ListActionStatic {
|
||||
upsertMany(lists: ListModelPoplutedFeeds[]) {
|
||||
if (lists.length === 0) return
|
||||
const feeds = [] as FeedModel[]
|
||||
set((state) => {
|
||||
|
||||
immerSet((state) => {
|
||||
for (const list of lists) {
|
||||
state.lists[list.id] = list
|
||||
|
||||
if (!list.feeds) continue
|
||||
for (const feed of list.feeds) {
|
||||
feeds.push(feed)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
lists: { ...state.lists },
|
||||
}
|
||||
return state
|
||||
})
|
||||
|
||||
for (const list of lists) {
|
||||
if (!list.feeds) continue
|
||||
for (const feed of list.feeds) {
|
||||
feeds.push(feed)
|
||||
}
|
||||
}
|
||||
|
||||
feedActions.upsertMany(feeds)
|
||||
|
||||
runTransactionInScope(() => ListService.upsertMany(lists))
|
||||
}
|
||||
|
||||
async fetchOwnedLists() {
|
||||
const res = await apiClient.lists.list.$get()
|
||||
const res = await apiClient.lists.list.$get({ query: {} })
|
||||
this.upsertMany(res.data)
|
||||
|
||||
return res.data
|
||||
|
|
@ -119,6 +125,106 @@ class ListActionStatic {
|
|||
return res.data
|
||||
}
|
||||
|
||||
async createList(data: Omit<InsertListModel, "listId">) {
|
||||
const tx = createTransaction<never, FeedModel>()
|
||||
tx.execute(async (_, ctx) => {
|
||||
const res = await apiClient.lists.$post({
|
||||
json: data,
|
||||
})
|
||||
|
||||
Object.assign(ctx, res.data)
|
||||
})
|
||||
tx.persist(async (_, ctx) => {
|
||||
if (!ctx.id) return
|
||||
ListService.upsert({
|
||||
...data,
|
||||
id: ctx.id,
|
||||
type: "list",
|
||||
feedIds: [],
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: null,
|
||||
})
|
||||
|
||||
const userId = ctx.ownerUserId
|
||||
if (!userId) return
|
||||
subscriptionActions.upsertMany([
|
||||
{
|
||||
listId: ctx.id,
|
||||
view: views[data.view]!.view,
|
||||
createdAt: new Date().toISOString(),
|
||||
title: data.title,
|
||||
userId,
|
||||
feedId: ctx.id,
|
||||
isPrivate: false,
|
||||
},
|
||||
])
|
||||
this.upsertMany([
|
||||
{
|
||||
...data,
|
||||
...ctx,
|
||||
id: ctx.id,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: null,
|
||||
type: "list",
|
||||
feedIds: [],
|
||||
},
|
||||
])
|
||||
})
|
||||
tx.rollback(async (_, ctx) => {
|
||||
if (!ctx.id) return
|
||||
ListService.bulkDelete([ctx.id])
|
||||
})
|
||||
|
||||
await tx.run()
|
||||
}
|
||||
async updateList(data: InsertListModel) {
|
||||
const tx = createTransaction()
|
||||
const snapshot = get().lists[data.listId]
|
||||
|
||||
tx.execute(
|
||||
async () =>
|
||||
void (await apiClient.lists.$patch({
|
||||
json: {
|
||||
...data,
|
||||
},
|
||||
})),
|
||||
)
|
||||
tx.optimistic(async () => {
|
||||
if (!snapshot) return
|
||||
|
||||
this.upsertMany([
|
||||
{
|
||||
...snapshot,
|
||||
...data,
|
||||
id: data.listId,
|
||||
createdAt: snapshot?.createdAt,
|
||||
updatedAt: new Date().toISOString(),
|
||||
type: "list",
|
||||
view: data.view,
|
||||
feedIds: [],
|
||||
fee: data.fee,
|
||||
},
|
||||
])
|
||||
})
|
||||
tx.persist(async () => {
|
||||
ListService.findAndUpdate(data.listId, data)
|
||||
|
||||
if (!snapshot) return
|
||||
|
||||
subscriptionActions.changeListView(
|
||||
data.listId,
|
||||
views[snapshot.view]!.view,
|
||||
views[data.view]!.view,
|
||||
)
|
||||
})
|
||||
tx.rollback(async () => {
|
||||
if (!snapshot) return
|
||||
this.upsertMany([snapshot])
|
||||
})
|
||||
|
||||
await tx.run()
|
||||
}
|
||||
|
||||
clear() {
|
||||
immerSet((state) => {
|
||||
state.lists = {}
|
||||
|
|
@ -130,3 +236,5 @@ export const listActions = new ListActionStatic()
|
|||
|
||||
export const getListById = (listId: string): Nullable<ListModel> =>
|
||||
useListStore.getState().lists[listId]
|
||||
|
||||
type InsertListModel = ExtractHonoParams<typeof apiClient.lists.$patch>
|
||||
|
|
|
|||
|
|
@ -149,40 +149,36 @@ export const useFeedsGroupedData = (view: FeedViewType) => {
|
|||
}, [autoGroup, data])
|
||||
}
|
||||
|
||||
export const useListsGroupedData = (view: FeedViewType) => {
|
||||
export const useSubscriptionListIds = (view: FeedViewType) => {
|
||||
const data = useSubscriptionByView(view)
|
||||
|
||||
return useMemo(() => {
|
||||
if (!data || data.length === 0) return {}
|
||||
|
||||
const lists = data.filter((s) => s && "listId" in s)
|
||||
|
||||
const groupFolder = {} as Record<string, string[]>
|
||||
|
||||
for (const subscription of lists.filter((s) => !!s)) {
|
||||
groupFolder[subscription.feedId] = [subscription.feedId]
|
||||
if (!data || data.length === 0) return []
|
||||
const ids: string[] = []
|
||||
for (const subscription of data) {
|
||||
if (!subscription) continue
|
||||
if ("listId" in subscription) {
|
||||
ids.push(subscription.listId!)
|
||||
}
|
||||
}
|
||||
|
||||
return groupFolder
|
||||
return ids
|
||||
}, [data])
|
||||
}
|
||||
|
||||
export const useInboxesGroupedData = (view: FeedViewType) => {
|
||||
export const useSubscriptionInboxIds = (view: FeedViewType) => {
|
||||
const data = useSubscriptionByView(view)
|
||||
|
||||
return useMemo(() => {
|
||||
if (!data || data.length === 0) return {}
|
||||
|
||||
const inboxes = data.filter((s) => s && "inboxId" in s)
|
||||
|
||||
const groupFolder = {} as Record<string, string[]>
|
||||
|
||||
for (const subscription of inboxes.filter((s) => !!s)) {
|
||||
if (!subscription.inboxId) continue
|
||||
groupFolder[subscription.inboxId] = [subscription.inboxId]
|
||||
if (!data || data.length === 0) return []
|
||||
// return data.filter((s) => s && "inboxId" in s). as unknown[] as string[]
|
||||
const ids: string[] = []
|
||||
for (const subscription of data) {
|
||||
if (!subscription) continue
|
||||
if ("inboxId" in subscription) {
|
||||
ids.push(subscription.inboxId!)
|
||||
}
|
||||
}
|
||||
|
||||
return groupFolder
|
||||
return ids
|
||||
}, [data])
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ export type ExtractBizResponse<T extends (...args: any[]) => any> = Exclude<
|
|||
Awaited<ReturnType<T>>,
|
||||
undefined
|
||||
>
|
||||
export type ExtractHonoParams<T extends (...args: any[]) => any> = Parameters<T>[0]["json"]
|
||||
|
||||
export type ActiveList = {
|
||||
id: string | number
|
||||
|
|
|
|||
Loading…
Reference in New Issue