From 1893b5d9542c5599fc97581b72ba412e202bfbd0 Mon Sep 17 00:00:00 2001 From: Nexmoe <16796652+nexmoe@users.noreply.github.com> Date: Sat, 15 Nov 2025 11:39:25 +0800 Subject: [PATCH] feat(subscriptions): add item queueing UI and IPC handler --- resources/drizzle/meta/0000_snapshot.json | 9 +- resources/drizzle/meta/_journal.json | 2 +- src/main/ipc/services/subscription-service.ts | 5 + src/main/lib/subscription-scheduler.ts | 13 ++ src/renderer/src/locales/en.json | 6 +- src/renderer/src/pages/Subscriptions.tsx | 178 +++++++++++------- 6 files changed, 134 insertions(+), 79 deletions(-) diff --git a/resources/drizzle/meta/0000_snapshot.json b/resources/drizzle/meta/0000_snapshot.json index be2caeb..26b8b9e 100644 --- a/resources/drizzle/meta/0000_snapshot.json +++ b/resources/drizzle/meta/0000_snapshot.json @@ -273,19 +273,14 @@ "indexes": { "subscription_items_subscription_idx": { "name": "subscription_items_subscription_idx", - "columns": [ - "subscription_id" - ], + "columns": ["subscription_id"], "isUnique": false } }, "foreignKeys": {}, "compositePrimaryKeys": { "subscription_items_pk": { - "columns": [ - "subscription_id", - "item_id" - ], + "columns": ["subscription_id", "item_id"], "name": "subscription_items_pk" } }, diff --git a/resources/drizzle/meta/_journal.json b/resources/drizzle/meta/_journal.json index 7073213..d99cc45 100644 --- a/resources/drizzle/meta/_journal.json +++ b/resources/drizzle/meta/_journal.json @@ -10,4 +10,4 @@ "breakpoints": true } ] -} \ No newline at end of file +} diff --git a/src/main/ipc/services/subscription-service.ts b/src/main/ipc/services/subscription-service.ts index 4da1e22..e8669b9 100644 --- a/src/main/ipc/services/subscription-service.ts +++ b/src/main/ipc/services/subscription-service.ts @@ -154,6 +154,11 @@ class SubscriptionService extends IpcService { async refresh(_context: IpcContext, id?: string): Promise { await subscriptionScheduler.runNow(id) } + + @IpcMethod() + async queueItem(_context: IpcContext, id: string, itemId: string): Promise { + return subscriptionScheduler.queueItem(id, itemId) + } } export { SubscriptionService } diff --git a/src/main/lib/subscription-scheduler.ts b/src/main/lib/subscription-scheduler.ts index a57b8ea..c4e02f2 100644 --- a/src/main/lib/subscription-scheduler.ts +++ b/src/main/lib/subscription-scheduler.ts @@ -149,6 +149,19 @@ export class SubscriptionScheduler extends EventEmitter { await this.checkAll() } + async queueItem(subscriptionId: string, itemId: string): Promise { + const subscription = subscriptionManager.getById(subscriptionId) + if (!subscription) { + return false + } + const item = subscription.items.find((entry) => entry.id === itemId) + if (!item || item.addedToQueue) { + return false + } + await this.queueDownload(subscriptionId, itemId, item.url) + return true + } + private scheduleNextRun(initialDelay?: number): void { if (this.timer) { clearTimeout(this.timer) diff --git a/src/renderer/src/locales/en.json b/src/renderer/src/locales/en.json index 16e4acd..baf3fcc 100644 --- a/src/renderer/src/locales/en.json +++ b/src/renderer/src/locales/en.json @@ -443,7 +443,8 @@ "notQueued": "Not in the download queue yet" }, "actions": { - "open": "Open in browser" + "open": "Open in browser", + "queue": "Add to download queue" } }, "labels": { @@ -459,6 +460,9 @@ "refreshStarted": "Refresh started", "removed": "Subscription removed", "updated": "Subscription updated", + "itemQueued": "Added to download queue", + "itemAlreadyQueued": "This video is already queued", + "queueError": "Failed to add to download queue.", "openLinkError": "Failed to open the video link.", "resolveError": "Failed to resolve RSS feed URL." }, diff --git a/src/renderer/src/pages/Subscriptions.tsx b/src/renderer/src/pages/Subscriptions.tsx index 1818019..6b8d7a1 100644 --- a/src/renderer/src/pages/Subscriptions.tsx +++ b/src/renderer/src/pages/Subscriptions.tsx @@ -36,7 +36,7 @@ import { import type { DownloadStatus, SubscriptionFeedItem, SubscriptionRule } from '@shared/types' import dayjs from 'dayjs' import { useAtom, useAtomValue, useSetAtom } from 'jotai' -import { Edit, ExternalLink, Plus, Power, RefreshCw, Trash2 } from 'lucide-react' +import { Download, Edit, ExternalLink, Plus, Power, RefreshCw, Trash2 } from 'lucide-react' import { useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' @@ -522,6 +522,27 @@ function SubscriptionCard({ subscription }: { subscription: SubscriptionRule }) } } + const handleQueueItem = useCallback( + async (item: SubscriptionFeedItem) => { + if (item.addedToQueue) { + toast.info(t('subscriptions.notifications.itemAlreadyQueued')) + return + } + try { + const queued = await ipcServices.subscriptions.queueItem(subscription.id, item.id) + if (queued) { + toast.success(t('subscriptions.notifications.itemQueued')) + return + } + toast.info(t('subscriptions.notifications.itemAlreadyQueued')) + } catch (error) { + console.error('Failed to queue subscription item:', error) + toast.error(t('subscriptions.notifications.queueError')) + } + }, + [subscription.id, t] + ) + if (feedItems.length === 0) { return (
@@ -548,78 +569,95 @@ function SubscriptionCard({ subscription }: { subscription: SubscriptionRule }) : t('subscriptions.items.tooltip.notQueued') const badgeClass = item.addedToQueue ? 'bg-emerald-500' : 'bg-black/70' return ( -
-
- {item.thumbnail ? ( - - ) : ( -
- {t('subscriptions.labels.noThumbnail')} -
- )} -
-
- {subscription.coverUrl ? ( -
+ + +
+
+ {item.thumbnail ? ( -
- ) : ( -
- {(subscription.title || t('subscriptions.labels.unknown')).slice(0, 1)} -
- )} - - {subscription.title || t('subscriptions.labels.unknown')} - -
-
- {dayjs(item.publishedAt).format('YYYY-MM-DD HH:mm')} -
- - - + {t('subscriptions.labels.noThumbnail')} +
+ )} +
+
+ {subscription.coverUrl ? ( +
+ +
+ ) : ( +
+ {(subscription.title || t('subscriptions.labels.unknown')).slice(0, 1)} +
)} - > - {badgeLabel} - - - {tooltipLabel} - -
-
-
-

- {item.title} -

-
-
- -
-
-
+ + {subscription.title || t('subscriptions.labels.unknown')} + +
+
+ {dayjs(item.publishedAt).format('YYYY-MM-DD HH:mm')} +
+ + + + {badgeLabel} + + + {tooltipLabel} + + +
+
+

+ {item.title} +

+
+
+ +
+
+ + + + void handleQueueItem(item)} + disabled={item.addedToQueue} + > + + {t('subscriptions.items.actions.queue')} + + void handleOpenItem(item.url)}> + + {t('subscriptions.items.actions.open')} + + + ) })}